mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-08 12:57:25 +02:00
Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76eeb3d0f7 | ||
|
|
279caca502 | ||
|
|
1c6bdc7369 | ||
|
|
8705d3450a | ||
|
|
33277bf25f | ||
|
|
cbd2342208 | ||
|
|
831f87f62e | ||
|
|
06c9609587 | ||
|
|
9b43d31b75 | ||
|
|
23a3c7f960 | ||
|
|
81acfaa424 | ||
|
|
9de40292c4 | ||
|
|
c489a7ed75 | ||
|
|
6c26eb8333 | ||
|
|
6a57c5ed74 | ||
|
|
dce33aaabc | ||
|
|
b1db0ff498 | ||
|
|
a348b483c3 | ||
|
|
d7ce99526c | ||
|
|
90f0cb862a | ||
|
|
ff0c7f6a50 | ||
|
|
d608b171de | ||
|
|
5e76488276 | ||
|
|
bc265c09ff | ||
|
|
c7fe828252 | ||
|
|
9fbf67d451 | ||
|
|
e5572d5833 | ||
|
|
6b647e4cf3 | ||
|
|
4661dc3124 | ||
|
|
e428346d3b | ||
|
|
59af264463 | ||
|
|
01c60a2faa | ||
|
|
0f880143e3 | ||
|
|
bbba8b4b30 | ||
|
|
3a3a864bcb | ||
|
|
2bc0b3716a | ||
|
|
1e89e70e57 | ||
|
|
c160e7a945 | ||
|
|
d6f6938465 | ||
|
|
0d2a154622 | ||
|
|
142ea20eaf | ||
|
|
c9b910937c | ||
|
|
70c863c80c | ||
|
|
b90d206514 | ||
|
|
29ea484a26 | ||
|
|
8aa6e83673 | ||
|
|
aef8e32da3 | ||
|
|
be0f64a6ad | ||
|
|
3c88fabab3 | ||
|
|
ee85ee0660 | ||
|
|
f639cf1134 | ||
|
|
65549d4456 | ||
|
|
d194681981 | ||
|
|
5b5ddb251b | ||
|
|
5ce72e2056 | ||
|
|
ef2218a3d8 | ||
|
|
2745a3bfaa | ||
|
|
dd3fc3b0be | ||
|
|
957dfaef52 | ||
|
|
9e8015f475 | ||
|
|
38c8977dab | ||
|
|
fdf2bc6adf | ||
|
|
89344f92b3 | ||
|
|
8e8eeaf6c8 | ||
|
|
dcf2f1dfdf | ||
|
|
a7f183d40d | ||
|
|
114de52434 | ||
|
|
468cab6982 | ||
|
|
02f12d40f0 | ||
|
|
1da452aa92 | ||
|
|
2cf2adafd3 | ||
|
|
261e72cae4 | ||
|
|
a6d682fdee | ||
|
|
f8013c0ec0 | ||
|
|
d6fff29a72 | ||
|
|
dba5a44c60 |
@@ -1,6 +1,6 @@
|
||||
language: scala
|
||||
sudo: true
|
||||
jdk:
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
- oraclejdk11
|
||||
- openjdk8
|
||||
@@ -17,5 +17,3 @@ cache:
|
||||
- $HOME/.sbt/boot
|
||||
- $HOME/.sbt/launchers
|
||||
- $HOME/.coursier
|
||||
- $HOME/.embedmysql
|
||||
- $HOME/.embedpostgresql
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
# Changelog
|
||||
All changes to the project will be documented in this file.
|
||||
|
||||
### 4.31.0 - 17 Mar 2019
|
||||
- Docker support in CI plugin
|
||||
- Verify GPG key signed commit
|
||||
- OAuth2 Token (sent as a parameter) authentication support and new APIs in Web API
|
||||
- OGP (Open Graph protocol) support
|
||||
- Username completion with avatars
|
||||
|
||||
### 4.30.1 - 22 Dec 2018
|
||||
- Bug fix for several WebHooks and Web API
|
||||
|
||||
|
||||
18
README.md
18
README.md
@@ -68,17 +68,13 @@ Support
|
||||
- If you can't find same question and report, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
|
||||
- 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.
|
||||
|
||||
What's New in 4.30.x
|
||||
What's New in 4.31.x
|
||||
-------------
|
||||
### 4.30.1 - 22 Dec 2018
|
||||
- Bug fix for several WebHooks and Web API
|
||||
|
||||
### 4.30.0 - 15 Dec 2018
|
||||
- Automatic ChangeLog Summary generation for new Releases
|
||||
- A lot of GitBucket Web API updates to increase compatibility with the GitHub API.
|
||||
- Display of checkboxes in Markdown files in Git repositories
|
||||
- A new extension point for plugins: anonymousAccessiblePaths
|
||||
- Group support in the Gist Plugin
|
||||
- Allow redirection to the Release Page from the Activity Timeline Page
|
||||
### 4.31.0 - 17 Mar 2019
|
||||
- Docker support in CI plugin
|
||||
- Verify GPG key signed commit
|
||||
- OAuth2 Token (sent as a parameter) authentication support and new APIs in Web API
|
||||
- OGP (Open Graph protocol) support
|
||||
- Username completion with avatars
|
||||
|
||||
See the [change log](CHANGELOG.md) for all of the updates.
|
||||
|
||||
11
build.sbt
11
build.sbt
@@ -3,10 +3,10 @@ import com.typesafe.sbt.pgp.PgpKeys._
|
||||
|
||||
val Organization = "io.github.gitbucket"
|
||||
val Name = "gitbucket"
|
||||
val GitBucketVersion = "4.30.1"
|
||||
val GitBucketVersion = "4.31.0"
|
||||
val ScalatraVersion = "2.6.3"
|
||||
val JettyVersion = "9.4.14.v20181114"
|
||||
val JgitVersion = "5.1.3.201810200350-r"
|
||||
val JgitVersion = "5.2.0.201812061821-r"
|
||||
|
||||
lazy val root = (project in file("."))
|
||||
.enablePlugins(SbtTwirl, ScalatraPlugin)
|
||||
@@ -21,6 +21,8 @@ scalaVersion := "2.12.8"
|
||||
|
||||
scalafmtOnCompile := true
|
||||
|
||||
coverageExcludedPackages := ".*\\.html\\..*"
|
||||
|
||||
// dependency settings
|
||||
resolvers ++= Seq(
|
||||
Classpaths.typesafeReleases,
|
||||
@@ -66,8 +68,9 @@ libraryDependencies ++= Seq(
|
||||
"junit" % "junit" % "4.12" % "test",
|
||||
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test",
|
||||
"org.mockito" % "mockito-core" % "2.23.4" % "test",
|
||||
"com.wix" % "wix-embedded-mysql" % "4.2.0" % "test",
|
||||
"ru.yandex.qatools.embed" % "postgresql-embedded" % "2.10" % "test",
|
||||
"com.dimafeng" %% "testcontainers-scala" % "0.22.0" % "test",
|
||||
"org.testcontainers" % "mysql" % "1.10.3" % "test",
|
||||
"org.testcontainers" % "postgresql" % "1.10.3" % "test",
|
||||
"net.i2p.crypto" % "eddsa" % "0.3.0",
|
||||
"is.tagomor.woothee" % "woothee-java" % "1.8.0",
|
||||
"org.ec4j.core" % "ec4j-core" % "0.0.3"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
How to run from the source tree
|
||||
How to build and run from the source tree
|
||||
========
|
||||
|
||||
Install [sbt](http://www.scala-sbt.org/index.html) at first.
|
||||
First of all, Install [sbt](http://www.scala-sbt.org/index.html).
|
||||
|
||||
```
|
||||
```shell
|
||||
$ brew install sbt
|
||||
```
|
||||
|
||||
@@ -12,7 +12,7 @@ Run for Development
|
||||
|
||||
If you want to test GitBucket, type the following command in the root directory of the source tree.
|
||||
|
||||
```
|
||||
```shell
|
||||
$ sbt ~jetty:start
|
||||
```
|
||||
|
||||
@@ -25,7 +25,7 @@ Build war file
|
||||
|
||||
To build war file, run the following command:
|
||||
|
||||
```
|
||||
```shell
|
||||
$ sbt package
|
||||
```
|
||||
|
||||
@@ -33,7 +33,7 @@ $ sbt package
|
||||
|
||||
To build an executable war file, run
|
||||
|
||||
```
|
||||
```shell
|
||||
$ sbt executable
|
||||
```
|
||||
|
||||
@@ -41,8 +41,21 @@ at the top of the source tree. It generates executable `gitbucket.war` into `tar
|
||||
|
||||
Run tests spec
|
||||
---------
|
||||
Before running tests, you need to install docker.
|
||||
|
||||
```shell
|
||||
$ brew cask install docker # Install Docker
|
||||
$ open /Applications/Docker.app # Start Docker
|
||||
```
|
||||
|
||||
To run the full series of tests, run the following command:
|
||||
|
||||
```
|
||||
```shell
|
||||
$ sbt test
|
||||
```
|
||||
|
||||
If you don't have docker, you can skip docker tests which require docker as follows:
|
||||
|
||||
```shell
|
||||
$ sbt "testOnly * -- -l ExternalDBTest"
|
||||
```
|
||||
22
doc/debug.md
Normal file
22
doc/debug.md
Normal file
@@ -0,0 +1,22 @@
|
||||
Debug GitBucket on IntelliJ
|
||||
========
|
||||
Add following configuration for allowing remote debugging to `buils.sbt`:
|
||||
|
||||
```scala
|
||||
javaOptions in Jetty ++= Seq(
|
||||
"-Xdebug",
|
||||
"-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000"
|
||||
)
|
||||
```
|
||||
|
||||
Run GitBucket:
|
||||
|
||||
```shell
|
||||
$ sbt ~jetty:start
|
||||
```
|
||||
|
||||
In IntelliJ, create remote debug configuration as follows. Make sure port number is same as above configuration.
|
||||
|
||||

|
||||
|
||||
Then you can start debugging on IntelliJ!
|
||||
@@ -9,16 +9,17 @@ This directory has following structure:
|
||||
* /repositories
|
||||
* /USER_NAME
|
||||
* /REPO_NAME.git (substance of repository. GitServlet sees this directory)
|
||||
* /REPO_NAME.wiki.git (wiki repository)
|
||||
* /REPO_NAME
|
||||
* /issues (files which are attached to issue)
|
||||
* /REPO_NAME.wiki.git (wiki repository)
|
||||
* /lfs (LFS managed files)
|
||||
* /data
|
||||
* /USER_NAME
|
||||
* /files
|
||||
* avatar.xxx (image file of user avatar)
|
||||
* /plugins
|
||||
* /PLUGIN_NAME
|
||||
* plugin.js
|
||||
* plugin.jar
|
||||
* /.installed (copied available plugins from the parent directory automatically)
|
||||
* /tmp
|
||||
* /_upload
|
||||
* /SESSION_ID (removed at session timeout)
|
||||
|
||||
172
doc/licenses.md
172
doc/licenses.md
@@ -4,98 +4,128 @@ Category | License | Dependency | Notes
|
||||
--- | --- | --- | ---
|
||||
Apache | [ Apache License, Version 2.0 ]( http://opensource.org/licenses/apache2.0.php ) | org.osgi # org.osgi.core # 4.3.1 | <notextile></notextile>
|
||||
Apache | [Apache 2](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.googlecode.javaewah # JavaEWAH # 1.1.6 | <notextile></notextile>
|
||||
Apache | [Apache 2](http://www.apache.org/licenses/LICENSE-2.0) | org.cache2k # cache2k-all # 1.0.0.CR1 | <notextile></notextile>
|
||||
Apache | [Apache 2](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.objenesis # objenesis # 2.5 | <notextile></notextile>
|
||||
Apache | [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0) | org.apache.sshd # apache-sshd # 1.4.0 | <notextile></notextile>
|
||||
Apache | [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0) | org.apache.sshd # sshd-core # 1.4.0 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | com.typesafe # config # 1.3.1 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | com.typesafe.akka # akka-actor_2.12 # 2.5.0 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | commons-io # commons-io # 2.5 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | fr.brouillard.oss.security.xhub # xhub4j-core # 1.0.0 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.commons # commons-compress # 1.13 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.commons # commons-email # 1.4 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.commons # commons-lang3 # 3.5 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.httpcomponents # httpclient # 4.5.3 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.httpcomponents # httpcore # 4.4.6 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.httpcomponents # httpmime # 4.5.2 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.tika # tika-core # 1.14 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.liquibase # liquibase-core # 3.4.1 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-http # 9.2.19.v20160908 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-io # 9.2.19.v20160908 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-security # 9.2.19.v20160908 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-server # 9.2.19.v20160908 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-servlet # 9.2.19.v20160908 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-util # 9.2.19.v20160908 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-webapp # 9.2.19.v20160908 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-xml # 9.2.19.v20160908 | <notextile></notextile>
|
||||
Apache | [Apache Software License, Version 1.1](http://www.apache.org/licenses/LICENSE-1.1) | org.bouncycastle # bcpg-jdk15on # 1.56 | <notextile></notextile>
|
||||
Apache | [Apache 2](http://www.apache.org/licenses/LICENSE-2.0) | org.cache2k # cache2k-all # 1.2.0.Final | <notextile></notextile>
|
||||
Apache | [Apache 2](http://www.apache.org/licenses/LICENSE-2.0) | org.cache2k # cache2k-api # 1.2.0.Final | <notextile></notextile>
|
||||
Apache | [Apache 2](http://www.apache.org/licenses/LICENSE-2.0) | org.cache2k # cache2k-core # 1.2.0.Final | <notextile></notextile>
|
||||
Apache | [Apache 2](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.objenesis # objenesis # 2.6 | <notextile></notextile>
|
||||
Apache | [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0) | org.apache.sshd # apache-sshd # 2.1.0 | <notextile></notextile>
|
||||
Apache | [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0) | org.apache.sshd # sshd-cli # 2.1.0 | <notextile></notextile>
|
||||
Apache | [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0) | org.apache.sshd # sshd-common # 2.1.0 | <notextile></notextile>
|
||||
Apache | [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0) | org.apache.sshd # sshd-core # 2.1.0 | <notextile></notextile>
|
||||
Apache | [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0) | org.apache.sshd # sshd-putty # 2.1.0 | <notextile></notextile>
|
||||
Apache | [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0) | org.apache.sshd # sshd-scp # 2.1.0 | <notextile></notextile>
|
||||
Apache | [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0) | org.apache.sshd # sshd-sftp # 2.1.0 | <notextile></notextile>
|
||||
Apache | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | net.java.dev.jna # jna # 4.5.1 | <notextile></notextile>
|
||||
Apache | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | net.java.dev.jna # jna-platform # 4.5.1 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.github.stephenc.jcip # jcip-annotations # 1.0-1 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.kohlschutter.junixsocket # junixsocket-common # 2.0.4 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.kohlschutter.junixsocket # junixsocket-native-common # 2.0.4 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) | com.typesafe # config # 1.3.3 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | com.typesafe.akka # akka-actor_2.12 # 2.5.18 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | commons-io # commons-io # 2.6 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | is.tagomor.woothee # woothee-java # 1.8.0 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.commons # commons-compress # 1.18 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.commons # commons-email # 1.5 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.commons # commons-lang3 # 3.6 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.httpcomponents # httpclient # 4.5.6 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.httpcomponents # httpcore # 4.4.10 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.httpcomponents # httpmime # 4.5.3 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.tika # tika-core # 1.19.1 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.ec4j.core # ec4j-core # 0.0.3 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.liquibase # liquibase-core # 3.6.2 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.yaml # snakeyaml # 1.18 | <notextile></notextile>
|
||||
Apache | [Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) | com.nimbusds # oauth2-oidc-sdk # 5.64.4 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-http # 9.4.6.v20170531 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-io # 9.4.6.v20170531 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-security # 9.4.6.v20170531 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-server # 9.4.6.v20170531 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-servlet # 9.4.6.v20170531 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-util # 9.4.6.v20170531 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-webapp # 9.4.6.v20170531 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-xml # 9.4.6.v20170531 | <notextile></notextile>
|
||||
Apache | [Apache Software License, Version 1.1](http://www.apache.org/licenses/LICENSE-1.1) | org.bouncycastle # bcpg-jdk15on # 1.60 | <notextile></notextile>
|
||||
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | com.github.bkromhout # java-diff-utils # 2.1.1 | <notextile></notextile>
|
||||
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.html) | com.typesafe.play # twirl-api_2.12 # 1.3.7 | <notextile></notextile>
|
||||
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.json4s # json4s-ast_2.12 # 3.5.1 | <notextile></notextile>
|
||||
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.json4s # json4s-core_2.12 # 3.5.1 | <notextile></notextile>
|
||||
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.json4s # json4s-jackson_2.12 # 3.5.1 | <notextile></notextile>
|
||||
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.json4s # json4s-scalap_2.12 # 3.5.1 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.enragedginger # akka-quartz-scheduler_2.12 # 1.6.0-akka-2.4.x | <notextile></notextile>
|
||||
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.html) | com.typesafe.play # twirl-api_2.12 # 1.3.15 | <notextile></notextile>
|
||||
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.json4s # json4s-ast_2.12 # 3.5.2 | <notextile></notextile>
|
||||
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.json4s # json4s-core_2.12 # 3.5.2 | <notextile></notextile>
|
||||
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.json4s # json4s-jackson_2.12 # 3.5.2 | <notextile></notextile>
|
||||
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.json4s # json4s-scalap_2.12 # 3.5.2 | <notextile></notextile>
|
||||
Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) | org.scala-lang # scala-library # 2.12.8 | <notextile></notextile>
|
||||
Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) | org.scala-lang # scala-reflect # 2.12.8 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.enragedginger # akka-quartz-scheduler_2.12 # 1.7.0-akka-2.5.x | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.fasterxml.jackson.core # jackson-annotations # 2.8.0 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.fasterxml.jackson.core # jackson-core # 2.8.4 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.fasterxml.jackson.core # jackson-databind # 2.8.4 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.github.takezoe # blocking-slick-32_2.12 # 0.0.10 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.github.takezoe # blocking-slick-32_2.12 # 0.0.11 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.google.code.findbugs # jsr305 # 3.0.0 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.zaxxer # HikariCP # 2.6.1 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | commons-codec # commons-codec # 1.9 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | commons-logging # commons-logging # 1.2 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | de.flapdoodle.embed # de.flapdoodle.embed.process # 2.0.1 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.nimbusds # lang-tag # 1.4.3 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.nimbusds # nimbus-jose-jwt # 5.5 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.zaxxer # HikariCP # 3.2.0 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | eu.medsea.mimeutil # mime-util # 2.1.3 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | io.github.gitbucket # markedj # 1.0.15 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | io.github.gitbucket # scalatra-forms_2.12 # 1.1.0 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | io.github.gitbucket # solidbase # 1.0.2 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | net.bytebuddy # byte-buddy # 1.6.11 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | net.bytebuddy # byte-buddy-agent # 1.6.11 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.quartz-scheduler # quartz # 2.2.3 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | ru.yandex.qatools.embed # postgresql-embedded # 2.0 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | tomcat # tomcat-apr # 5.5.23 | <notextile></notextile>
|
||||
Apache | [the Apache License, ASL Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.scalactic # scalactic_2.12 # 3.0.0 | <notextile></notextile>
|
||||
Apache | [the Apache License, ASL Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.scalatest # scalatest_2.12 # 3.0.0 | <notextile></notextile>
|
||||
BSD | [BSD](LICENSE.txt) | com.thoughtworks.paranamer # paranamer # 2.8 | <notextile></notextile>
|
||||
BSD | [BSD](http://software.clapper.org/grizzled-slf4j/license.html) | org.clapper # grizzled-slf4j_2.12 # 1.3.0 | <notextile></notextile>
|
||||
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-common_2.12 # 2.5.0 | <notextile></notextile>
|
||||
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-json_2.12 # 2.5.0 | <notextile></notextile>
|
||||
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-scalatest_2.12 # 2.5.0 | <notextile></notextile>
|
||||
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-test_2.12 # 2.5.0 | <notextile></notextile>
|
||||
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra_2.12 # 2.5.0 | <notextile></notextile>
|
||||
BSD | [BSD 3-Clause](http://www.scala-lang.org/license.html) | org.scala-lang # scala-library # 2.12.3 | <notextile></notextile>
|
||||
BSD | [BSD 3-Clause](http://www.scala-lang.org/license.html) | org.scala-lang # scala-reflect # 2.12.3 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | io.github.gitbucket # solidbase # 1.0.3 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | net.bytebuddy # byte-buddy # 1.9.3 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | net.bytebuddy # byte-buddy-agent # 1.9.3 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | net.minidev # accessors-smart # 1.2 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | net.minidev # json-smart # 2.3 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.jetbrains # annotations # 15.0 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.quartz-scheduler # quartz # 2.3.0 | <notextile></notextile>
|
||||
Apache | [the Apache License, ASL Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.scalactic # scalactic_2.12 # 3.0.3 | <notextile></notextile>
|
||||
Apache | [the Apache License, ASL Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.scalatest # scalatest_2.12 # 3.0.3 | <notextile></notextile>
|
||||
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-common_2.12 # 2.6.3 | <notextile></notextile>
|
||||
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-forms_2.12 # 2.6.3 | <notextile></notextile>
|
||||
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-json_2.12 # 2.6.3 | <notextile></notextile>
|
||||
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-scalatest_2.12 # 2.6.3 | <notextile></notextile>
|
||||
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-test_2.12 # 2.6.3 | <notextile></notextile>
|
||||
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra_2.12 # 2.6.3 | <notextile></notextile>
|
||||
BSD | [BSD 3-clause](http://opensource.org/licenses/BSD-3-Clause) | org.scala-lang.modules # scala-java8-compat_2.12 # 0.8.0 | <notextile></notextile>
|
||||
BSD | [BSD 3-clause](http://opensource.org/licenses/BSD-3-Clause) | org.scala-lang.modules # scala-parser-combinators_2.12 # 1.0.4 | <notextile></notextile>
|
||||
BSD | [BSD 3-clause](http://opensource.org/licenses/BSD-3-Clause) | org.scala-lang.modules # scala-parser-combinators_2.12 # 1.0.6 | <notextile></notextile>
|
||||
BSD | [BSD 3-clause](http://opensource.org/licenses/BSD-3-Clause) | org.scala-lang.modules # scala-xml_2.12 # 1.0.6 | <notextile></notextile>
|
||||
BSD | [BSD License](http://www.opensource.org/licenses/bsd-license.php) | com.wix # wix-embedded-mysql # 2.1.4 | <notextile></notextile>
|
||||
BSD | [BSD-2-Clause](https://jdbc.postgresql.org/about/license.html) | org.postgresql # postgresql # 42.0.0 | <notextile></notextile>
|
||||
BSD | [Eclipse Distribution License (New BSD License)](null) | org.eclipse.jgit # org.eclipse.jgit # 4.8.0.201706111038-r | <notextile></notextile>
|
||||
BSD | [Eclipse Distribution License (New BSD License)](null) | org.eclipse.jgit # org.eclipse.jgit.archive # 4.8.0.201706111038-r | <notextile></notextile>
|
||||
BSD | [Eclipse Distribution License (New BSD License)](null) | org.eclipse.jgit # org.eclipse.jgit.http.server # 4.8.0.201706111038-r | <notextile></notextile>
|
||||
BSD | [BSD-2-Clause](https://jdbc.postgresql.org/about/license.html) | org.postgresql # postgresql # 42.2.5 | <notextile></notextile>
|
||||
BSD | [Eclipse Distribution License (New BSD License)](null) | org.eclipse.jgit # org.eclipse.jgit # 5.2.0.201812061821-r | <notextile></notextile>
|
||||
BSD | [Eclipse Distribution License (New BSD License)](null) | org.eclipse.jgit # org.eclipse.jgit.archive # 5.2.0.201812061821-r | <notextile></notextile>
|
||||
BSD | [Eclipse Distribution License (New BSD License)](null) | org.eclipse.jgit # org.eclipse.jgit.http.server # 5.2.0.201812061821-r | <notextile></notextile>
|
||||
BSD | [New BSD License](http://www.opensource.org/licenses/bsd-license.php) | org.hamcrest # hamcrest-core # 1.3 | <notextile></notextile>
|
||||
BSD | [Revised BSD](http://www.jcraft.com/jsch/LICENSE.txt) | com.jcraft # jsch # 0.1.54 | <notextile></notextile>
|
||||
BSD | [Revised BSD](http://www.jcraft.com/jzlib/LICENSE.txt) | com.jcraft # jzlib # 1.1.1 | <notextile></notextile>
|
||||
BSD | [Two-clause BSD-style license](http://github.com/slick/slick/blob/master/LICENSE.txt) | com.typesafe.slick # slick_2.12 # 3.2.1 | <notextile></notextile>
|
||||
CC0 | [CC0](http://creativecommons.org/publicdomain/zero/1.0/) | org.reactivestreams # reactive-streams # 1.0.0 | <notextile></notextile>
|
||||
CDDL | [COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0](https://glassfish.dev.java.net/public/CDDLv1.0.html) | javax.activation # activation # 1.1.1 | <notextile></notextile>
|
||||
GPL | [CDDL/GPLv2+CE](https://glassfish.java.net/public/CDDL+GPL_1_1.html) | com.sun.mail # javax.mail # 1.5.2 | <notextile></notextile>
|
||||
CC0 | [CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/) | net.i2p.crypto # eddsa # 0.3.0 | <notextile></notextile>
|
||||
CC0 | [CC0 1.0 Universal License](http://creativecommons.org/publicdomain/zero/1.0/) | org.scijava # native-lib-loader # 2.0.2 | <notextile></notextile>
|
||||
CDDL | [Common Development and Distribution License (CDDL) v1.0](https://glassfish.dev.java.net/public/CDDLv1.0.html) | javax.activation # activation # 1.1 | <notextile></notextile>
|
||||
GPL | [CDDL/GPLv2+CE](https://javaee.github.io/javamail/LICENSE) | com.sun.mail # javax.mail # 1.6.1 | <notextile></notextile>
|
||||
GPL | [GPL2 w/ CPE](https://oss.oracle.com/licenses/CDDL+GPL-1.1) | javax.xml.bind # jaxb-api # 2.3.0 | <notextile></notextile>
|
||||
GPL with Classpath Extension | [CDDL + GPLv2 with classpath exception](https://github.com/javaee/javax.annotation/blob/master/LICENSE) | javax.annotation # javax.annotation-api # 1.3.1 | <notextile></notextile>
|
||||
GPL with Classpath Extension | [CDDL + GPLv2 with classpath exception](https://glassfish.dev.java.net/nonav/public/CDDL+GPL.html) | javax.servlet # javax.servlet-api # 3.1.0 | <notextile></notextile>
|
||||
LGPL | [GNU Lesser General Public License](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html) | ch.qos.logback # logback-classic # 1.2.3 | <notextile></notextile>
|
||||
LGPL | [GNU Lesser General Public License](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html) | ch.qos.logback # logback-core # 1.2.3 | <notextile></notextile>
|
||||
LGPL | [LGPL, version 2.1](http://www.gnu.org/licenses/licenses.html) | net.java.dev.jna # jna # 4.0.0 | <notextile></notextile>
|
||||
LGPL | [LGPL, version 2.1](http://www.gnu.org/licenses/licenses.html) | net.java.dev.jna # jna-platform # 4.0.0 | <notextile></notextile>
|
||||
LGPL | [LGPL-2.1](null) | org.mariadb.jdbc # mariadb-java-client # 2.0.3 | <notextile></notextile>
|
||||
LGPL | [GNU Lesser General Public License, Version 2.1](http://www.gnu.org/licenses/lgpl-2.1.html) | com.mchange # c3p0 # 0.9.5.2 | <notextile></notextile>
|
||||
LGPL | [GNU Lesser General Public License, Version 2.1](http://www.gnu.org/licenses/lgpl-2.1.html) | com.mchange # mchange-commons-java # 0.2.11 | <notextile></notextile>
|
||||
LGPL | [LGPL-2.1](null) | org.mariadb.jdbc # mariadb-java-client # 2.3.0 | <notextile></notextile>
|
||||
MIT | [MIT](http://opensource.org/licenses/MIT) | org.rnorth # tcp-unix-socket-proxy # 1.0.2 | <notextile></notextile>
|
||||
MIT | [MIT](http://opensource.org/licenses/MIT) | org.rnorth.duct-tape # duct-tape # 1.0.7 | <notextile></notextile>
|
||||
MIT | [MIT](http://opensource.org/licenses/MIT) | org.rnorth.visible-assertions # visible-assertions # 2.1.1 | <notextile></notextile>
|
||||
MIT | [MIT](http://opensource.org/licenses/MIT) | org.testcontainers # database-commons # 1.10.3 | <notextile></notextile>
|
||||
MIT | [MIT](http://opensource.org/licenses/MIT) | org.testcontainers # jdbc # 1.10.3 | <notextile></notextile>
|
||||
MIT | [MIT](http://opensource.org/licenses/MIT) | org.testcontainers # mysql # 1.10.3 | <notextile></notextile>
|
||||
MIT | [MIT](http://opensource.org/licenses/MIT) | org.testcontainers # postgresql # 1.10.3 | <notextile></notextile>
|
||||
MIT | [MIT](http://opensource.org/licenses/MIT) | org.testcontainers # testcontainers # 1.10.3 | <notextile></notextile>
|
||||
MIT | [MIT License](http://www.opensource.org/licenses/mit-license.php) | org.slf4j # slf4j-api # 1.7.25 | <notextile></notextile>
|
||||
MIT | [The MIT License](http://www.opensource.org/licenses/mit-license.php) | com.github.zafarkhaja # java-semver # 0.9.0 | <notextile></notextile>
|
||||
MIT | [The MIT License](https://jsoup.org/license) | org.jsoup # jsoup # 1.10.2 | <notextile></notextile>
|
||||
MIT | [The MIT License](http://github.com/mockito/mockito/blob/master/LICENSE) | org.mockito # mockito-all # 1.10.19 | <notextile></notextile>
|
||||
MIT | [The MIT License](http://github.com/mockito/mockito/blob/master/LICENSE) | org.mockito # mockito-core # 2.7.22 | <notextile></notextile>
|
||||
MIT | [The MIT License](https://github.com/mockito/mockito/blob/master/LICENSE) | org.mockito # mockito-core # 2.23.4 | <notextile></notextile>
|
||||
MIT | [The MIT License (MIT)](https://opensource.org/licenses/MIT) | com.dimafeng # testcontainers-scala_2.12 # 0.22.0 | <notextile></notextile>
|
||||
MIT | [The MIT License (MIT)](http://www.opensource.org/licenses/mit-license.html) | net.coobird # thumbnailator # 0.4.8 | <notextile></notextile>
|
||||
Mozilla | [MPL 2.0 or EPL 1.0](http://h2database.com/html/license.html) | com.h2database # h2 # 1.4.195 | <notextile></notextile>
|
||||
Mozilla | [MPL 2.0 or EPL 1.0](http://h2database.com/html/license.html) | com.h2database # h2 # 1.4.197 | <notextile></notextile>
|
||||
Mozilla | [Mozilla Public License 1.1 (MPL 1.1)](http://www.mozilla.org/MPL/MPL-1.1.html) | com.googlecode.juniversalchardet # juniversalchardet # 1.0.3 | <notextile></notextile>
|
||||
Public Domain | [Public Domain](http://en.wikipedia.org/wiki/Public_domain) | net.i2p.crypto # eddsa # 0.1.0 | <notextile></notextile>
|
||||
unrecognized | [Bouncy Castle Licence](http://www.bouncycastle.org/licence.html) | org.bouncycastle # bcpkix-jdk15on # 1.56 | <notextile></notextile>
|
||||
unrecognized | [Bouncy Castle Licence](http://www.bouncycastle.org/licence.html) | org.bouncycastle # bcprov-jdk15on # 1.56 | <notextile></notextile>
|
||||
unrecognized | [Bouncy Castle Licence](http://www.bouncycastle.org/licence.html) | org.bouncycastle # bcpkix-jdk15on # 1.60 | <notextile></notextile>
|
||||
unrecognized | [Bouncy Castle Licence](http://www.bouncycastle.org/licence.html) | org.bouncycastle # bcprov-jdk15on # 1.60 | <notextile></notextile>
|
||||
unrecognized | [Eclipse Public License 1.0](http://www.eclipse.org/legal/epl-v10.html) | junit # junit # 4.12 | <notextile></notextile>
|
||||
unrecognized | [The OpenLDAP Public License](http://www.openldap.org/software/release/license.html) | com.novell.ldap # jldap # 2009-10-07 | <notextile></notextile>
|
||||
unrecognized | [none specified](none specified) | com.thoughtworks.paranamer # paranamer # 2.8 | <notextile></notextile>
|
||||
unrecognized | [none specified](none specified) | commons-codec # commons-codec # 1.10 | <notextile></notextile>
|
||||
unrecognized | [none specified](none specified) | commons-logging # commons-logging # 1.2 | <notextile></notextile>
|
||||
unrecognized | [none specified](none specified) | fr.brouillard.oss.security.xhub # xhub4j-core # 1.0.0 | <notextile></notextile>
|
||||
unrecognized | [none specified](none specified) | org.ow2.asm # asm # 5.0.4 | <notextile></notextile>
|
||||
unrecognized | [none specified](none specified) | tomcat # tomcat-apr # 5.5.23 | <notextile></notextile>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
Developer's Guide
|
||||
========
|
||||
* [How to run from source tree](how_to_run.md)
|
||||
* [Build from source tree](build.md)
|
||||
* [Debug on IntelliJ](debug.md)
|
||||
* [Directory Structure](directory.md)
|
||||
* [Mapping and Validation](validation.md)
|
||||
* [Authentication in Controller](authenticator.md)
|
||||
|
||||
@@ -34,6 +34,20 @@ object GitBucketCoreModule extends Module("gitbucket-core",
|
||||
Generate release files
|
||||
--------
|
||||
|
||||
### Deploy assembly jar file
|
||||
|
||||
For plug-in development, we have to publish the GitBucket jar file to the Maven central repository before release GitBucket itself.
|
||||
|
||||
First, hit following command to publish artifacts to the sonatype OSS repository:
|
||||
|
||||
```bash
|
||||
$ sbt publishSigned
|
||||
```
|
||||
|
||||
Then logged-in to https://oss.sonatype.org/, close and release the repository.
|
||||
|
||||
You need to wait up to a day until [gitbucket-notification-plugin](https://plugins.gitbucket-community.org/) which is default bundled plugin is built for new version of GitBucket.
|
||||
|
||||
### Make release war file
|
||||
|
||||
Run `sbt executable`. The release war file and fingerprint are generated into `target/executable/gitbucket.war`.
|
||||
@@ -42,20 +56,4 @@ Run `sbt executable`. The release war file and fingerprint are generated into `t
|
||||
$ sbt executable
|
||||
```
|
||||
|
||||
### Deploy assembly jar file
|
||||
|
||||
For plug-in development, we have to publish the GitBucket jar file to the Maven central repository as well. At first, hit following command to publish artifacts to the sonatype OSS repository:
|
||||
|
||||
```bash
|
||||
$ sbt publishSigned
|
||||
```
|
||||
|
||||
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.
|
||||
Create new release from the corresponded tag on GitHub, then upload generated jar file and fingerprints to the release.
|
||||
|
||||
BIN
doc/remote_debug.png
Normal file
BIN
doc/remote_debug.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 203 KiB |
@@ -1,11 +1,11 @@
|
||||
Mapping and Validation
|
||||
========
|
||||
GitBucket uses [scalatra-forms](https://github.com/takezoe/scalatra-forms) to validate request parameters and map them to the scala object. This is inspired by Play2 form mapping / validation.
|
||||
GitBucket uses [scalatra-forms](http://scalatra.org/guides/2.6/formats/forms.html) to validate request parameters and map them to the scala object. This is inspired by Play2 form mapping / validation.
|
||||
|
||||
At first, define the mapping as following:
|
||||
|
||||
```scala
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.scalatra.forms._
|
||||
|
||||
case class RegisterForm(name: String, description: String)
|
||||
|
||||
@@ -15,17 +15,17 @@ val form = mapping(
|
||||
)(RegisterForm.apply)
|
||||
```
|
||||
|
||||
The servlet have to mixed in ```jp.sf.amateras.scalatra.forms.ClientSideValidationFormSupport``` to validate request parameters and take mapped object. It validates request parameters before action. If any errors are detected, it throws an exception.
|
||||
The servlet have to mixed in `gitbucket.core.controller.ValidationFormSupport` to validate request parameters and take mapped object. It validates request parameters before action. If any errors are detected, it throws an exception.
|
||||
|
||||
```scala
|
||||
class RegisterServlet extends ScalatraServlet with ClientSideValidationFormSupport {
|
||||
class RegisterServlet extends ScalatraServlet with ValidationFormSupport {
|
||||
post("/register", form) { form: RegisterForm =>
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In the view template, you can add client-side validation by adding ```validate="true"``` to your form. Error messages are set to ```span#error-<fieldname>```.
|
||||
In the view template, you can add client-side validation by adding `validate="true"` to your form. Error messages are set to `span#error-<fieldname>`.
|
||||
|
||||
```html
|
||||
<form method="POST" action="/register" validate="true">
|
||||
@@ -39,9 +39,9 @@ In the view template, you can add client-side validation by adding ```validate="
|
||||
</form>
|
||||
```
|
||||
|
||||
Client-side validation calls ```<form-action>/validate``` to validate form contents. It returns a validation result as JSON. In this case, form action is ```/register```, so ```/register/validate``` is called before submitting a form. ```ClientSideValidationFormSupport``` adds this JSON API automatically.
|
||||
Client-side validation calls `<form-action>/validate` to validate form contents. It returns a validation result as JSON. In this case, form action is `/register`, so `/register/validate` is called before submitting a form. `ValidationFormSupport` adds this JSON API automatically.
|
||||
|
||||
For Ajax request, you have to use '''ajaxGet''' or '''ajaxPost''' to define action. It almost same as '''get''' or '''post'''. You can implement actions which handle Ajax request as same as normal actions.
|
||||
For Ajax request, you have to use `ajaxGet` or `ajaxPost` to define action. It almost same as '''get''' or '''post'''. You can implement actions which handle Ajax request as same as normal actions.
|
||||
Small difference is they return validation errors as JSON.
|
||||
|
||||
```scala
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
||||
|
||||
addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.0")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.3.15")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.9")
|
||||
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.3")
|
||||
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2")
|
||||
addSbtCoursier
|
||||
addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.0")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.3.15")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.9")
|
||||
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.3")
|
||||
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0")
|
||||
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.9.2")
|
||||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.1")
|
||||
|
||||
addSbtCoursier
|
||||
|
||||
@@ -206,7 +206,7 @@
|
||||
<column name="PULL_REQUEST" type="boolean" nullable="false"/>
|
||||
</createTable>
|
||||
|
||||
<addPrimaryKey constraintName="IDX_ISSUE_PK" tableName="ISSUE" columnNames="ISSUE_ID, USER_NAME, REPOSITORY_NAME"/>
|
||||
<addPrimaryKey constraintName="IDX_ISSUE_PK" tableName="ISSUE" columnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_ISSUE_FK0" baseTableName="ISSUE" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_ISSUE_FK2" baseTableName="ISSUE" baseColumnNames="MILESTONE_ID" referencedTableName="MILESTONE" referencedColumnNames="MILESTONE_ID"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_ISSUE_FK1" baseTableName="ISSUE" baseColumnNames="OPENED_USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||
@@ -348,4 +348,4 @@
|
||||
<addPrimaryKey constraintName="IDX_PROTECTED_BRANCH_REQUIRE_CONTEXT_PK" tableName="PROTECTED_BRANCH_REQUIRE_CONTEXT" columnNames="USER_NAME, REPOSITORY_NAME, BRANCH, CONTEXT"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_PROTECTED_BRANCH_REQUIRE_CONTEXT_FK0" baseTableName="PROTECTED_BRANCH_REQUIRE_CONTEXT" baseColumnNames="USER_NAME, REPOSITORY_NAME, BRANCH" referencedTableName="PROTECTED_BRANCH" referencedColumnNames="USER_NAME, REPOSITORY_NAME, BRANCH" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||
|
||||
</changeSet>
|
||||
</changeSet>
|
||||
|
||||
16
src/main/resources/update/gitbucket-core_4.31.xml
Normal file
16
src/main/resources/update/gitbucket-core_4.31.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<changeSet>
|
||||
<!--================================================================================================-->
|
||||
<!-- SSH_KEY -->
|
||||
<!--================================================================================================-->
|
||||
<createTable tableName="GPG_KEY">
|
||||
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="KEY_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||
<column name="GPG_KEY_ID" type="bigint" nullable="false"/>
|
||||
<column name="TITLE" type="varchar(100)" nullable="false"/>
|
||||
<column name="PUBLIC_KEY" type="text" nullable="false"/>
|
||||
</createTable>
|
||||
|
||||
<addPrimaryKey constraintName="IDX_GPG_KEY_PK" tableName="GPG_KEY" columnNames="USER_NAME, GPG_KEY_ID"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_GPG_KEY_FK0" baseTableName="GPG_KEY" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||
</changeSet>
|
||||
@@ -60,5 +60,6 @@ object GitBucketCoreModule
|
||||
new Version("4.28.0"),
|
||||
new Version("4.29.0"),
|
||||
new Version("4.30.0"),
|
||||
new Version("4.30.1")
|
||||
new Version("4.30.1"),
|
||||
new Version("4.31.0", new LiquibaseMigration("update/gitbucket-core_4.31.xml"))
|
||||
)
|
||||
|
||||
@@ -9,7 +9,7 @@ import gitbucket.core.util.RepositoryName
|
||||
case class ApiBranch(name: String, commit: ApiBranchCommit, protection: ApiBranchProtection)(
|
||||
repositoryName: RepositoryName
|
||||
) extends FieldSerializable {
|
||||
def _links =
|
||||
val _links =
|
||||
Map(
|
||||
"self" -> ApiPath(s"/api/v3/repos/${repositoryName.fullName}/branches/${name}"),
|
||||
"html" -> ApiPath(s"/${repositoryName.fullName}/tree/${name}")
|
||||
|
||||
@@ -21,22 +21,14 @@ case class ApiCommit(
|
||||
modified: List[String],
|
||||
author: ApiPersonIdent,
|
||||
committer: ApiPersonIdent
|
||||
)(repositoryName: RepositoryName, urlIsHtmlUrl: Boolean)
|
||||
)(repositoryName: RepositoryName)
|
||||
extends FieldSerializable {
|
||||
val url = if (urlIsHtmlUrl) {
|
||||
ApiPath(s"/${repositoryName.fullName}/commit/${id}")
|
||||
} else {
|
||||
ApiPath(s"/api/v3/${repositoryName.fullName}/commits/${id}")
|
||||
}
|
||||
val html_url = if (urlIsHtmlUrl) {
|
||||
None
|
||||
} else {
|
||||
Some(ApiPath(s"/${repositoryName.fullName}/commit/${id}"))
|
||||
}
|
||||
val url = ApiPath(s"/api/v3/${repositoryName.fullName}/commits/${id}")
|
||||
val html_url = ApiPath(s"/${repositoryName.fullName}/commit/${id}")
|
||||
}
|
||||
|
||||
object ApiCommit {
|
||||
def apply(git: Git, repositoryName: RepositoryName, commit: CommitInfo, urlIsHtmlUrl: Boolean = false): ApiCommit = {
|
||||
def apply(git: Git, repositoryName: RepositoryName, commit: CommitInfo): ApiCommit = {
|
||||
val diffs = JGitUtil.getDiffs(git, None, commit.id, false, false)
|
||||
ApiCommit(
|
||||
id = commit.id,
|
||||
@@ -53,8 +45,6 @@ object ApiCommit {
|
||||
},
|
||||
author = ApiPersonIdent.author(commit),
|
||||
committer = ApiPersonIdent.committer(commit)
|
||||
)(repositoryName, urlIsHtmlUrl)
|
||||
)(repositoryName)
|
||||
}
|
||||
def forWebhookPayload(git: Git, repositoryName: RepositoryName, commit: CommitInfo): ApiCommit =
|
||||
apply(git, repositoryName, commit, true)
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ object ApiCommits {
|
||||
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${commitInfo.id}"),
|
||||
sha = commitInfo.id,
|
||||
html_url = ApiPath(s"${repositoryName.fullName}/commit/${commitInfo.id}"),
|
||||
comment_url = ApiPath(""),
|
||||
comment_url = ApiPath(""), // TODO no API for commit comment
|
||||
commit = Commit(
|
||||
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${commitInfo.id}"),
|
||||
author = ApiPersonIdent.author(commitInfo),
|
||||
|
||||
42
src/main/scala/gitbucket/core/api/ApiRelease.scala
Normal file
42
src/main/scala/gitbucket/core/api/ApiRelease.scala
Normal file
@@ -0,0 +1,42 @@
|
||||
package gitbucket.core.api
|
||||
import gitbucket.core.model.{Account, ReleaseAsset, ReleaseTag}
|
||||
import gitbucket.core.util.RepositoryName
|
||||
|
||||
case class ApiReleaseAsset(name: String, size: Long)(tag: String, fileName: String, repositoryName: RepositoryName) {
|
||||
val label = name
|
||||
val file_id = fileName
|
||||
val browser_download_url = ApiPath(
|
||||
s"/api/v3/repos/${repositoryName.fullName}/releases/${tag}/assets/${fileName}"
|
||||
)
|
||||
}
|
||||
|
||||
object ApiReleaseAsset {
|
||||
def apply(asset: ReleaseAsset, repositoryName: RepositoryName): ApiReleaseAsset =
|
||||
ApiReleaseAsset(asset.label, asset.size)(asset.tag, asset.fileName, repositoryName)
|
||||
}
|
||||
|
||||
case class ApiRelease(
|
||||
name: String,
|
||||
tag_name: String,
|
||||
body: Option[String],
|
||||
author: ApiUser,
|
||||
assets: Seq[ApiReleaseAsset]
|
||||
)
|
||||
|
||||
object ApiRelease {
|
||||
def apply(
|
||||
release: ReleaseTag,
|
||||
assets: Seq[ReleaseAsset],
|
||||
author: Account,
|
||||
repositoryName: RepositoryName
|
||||
): ApiRelease =
|
||||
ApiRelease(
|
||||
release.name,
|
||||
release.tag,
|
||||
release.content,
|
||||
ApiUser(author),
|
||||
assets.map { asset =>
|
||||
ApiReleaseAsset(asset, repositoryName)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -13,15 +13,11 @@ case class ApiRepository(
|
||||
`private`: Boolean,
|
||||
default_branch: String,
|
||||
owner: ApiUser
|
||||
)(urlIsHtmlUrl: Boolean) {
|
||||
) {
|
||||
val id = 0 // dummy id
|
||||
val forks_count = forks
|
||||
val watchers_count = watchers
|
||||
val url = if (urlIsHtmlUrl) {
|
||||
ApiPath(s"/${full_name}")
|
||||
} else {
|
||||
ApiPath(s"/api/v3/repos/${full_name}")
|
||||
}
|
||||
val url = ApiPath(s"/api/v3/repos/${full_name}")
|
||||
val http_url = ApiPath(s"/git/${full_name}.git")
|
||||
val clone_url = ApiPath(s"/git/${full_name}.git")
|
||||
val html_url = ApiPath(s"/${full_name}")
|
||||
@@ -33,8 +29,7 @@ object ApiRepository {
|
||||
repository: Repository,
|
||||
owner: ApiUser,
|
||||
forkedCount: Int = 0,
|
||||
watchers: Int = 0,
|
||||
urlIsHtmlUrl: Boolean = false
|
||||
watchers: Int = 0
|
||||
): ApiRepository =
|
||||
ApiRepository(
|
||||
name = repository.repositoryName,
|
||||
@@ -45,16 +40,13 @@ object ApiRepository {
|
||||
`private` = repository.isPrivate,
|
||||
default_branch = repository.defaultBranch,
|
||||
owner = owner
|
||||
)(urlIsHtmlUrl)
|
||||
)
|
||||
|
||||
def apply(repositoryInfo: RepositoryInfo, owner: ApiUser): ApiRepository =
|
||||
ApiRepository(repositoryInfo.repository, owner, forkedCount = repositoryInfo.forkedCount)
|
||||
|
||||
def apply(repositoryInfo: RepositoryInfo, owner: Account): ApiRepository =
|
||||
this(repositoryInfo.repository, ApiUser(owner))
|
||||
|
||||
def forWebhookPayload(repositoryInfo: RepositoryInfo, owner: ApiUser): ApiRepository =
|
||||
ApiRepository(repositoryInfo.repository, owner, forkedCount = repositoryInfo.forkedCount, urlIsHtmlUrl = true)
|
||||
this(repositoryInfo, ApiUser(owner))
|
||||
|
||||
def forDummyPayload(owner: ApiUser): ApiRepository =
|
||||
ApiRepository(
|
||||
@@ -66,5 +58,5 @@ object ApiRepository {
|
||||
`private` = false,
|
||||
default_branch = "master",
|
||||
owner = owner
|
||||
)(true)
|
||||
)
|
||||
}
|
||||
|
||||
13
src/main/scala/gitbucket/core/api/CreateAFile.scala
Normal file
13
src/main/scala/gitbucket/core/api/CreateAFile.scala
Normal file
@@ -0,0 +1,13 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/contents/#create-a-file
|
||||
*/
|
||||
case class CreateAFile(
|
||||
message: String,
|
||||
content: String,
|
||||
sha: Option[String],
|
||||
branch: Option[String],
|
||||
committer: Option[ApiPusher],
|
||||
author: Option[ApiPusher]
|
||||
)
|
||||
8
src/main/scala/gitbucket/core/api/CreateAGroup.scala
Normal file
8
src/main/scala/gitbucket/core/api/CreateAGroup.scala
Normal file
@@ -0,0 +1,8 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
case class CreateAGroup(
|
||||
login: String,
|
||||
admin: String,
|
||||
profile_name: Option[String],
|
||||
url: Option[String]
|
||||
)
|
||||
10
src/main/scala/gitbucket/core/api/CreateARelease.scala
Normal file
10
src/main/scala/gitbucket/core/api/CreateARelease.scala
Normal file
@@ -0,0 +1,10 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
case class CreateARelease(
|
||||
tag_name: String,
|
||||
target_commitish: Option[String],
|
||||
name: Option[String],
|
||||
body: Option[String],
|
||||
draft: Option[Boolean],
|
||||
prerelease: Option[Boolean]
|
||||
)
|
||||
@@ -43,6 +43,8 @@ object JsonFormat {
|
||||
FieldSerializer[ApiCommits.Tree]() +
|
||||
FieldSerializer[ApiCommits.Stats]() +
|
||||
FieldSerializer[ApiCommits.File]() +
|
||||
FieldSerializer[ApiRelease]() +
|
||||
FieldSerializer[ApiReleaseAsset]() +
|
||||
ApiBranchProtection.enforcementLevelSerializer
|
||||
|
||||
def apiPathSerializer(c: Context) =
|
||||
|
||||
@@ -26,6 +26,7 @@ class AccountController
|
||||
with WikiService
|
||||
with LabelsService
|
||||
with SshKeyService
|
||||
with GpgKeyService
|
||||
with OneselfAuthenticator
|
||||
with UsersAuthenticator
|
||||
with GroupManagerAuthenticator
|
||||
@@ -42,6 +43,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
with WikiService
|
||||
with LabelsService
|
||||
with SshKeyService
|
||||
with GpgKeyService
|
||||
with OneselfAuthenticator
|
||||
with UsersAuthenticator
|
||||
with GroupManagerAuthenticator
|
||||
@@ -75,6 +77,8 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
case class SshKeyForm(title: String, publicKey: String)
|
||||
|
||||
case class GpgKeyForm(title: String, publicKey: String)
|
||||
|
||||
case class PersonalTokenForm(note: String)
|
||||
|
||||
val newForm = mapping(
|
||||
@@ -108,6 +112,11 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
"publicKey" -> trim2(label("Key", text(required, validPublicKey)))
|
||||
)(SshKeyForm.apply)
|
||||
|
||||
val gpgKeyForm = mapping(
|
||||
"title" -> trim(label("Title", text(required, maxlength(100)))),
|
||||
"publicKey" -> label("Key", text(required, validGpgPublicKey))
|
||||
)(GpgKeyForm.apply)
|
||||
|
||||
val personalTokenForm = mapping(
|
||||
"note" -> trim(label("Token", text(required, maxlength(100))))
|
||||
)(PersonalTokenForm.apply)
|
||||
@@ -387,6 +396,27 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
redirect(s"/${userName}/_ssh")
|
||||
})
|
||||
|
||||
get("/:userName/_gpg")(oneselfOnly {
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).map { x =>
|
||||
//html.ssh(x, getPublicKeys(x.userName))
|
||||
html.gpg(x, getGpgPublicKeys(x.userName))
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
post("/:userName/_gpg", gpgKeyForm)(oneselfOnly { form =>
|
||||
val userName = params("userName")
|
||||
addGpgPublicKey(userName, form.title, form.publicKey)
|
||||
redirect(s"/${userName}/_gpg")
|
||||
})
|
||||
|
||||
get("/:userName/_gpg/delete/:id")(oneselfOnly {
|
||||
val userName = params("userName")
|
||||
val keyId = params("id").toInt
|
||||
deleteGpgPublicKey(userName, keyId)
|
||||
redirect(s"/${userName}/_gpg")
|
||||
})
|
||||
|
||||
get("/:userName/_application")(oneselfOnly {
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).map { x =>
|
||||
@@ -771,6 +801,20 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
private def validGpgPublicKey: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
GpgUtil.str2GpgKeyId(value) match {
|
||||
case Some(s) if GpgUtil.getGpgKey(s).isEmpty =>
|
||||
None
|
||||
case Some(_) =>
|
||||
Some("GPG key is duplicated.")
|
||||
case None =>
|
||||
Some("GPG key is invalid.")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private def validAccountName: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
getAccountByUserName(value) match {
|
||||
|
||||
@@ -15,6 +15,7 @@ class ApiController
|
||||
with ApiIssueLabelControllerBase
|
||||
with ApiOrganizationControllerBase
|
||||
with ApiPullRequestControllerBase
|
||||
with ApiReleaseControllerBase
|
||||
with ApiRepositoryBranchControllerBase
|
||||
with ApiRepositoryCollaboratorControllerBase
|
||||
with ApiRepositoryCommitControllerBase
|
||||
@@ -31,7 +32,9 @@ class ApiController
|
||||
with PullRequestService
|
||||
with CommitsService
|
||||
with CommitStatusService
|
||||
with ReleaseService
|
||||
with RepositoryCreationService
|
||||
with RepositoryCommitFileService
|
||||
with IssueCreationService
|
||||
with HandleCommentService
|
||||
with MergeService
|
||||
|
||||
@@ -9,6 +9,7 @@ import gitbucket.core.service.IssuesService._
|
||||
class DashboardController
|
||||
extends DashboardControllerBase
|
||||
with IssuesService
|
||||
with MergeService
|
||||
with PullRequestService
|
||||
with RepositoryService
|
||||
with AccountService
|
||||
|
||||
@@ -10,6 +10,7 @@ import gitbucket.core.service._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.view.helpers._
|
||||
import org.scalatra.Ok
|
||||
import org.scalatra.forms._
|
||||
|
||||
@@ -206,7 +207,8 @@ trait IndexControllerBase extends ControllerBase {
|
||||
}
|
||||
.map { t =>
|
||||
Map(
|
||||
"label" -> s"<b>@${StringUtil.escapeHtml(t.userName)}</b> ${StringUtil.escapeHtml(t.fullName)}",
|
||||
"label" -> s"${avatar(t.userName, 16)}<b>@${StringUtil.escapeHtml(t.userName)}</b> ${StringUtil
|
||||
.escapeHtml(t.fullName)}",
|
||||
"value" -> t.userName
|
||||
)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ class IssuesController
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
with MergeService
|
||||
with PullRequestService
|
||||
with WebHookIssueCommentService
|
||||
with WebHookPullRequestReviewCommentService
|
||||
|
||||
@@ -534,15 +534,15 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
)
|
||||
|
||||
createPullRequest(
|
||||
originUserName = repository.owner,
|
||||
originRepositoryName = repository.name,
|
||||
originRepository = repository,
|
||||
issueId = issueId,
|
||||
originBranch = form.targetBranch,
|
||||
requestUserName = form.requestUserName,
|
||||
requestRepositoryName = form.requestRepositoryName,
|
||||
requestBranch = form.requestBranch,
|
||||
commitIdFrom = form.commitIdFrom,
|
||||
commitIdTo = form.commitIdTo
|
||||
commitIdTo = form.commitIdTo,
|
||||
loginAccount = context.loginAccount.get
|
||||
)
|
||||
|
||||
// insert labels
|
||||
@@ -557,29 +557,6 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
// fetch requested branch
|
||||
fetchAsPullRequest(owner, name, form.requestUserName, form.requestRepositoryName, form.requestBranch, issueId)
|
||||
|
||||
// record activity
|
||||
recordPullRequestActivity(owner, name, loginUserName, issueId, form.title)
|
||||
|
||||
// call web hook
|
||||
callPullRequestWebHook("opened", repository, issueId, context.loginAccount.get)
|
||||
|
||||
getIssue(owner, name, issueId.toString) foreach { issue =>
|
||||
// extract references and create refer comment
|
||||
createReferComment(
|
||||
owner,
|
||||
name,
|
||||
issue,
|
||||
form.title + " " + form.content.getOrElse(""),
|
||||
context.loginAccount.get
|
||||
)
|
||||
|
||||
// call hooks
|
||||
PluginRegistry().getPullRequestHooks.foreach(_.created(issue, repository))
|
||||
}
|
||||
|
||||
redirect(s"/${owner}/${name}/pull/${issueId}")
|
||||
}
|
||||
})
|
||||
@@ -589,23 +566,26 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
val mailAddresses =
|
||||
context.loginAccount.map(x => Seq(x.mailAddress) ++ getAccountExtraMailAddresses(x.userName)).getOrElse(Nil)
|
||||
|
||||
val branches = JGitUtil
|
||||
.getBranches(
|
||||
owner = repository.owner,
|
||||
name = repository.name,
|
||||
defaultBranch = repository.repository.defaultBranch,
|
||||
origin = repository.repository.originUserName.isEmpty
|
||||
)
|
||||
.filter { x =>
|
||||
x.mergeInfo.map(_.ahead).getOrElse(0) > 0 && x.mergeInfo.map(_.behind).getOrElse(0) == 0 &&
|
||||
x.commitTime.getTime > thresholdTime &&
|
||||
mailAddresses.contains(x.committerEmailAddress)
|
||||
val branches =
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
git =>
|
||||
JGitUtil
|
||||
.getBranches(
|
||||
git = git,
|
||||
defaultBranch = repository.repository.defaultBranch,
|
||||
origin = repository.repository.originUserName.isEmpty
|
||||
)
|
||||
.filter { x =>
|
||||
x.mergeInfo.map(_.ahead).getOrElse(0) > 0 && x.mergeInfo.map(_.behind).getOrElse(0) == 0 &&
|
||||
x.commitTime.getTime > thresholdTime &&
|
||||
mailAddresses.contains(x.committerEmailAddress)
|
||||
}
|
||||
.sortBy { br =>
|
||||
(br.mergeInfo.isEmpty, br.commitTime)
|
||||
}
|
||||
.map(_.name)
|
||||
.reverse
|
||||
}
|
||||
.sortBy { br =>
|
||||
(br.mergeInfo.isEmpty, br.commitTime)
|
||||
}
|
||||
.map(_.name)
|
||||
.reverse
|
||||
|
||||
val targetRepository = (for {
|
||||
parentUserName <- repository.repository.parentUserName
|
||||
|
||||
@@ -121,7 +121,7 @@ trait ReleaseControllerBase extends ControllerBase {
|
||||
createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount)
|
||||
}
|
||||
|
||||
recordReleaseActivity(repository.owner, repository.name, loginAccount.userName, form.name)
|
||||
recordReleaseActivity(repository.owner, repository.name, loginAccount.userName, form.name, tagName)
|
||||
|
||||
redirect(s"/${repository.owner}/${repository.name}/releases/${tagName}")
|
||||
})
|
||||
|
||||
@@ -14,6 +14,7 @@ import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.model.{Account, CommitState, CommitStatus}
|
||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||
import gitbucket.core.view
|
||||
import gitbucket.core.view.helpers
|
||||
import org.apache.commons.compress.archivers.{ArchiveEntry, ArchiveOutputStream}
|
||||
@@ -29,8 +30,10 @@ import org.eclipse.jgit.api.{ArchiveCommand, Git}
|
||||
import org.eclipse.jgit.archive.{TgzFormat, ZipFormat}
|
||||
import org.eclipse.jgit.errors.MissingObjectException
|
||||
import org.eclipse.jgit.lib._
|
||||
import org.eclipse.jgit.treewalk.TreeWalk
|
||||
import org.eclipse.jgit.treewalk.{TreeWalk, WorkingTreeOptions}
|
||||
import org.eclipse.jgit.treewalk.TreeWalk.OperationType
|
||||
import org.eclipse.jgit.treewalk.filter.PathFilter
|
||||
import org.eclipse.jgit.util.io.EolStreamTypeUtil
|
||||
import org.json4s.jackson.Serialization
|
||||
import org.scalatra._
|
||||
import org.scalatra.i18n.Messages
|
||||
@@ -50,6 +53,7 @@ class RepositoryViewerController
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
with MergeService
|
||||
with PullRequestService
|
||||
with CommitStatusService
|
||||
with WebHookPullRequestService
|
||||
@@ -270,9 +274,30 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
if (path.isEmpty) Nil else path.split("/").toList,
|
||||
branchName,
|
||||
repository,
|
||||
logs.splitWith { (commit1, commit2) =>
|
||||
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
||||
},
|
||||
logs
|
||||
.map {
|
||||
c =>
|
||||
CommitInfo(
|
||||
id = c.id,
|
||||
shortMessage = c.shortMessage,
|
||||
fullMessage = c.fullMessage,
|
||||
parents = c.parents,
|
||||
authorTime = c.authorTime,
|
||||
authorName = c.authorName,
|
||||
authorEmailAddress = c.authorEmailAddress,
|
||||
commitTime = c.commitTime,
|
||||
committerName = c.committerName,
|
||||
committerEmailAddress = c.committerEmailAddress,
|
||||
commitSign = c.commitSign,
|
||||
verified = c.commitSign
|
||||
.flatMap { s =>
|
||||
GpgUtil.verifySign(s)
|
||||
}
|
||||
)
|
||||
}
|
||||
.splitWith { (commit1, commit2) =>
|
||||
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
||||
},
|
||||
page,
|
||||
hasNext,
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount),
|
||||
@@ -723,29 +748,31 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
*/
|
||||
get("/:owner/:repository/branches")(referrersOnly { repository =>
|
||||
val protectedBranches = getProtectedBranchList(repository.owner, repository.name).toSet
|
||||
val branches = JGitUtil
|
||||
.getBranches(
|
||||
owner = repository.owner,
|
||||
name = repository.name,
|
||||
defaultBranch = repository.repository.defaultBranch,
|
||||
origin = repository.repository.originUserName.isEmpty
|
||||
)
|
||||
.sortBy(br => (br.mergeInfo.isEmpty, br.commitTime))
|
||||
.map(
|
||||
br =>
|
||||
(
|
||||
br,
|
||||
getPullRequestByRequestCommit(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
repository.repository.defaultBranch,
|
||||
br.name,
|
||||
br.commitId
|
||||
),
|
||||
protectedBranches.contains(br.name)
|
||||
)
|
||||
)
|
||||
.reverse
|
||||
val branches = using(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
git =>
|
||||
JGitUtil
|
||||
.getBranches(
|
||||
git = git,
|
||||
defaultBranch = repository.repository.defaultBranch,
|
||||
origin = repository.repository.originUserName.isEmpty
|
||||
)
|
||||
.sortBy(br => (br.mergeInfo.isEmpty, br.commitTime))
|
||||
.map(
|
||||
br =>
|
||||
(
|
||||
br,
|
||||
getPullRequestByRequestCommit(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
repository.repository.defaultBranch,
|
||||
br.name,
|
||||
br.commitId
|
||||
),
|
||||
protectedBranches.contains(br.name)
|
||||
)
|
||||
)
|
||||
.reverse
|
||||
}
|
||||
|
||||
html.branches(branches, hasDeveloperRole(repository.owner, repository.name, context.loginAccount), repository)
|
||||
})
|
||||
@@ -920,7 +947,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
repository,
|
||||
if (path == ".") Nil else path.split("/").toList, // current path
|
||||
new JGitUtil.CommitInfo(lastModifiedCommit), // last modified commit
|
||||
JGitUtil.getCommitCount(repository.owner, repository.name, lastModifiedCommit.getName),
|
||||
JGitUtil.getCommitCount(git, lastModifiedCommit.getName),
|
||||
files,
|
||||
readme,
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount),
|
||||
@@ -967,12 +994,23 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
val entryPath =
|
||||
if (path.isEmpty) baseName + "/" + treeWalk.getPathString
|
||||
else path.split("/").last + treeWalk.getPathString.substring(path.length)
|
||||
val size = JGitUtil.getFileSize(git, repository, treeWalk)
|
||||
val size = JGitUtil.getContentSize(git.getRepository.open(treeWalk.getObjectId(0)))
|
||||
val mode = treeWalk.getFileMode.getBits
|
||||
val entry: ArchiveEntry = entryCreator(entryPath, size, date, mode)
|
||||
JGitUtil.openFile(git, repository, commit.getTree, treeWalk.getPathString) { in =>
|
||||
archive.putArchiveEntry(entry)
|
||||
IOUtils.copy(in, archive)
|
||||
IOUtils.copy(
|
||||
EolStreamTypeUtil.wrapInputStream(
|
||||
in,
|
||||
EolStreamTypeUtil
|
||||
.detectStreamType(
|
||||
OperationType.CHECKOUT_OP,
|
||||
git.getRepository.getConfig.get(WorkingTreeOptions.KEY),
|
||||
treeWalk.getAttributes
|
||||
)
|
||||
),
|
||||
archive
|
||||
)
|
||||
archive.closeArchiveEntry()
|
||||
}
|
||||
}
|
||||
@@ -997,7 +1035,6 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
using(new ZipArchiveOutputStream(response.getOutputStream)) { zip =>
|
||||
archive(revision, ".zip", zip) { (path, size, date, mode) =>
|
||||
val entry = new ZipArchiveEntry(path)
|
||||
entry.setSize(size)
|
||||
entry.setUnixMode(mode)
|
||||
entry.setTime(date.getTime)
|
||||
entry
|
||||
@@ -1022,7 +1059,6 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
tar.setAddPaxHeadersForNonAsciiNames(true)
|
||||
archive(revision, ".tar.gz", tar) { (path, size, date, mode) =>
|
||||
val entry = new TarArchiveEntry(path)
|
||||
entry.setSize(size)
|
||||
entry.setModTime(date)
|
||||
entry.setMode(mode)
|
||||
entry
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package gitbucket.core.controller.api
|
||||
import gitbucket.core.api.{ApiGroup, ApiRepository, ApiUser, JsonFormat}
|
||||
import gitbucket.core.api.{ApiGroup, CreateAGroup, ApiRepository, ApiUser, JsonFormat}
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.service.{AccountService, RepositoryService}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.UsersAuthenticator
|
||||
import gitbucket.core.util.{AdminAuthenticator, UsersAuthenticator}
|
||||
|
||||
trait ApiOrganizationControllerBase extends ControllerBase {
|
||||
self: RepositoryService with AccountService with UsersAuthenticator =>
|
||||
self: RepositoryService with AccountService with AdminAuthenticator with UsersAuthenticator =>
|
||||
|
||||
/*
|
||||
* i. List your organizations
|
||||
@@ -51,6 +51,19 @@ trait ApiOrganizationControllerBase extends ControllerBase {
|
||||
* ghe: i. Create an organization
|
||||
* https://developer.github.com/enterprise/2.14/v3/enterprise-admin/orgs/#create-an-organization
|
||||
*/
|
||||
post("/api/v3/admin/organizations")(adminOnly {
|
||||
for {
|
||||
data <- extractFromJsonBody[CreateAGroup]
|
||||
} yield {
|
||||
val group = createGroup(
|
||||
data.login,
|
||||
data.profile_name,
|
||||
data.url
|
||||
)
|
||||
updateGroupMembers(data.login, List(data.admin -> true))
|
||||
JsonFormat(ApiGroup(group))
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
* ghe: ii. Rename an organization
|
||||
|
||||
@@ -106,15 +106,15 @@ trait ApiPullRequestControllerBase extends ControllerBase {
|
||||
)
|
||||
|
||||
createPullRequest(
|
||||
originUserName = repository.owner,
|
||||
originRepositoryName = repository.name,
|
||||
originRepository = repository,
|
||||
issueId = issueId,
|
||||
originBranch = createPullReq.base,
|
||||
requestUserName = reqOwner,
|
||||
requestRepositoryName = repository.name,
|
||||
requestBranch = reqBranch,
|
||||
commitIdFrom = commitIdFrom.getName,
|
||||
commitIdTo = commitIdTo.getName
|
||||
commitIdTo = commitIdTo.getName,
|
||||
loginAccount = context.loginAccount.get
|
||||
)
|
||||
getApiPullRequest(repository, issueId).map(JsonFormat(_))
|
||||
case _ =>
|
||||
@@ -133,15 +133,15 @@ trait ApiPullRequestControllerBase extends ControllerBase {
|
||||
case (Some(commitIdFrom), Some(commitIdTo)) =>
|
||||
changeIssueToPullRequest(repository.owner, repository.name, createPullReqAlt.issue)
|
||||
createPullRequest(
|
||||
originUserName = repository.owner,
|
||||
originRepositoryName = repository.name,
|
||||
originRepository = repository,
|
||||
issueId = createPullReqAlt.issue,
|
||||
originBranch = createPullReqAlt.base,
|
||||
requestUserName = reqOwner,
|
||||
requestRepositoryName = repository.name,
|
||||
requestBranch = reqBranch,
|
||||
commitIdFrom = commitIdFrom.getName,
|
||||
commitIdTo = commitIdTo.getName
|
||||
commitIdTo = commitIdTo.getName,
|
||||
loginAccount = context.loginAccount.get
|
||||
)
|
||||
getApiPullRequest(repository, createPullReqAlt.issue).map(JsonFormat(_))
|
||||
case _ =>
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
package gitbucket.core.controller.api
|
||||
import java.io.{ByteArrayInputStream, File}
|
||||
|
||||
import gitbucket.core.api._
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.service.{AccountService, ReleaseService}
|
||||
import gitbucket.core.util.Directory.getReleaseFilesDir
|
||||
import gitbucket.core.util.{FileUtil, ReferrerAuthenticator, RepositoryName, WritableUsersAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.SyntaxSugars.defining
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.scalatra.{Created, NoContent}
|
||||
|
||||
trait ApiReleaseControllerBase extends ControllerBase {
|
||||
self: AccountService with ReleaseService with ReferrerAuthenticator with WritableUsersAuthenticator =>
|
||||
|
||||
/**
|
||||
* i. List releases for a repository
|
||||
* https://developer.github.com/v3/repos/releases/#list-releases-for-a-repository
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/releases")(referrersOnly { repository =>
|
||||
val releases = getReleases(repository.owner, repository.name)
|
||||
JsonFormat(releases.map { rel =>
|
||||
val assets = getReleaseAssets(repository.owner, repository.name, rel.tag)
|
||||
ApiRelease(rel, assets, getAccountByUserName(rel.author).get, RepositoryName(repository))
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* ii. Get a single release
|
||||
* https://developer.github.com/v3/repos/releases/#get-a-single-release
|
||||
* GitBucket doesn't have release id
|
||||
*/
|
||||
/**
|
||||
* iii. Get the latest release
|
||||
* https://developer.github.com/v3/repos/releases/#get-the-latest-release
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/releases/latest")(referrersOnly { repository =>
|
||||
getReleases(repository.owner, repository.name).lastOption
|
||||
.map { release =>
|
||||
val assets = getReleaseAssets(repository.owner, repository.name, release.tag)
|
||||
JsonFormat(ApiRelease(release, assets, getAccountByUserName(release.author).get, RepositoryName(repository)))
|
||||
}
|
||||
.getOrElse {
|
||||
NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* iv. Get a release by tag name
|
||||
* https://developer.github.com/v3/repos/releases/#get-a-release-by-tag-name
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/releases/tags/:tag")(referrersOnly { repository =>
|
||||
val tag = params("tag")
|
||||
getRelease(repository.owner, repository.name, tag)
|
||||
.map { release =>
|
||||
val assets = getReleaseAssets(repository.owner, repository.name, tag)
|
||||
JsonFormat(ApiRelease(release, assets, getAccountByUserName(release.author).get, RepositoryName(repository)))
|
||||
}
|
||||
.getOrElse {
|
||||
NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* v. Create a release
|
||||
* https://developer.github.com/v3/repos/releases/#create-a-release
|
||||
*/
|
||||
post("/api/v3/repos/:owner/:repository/releases")(writableUsersOnly { repository =>
|
||||
(for {
|
||||
data <- extractFromJsonBody[CreateARelease]
|
||||
} yield {
|
||||
createRelease(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
data.name.getOrElse(data.tag_name),
|
||||
data.body,
|
||||
data.tag_name,
|
||||
context.loginAccount.get
|
||||
)
|
||||
val release = getRelease(repository.owner, repository.name, data.tag_name).get
|
||||
val assets = getReleaseAssets(repository.owner, repository.name, data.tag_name)
|
||||
JsonFormat(ApiRelease(release, assets, context.loginAccount.get, RepositoryName(repository)))
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* vi. Edit a release
|
||||
* https://developer.github.com/v3/repos/releases/#edit-a-release
|
||||
* Incompatiblity info: GitHub API requires :release_id, but GitBucket API requires :tag_name
|
||||
*/
|
||||
patch("/api/v3/repos/:owner/:repository/releases/:tag")(writableUsersOnly { repository =>
|
||||
(for {
|
||||
data <- extractFromJsonBody[CreateARelease]
|
||||
} yield {
|
||||
val tag = params("tag")
|
||||
updateRelease(repository.owner, repository.name, tag, data.name.getOrElse(data.tag_name), data.body)
|
||||
val release = getRelease(repository.owner, repository.name, data.tag_name).get
|
||||
val assets = getReleaseAssets(repository.owner, repository.name, data.tag_name)
|
||||
JsonFormat(ApiRelease(release, assets, context.loginAccount.get, RepositoryName(repository)))
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* vii. Delete a release
|
||||
* https://developer.github.com/v3/repos/releases/#delete-a-release
|
||||
* Incompatiblity info: GitHub API requires :release_id, but GitBucket API requires :tag_name
|
||||
*/
|
||||
delete("/api/v3/repos/:owner/:repository/releases/:tag")(writableUsersOnly { repository =>
|
||||
val tag = params("tag")
|
||||
deleteRelease(repository.owner, repository.name, tag)
|
||||
NoContent()
|
||||
})
|
||||
|
||||
/**
|
||||
* viii. List assets for a release
|
||||
* https://developer.github.com/v3/repos/releases/#list-assets-for-a-release
|
||||
*/
|
||||
/**
|
||||
* ix. Upload a release asset
|
||||
* https://developer.github.com/v3/repos/releases/#upload-a-release-asset
|
||||
*/
|
||||
post("/api/v3/repos/:owner/:repository/releases/:tag/assets")(writableUsersOnly { repository =>
|
||||
val name = params("name")
|
||||
val tag = params("tag")
|
||||
getRelease(repository.owner, repository.name, tag)
|
||||
.map {
|
||||
release =>
|
||||
defining(FileUtil.generateFileId) { fileId =>
|
||||
val buf = new Array[Byte](request.inputStream.available())
|
||||
request.inputStream.read(buf)
|
||||
FileUtils.writeByteArrayToFile(
|
||||
new File(
|
||||
getReleaseFilesDir(repository.owner, repository.name),
|
||||
FileUtil.checkFilename(tag + "/" + fileId)
|
||||
),
|
||||
buf
|
||||
)
|
||||
createReleaseAsset(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
tag,
|
||||
fileId,
|
||||
name,
|
||||
request.contentLength.getOrElse(0),
|
||||
context.loginAccount.get
|
||||
)
|
||||
getReleaseAsset(repository.owner, repository.name, tag, fileId)
|
||||
.map { asset =>
|
||||
JsonFormat(ApiReleaseAsset(asset, RepositoryName(repository)))
|
||||
}
|
||||
.getOrElse {
|
||||
ApiError("Unknown error")
|
||||
}
|
||||
}
|
||||
}
|
||||
.getOrElse(NotFound())
|
||||
})
|
||||
|
||||
/**
|
||||
* x. Get a single release asset
|
||||
* https://developer.github.com/v3/repos/releases/#get-a-single-release-asset
|
||||
* Incompatibility info: GitHub requires only asset_id, but GitBucket requires tag and fileId(file_id).
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/releases/:tag/assets/:fileId")(referrersOnly { repository =>
|
||||
val tag = params("tag")
|
||||
val fileId = params("fileId")
|
||||
getReleaseAsset(repository.owner, repository.name, tag, fileId)
|
||||
.map { asset =>
|
||||
JsonFormat(ApiReleaseAsset(asset, RepositoryName(repository)))
|
||||
}
|
||||
.getOrElse(NotFound())
|
||||
})
|
||||
|
||||
/*
|
||||
* xi. Edit a release asset
|
||||
* https://developer.github.com/v3/repos/releases/#edit-a-release-asset
|
||||
*/
|
||||
|
||||
/*
|
||||
* xii. Delete a release asset
|
||||
* https://developer.github.com/v3/repos/releases/#edit-a-release-asset
|
||||
*/
|
||||
}
|
||||
@@ -3,8 +3,11 @@ import gitbucket.core.api._
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.service.{AccountService, ProtectedBranchService, RepositoryService}
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.JGitUtil.getBranches
|
||||
import org.eclipse.jgit.api.Git
|
||||
|
||||
trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
self: RepositoryService
|
||||
@@ -22,18 +25,19 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
* https://developer.github.com/v3/repos/branches/#list-branches
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/branches")(referrersOnly { repository =>
|
||||
JsonFormat(
|
||||
JGitUtil
|
||||
.getBranches(
|
||||
owner = repository.owner,
|
||||
name = repository.name,
|
||||
defaultBranch = repository.repository.defaultBranch,
|
||||
origin = repository.repository.originUserName.isEmpty
|
||||
)
|
||||
.map { br =>
|
||||
ApiBranchForList(br.name, ApiBranchCommit(br.commitId))
|
||||
}
|
||||
)
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
JsonFormat(
|
||||
JGitUtil
|
||||
.getBranches(
|
||||
git = git,
|
||||
defaultBranch = repository.repository.defaultBranch,
|
||||
origin = repository.repository.originUserName.isEmpty
|
||||
)
|
||||
.map { br =>
|
||||
ApiBranchForList(br.name, ApiBranchCommit(br.commitId))
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -41,21 +45,22 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
* https://developer.github.com/v3/repos/branches/#get-branch
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/branches/*")(referrersOnly { repository =>
|
||||
//import gitbucket.core.api._
|
||||
(for {
|
||||
branch <- params.get("splat") if repository.branchList.contains(branch)
|
||||
br <- getBranches(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
repository.repository.defaultBranch,
|
||||
repository.repository.originUserName.isEmpty
|
||||
).find(_.name == branch)
|
||||
} yield {
|
||||
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||
JsonFormat(
|
||||
ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtection(protection))(RepositoryName(repository))
|
||||
)
|
||||
}) getOrElse NotFound()
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
git =>
|
||||
(for {
|
||||
branch <- params.get("splat") if repository.branchList.contains(branch)
|
||||
br <- getBranches(
|
||||
git,
|
||||
repository.repository.defaultBranch,
|
||||
repository.repository.originUserName.isEmpty
|
||||
).find(_.name == branch)
|
||||
} yield {
|
||||
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||
JsonFormat(
|
||||
ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtection(protection))(RepositoryName(repository))
|
||||
)
|
||||
}) getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
@@ -209,28 +214,30 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
*/
|
||||
patch("/api/v3/repos/:owner/:repository/branches/*")(ownerOnly { repository =>
|
||||
import gitbucket.core.api._
|
||||
(for {
|
||||
branch <- params.get("splat") if repository.branchList.contains(branch)
|
||||
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
|
||||
br <- getBranches(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
repository.repository.defaultBranch,
|
||||
repository.repository.originUserName.isEmpty
|
||||
).find(_.name == branch)
|
||||
} yield {
|
||||
if (protection.enabled) {
|
||||
enableBranchProtection(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
branch,
|
||||
protection.status.enforcement_level == ApiBranchProtection.Everyone,
|
||||
protection.status.contexts
|
||||
)
|
||||
} else {
|
||||
disableBranchProtection(repository.owner, repository.name, branch)
|
||||
}
|
||||
JsonFormat(ApiBranch(branch, ApiBranchCommit(br.commitId), protection)(RepositoryName(repository)))
|
||||
}) getOrElse NotFound()
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
git =>
|
||||
(for {
|
||||
branch <- params.get("splat") if repository.branchList.contains(branch)
|
||||
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
|
||||
br <- getBranches(
|
||||
git,
|
||||
repository.repository.defaultBranch,
|
||||
repository.repository.originUserName.isEmpty
|
||||
).find(_.name == branch)
|
||||
} yield {
|
||||
if (protection.enabled) {
|
||||
enableBranchProtection(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
branch,
|
||||
protection.status.enforcement_level == ApiBranchProtection.Everyone,
|
||||
protection.status.contexts
|
||||
)
|
||||
} else {
|
||||
disableBranchProtection(repository.owner, repository.name, branch)
|
||||
}
|
||||
JsonFormat(ApiBranch(branch, ApiBranchCommit(br.commitId), protection)(RepositoryName(repository)))
|
||||
}) getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package gitbucket.core.controller.api
|
||||
import gitbucket.core.api.{ApiContents, JsonFormat}
|
||||
import gitbucket.core.api.{ApiContents, ApiError, CreateAFile, JsonFormat}
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.service.{AccountService, ProtectedBranchService, RepositoryService}
|
||||
import gitbucket.core.service.{RepositoryCommitFileService, RepositoryService}
|
||||
import gitbucket.core.util.Directory.getRepositoryDir
|
||||
import gitbucket.core.util.JGitUtil.{FileInfo, getContentFromId, getFileList}
|
||||
import gitbucket.core.util._
|
||||
@@ -11,7 +11,7 @@ import gitbucket.core.util.Implicits._
|
||||
import org.eclipse.jgit.api.Git
|
||||
|
||||
trait ApiRepositoryContentsControllerBase extends ControllerBase {
|
||||
self: ReferrerAuthenticator =>
|
||||
self: ReferrerAuthenticator with WritableUsersAuthenticator with RepositoryCommitFileService =>
|
||||
|
||||
/*
|
||||
* i. Get the README
|
||||
@@ -101,16 +101,48 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
/*
|
||||
* iii. Create a file
|
||||
* iii. Create a file or iv. Update a file
|
||||
* https://developer.github.com/v3/repos/contents/#create-a-file
|
||||
* https://developer.github.com/v3/repos/contents/#update-a-file
|
||||
* if sha is presented, update a file else create a file.
|
||||
* requested #2112
|
||||
*/
|
||||
|
||||
/*
|
||||
* iv. Update a file
|
||||
* https://developer.github.com/v3/repos/contents/#update-a-file
|
||||
* requested #2112
|
||||
*/
|
||||
put("/api/v3/repos/:owner/:repository/contents/*")(writableUsersOnly { repository =>
|
||||
JsonFormat(for {
|
||||
data <- extractFromJsonBody[CreateAFile]
|
||||
} yield {
|
||||
val branch = data.branch.getOrElse(repository.repository.defaultBranch)
|
||||
val commit = using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
|
||||
revCommit.name
|
||||
}
|
||||
val paths = multiParams("splat").head.split("/")
|
||||
val path = paths.take(paths.size - 1).toList.mkString("/")
|
||||
if (data.sha.isDefined && data.sha.get != commit) {
|
||||
ApiError("The blob SHA is not matched.", Some("https://developer.github.com/v3/repos/contents/#update-a-file"))
|
||||
} else {
|
||||
val objectId = commitFile(
|
||||
repository,
|
||||
branch,
|
||||
path,
|
||||
Some(paths.last),
|
||||
if (data.sha.isDefined) {
|
||||
Some(paths.last)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
StringUtil.base64Decode(data.content),
|
||||
data.message,
|
||||
commit,
|
||||
context.loginAccount.get,
|
||||
data.committer.map(_.name).getOrElse(context.loginAccount.get.fullName),
|
||||
data.committer.map(_.email).getOrElse(context.loginAccount.get.mailAddress)
|
||||
)
|
||||
ApiContents("file", paths.last, path, objectId.name, None, None)(RepositoryName(repository))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
/*
|
||||
* v. Delete a file
|
||||
|
||||
29
src/main/scala/gitbucket/core/model/GpgKey.scala
Normal file
29
src/main/scala/gitbucket/core/model/GpgKey.scala
Normal file
@@ -0,0 +1,29 @@
|
||||
package gitbucket.core.model
|
||||
|
||||
trait GpgKeyComponent { self: Profile =>
|
||||
import profile.api._
|
||||
|
||||
lazy val GpgKeys = TableQuery[GpgKeys]
|
||||
|
||||
class GpgKeys(tag: Tag) extends Table[GpgKey](tag, "GPG_KEY") {
|
||||
val userName = column[String]("USER_NAME")
|
||||
val keyId = column[Int]("KEY_ID", O AutoInc)
|
||||
val gpgKeyId = column[Long]("GPG_KEY_ID")
|
||||
val title = column[String]("TITLE")
|
||||
val publicKey = column[String]("PUBLIC_KEY")
|
||||
def * = (userName, keyId, gpgKeyId, title, publicKey) <> (GpgKey.tupled, GpgKey.unapply)
|
||||
|
||||
def byPrimaryKey(userName: String, keyId: Int) =
|
||||
(this.userName === userName.bind) && (this.keyId === keyId.bind)
|
||||
def byGpgKeyId(gpgKeyId: Long) =
|
||||
this.gpgKeyId === gpgKeyId.bind
|
||||
}
|
||||
}
|
||||
|
||||
case class GpgKey(
|
||||
userName: String,
|
||||
keyId: Int = 0,
|
||||
gpgKeyId: Long,
|
||||
title: String,
|
||||
publicKey: String
|
||||
)
|
||||
@@ -59,6 +59,7 @@ trait CoreProfile
|
||||
with PullRequestComponent
|
||||
with RepositoryComponent
|
||||
with SshKeyComponent
|
||||
with GpgKeyComponent
|
||||
with RepositoryWebHookComponent
|
||||
with RepositoryWebHookEventComponent
|
||||
with AccountWebHookComponent
|
||||
|
||||
@@ -331,6 +331,7 @@ object PluginRegistry {
|
||||
instance.getPlugins().find(_.pluginId == pluginId) match {
|
||||
case Some(x) => {
|
||||
logger.warn(s"Plugin ${pluginId} is duplicated. ${x.pluginJar.getName} is available.")
|
||||
classLoader.close()
|
||||
}
|
||||
case None => {
|
||||
// Migration
|
||||
@@ -370,7 +371,9 @@ object PluginRegistry {
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
case e: Throwable => logger.error(s"Error during plugin initialization: ${pluginJar.getName}", e)
|
||||
case e: Throwable =>
|
||||
logger.error(s"Error during plugin initialization: ${pluginJar.getName}", e)
|
||||
classLoader.close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,6 +412,13 @@ object PluginRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
def getPluginInfoFromClassLoader(classLoader: ClassLoader): Option[PluginInfo] = {
|
||||
instance
|
||||
.getPlugins()
|
||||
.find { info =>
|
||||
info.classLoader.equals(classLoader)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case class Link(
|
||||
|
||||
@@ -240,8 +240,8 @@ trait AccountService {
|
||||
def updateLastLoginDate(userName: String)(implicit s: Session): Unit =
|
||||
Accounts.filter(_.userName === userName.bind).map(_.lastLoginDate).update(currentDate)
|
||||
|
||||
def createGroup(groupName: String, description: Option[String], url: Option[String])(implicit s: Session): Unit =
|
||||
Accounts insert Account(
|
||||
def createGroup(groupName: String, description: Option[String], url: Option[String])(implicit s: Session): Account = {
|
||||
val group = Account(
|
||||
userName = groupName,
|
||||
password = "",
|
||||
fullName = groupName,
|
||||
@@ -256,6 +256,9 @@ trait AccountService {
|
||||
isRemoved = false,
|
||||
description = description
|
||||
)
|
||||
Accounts insert group
|
||||
group
|
||||
}
|
||||
|
||||
def updateGroup(groupName: String, description: Option[String], url: Option[String], removed: Boolean)(
|
||||
implicit s: Session
|
||||
|
||||
@@ -352,15 +352,19 @@ trait ActivityService {
|
||||
currentDate
|
||||
)
|
||||
|
||||
def recordReleaseActivity(userName: String, repositoryName: String, activityUserName: String, name: String)(
|
||||
implicit s: Session
|
||||
): Unit =
|
||||
def recordReleaseActivity(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
releaseName: String,
|
||||
tagName: String
|
||||
)(implicit s: Session): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"release",
|
||||
s"[user:${activityUserName}] released [release:${userName}/${repositoryName}/${name}] at [repo:${userName}/${repositoryName}]",
|
||||
s"[user:${activityUserName}] released [release:${userName}/${repositoryName}/${tagName}:${releaseName}] at [repo:${userName}/${repositoryName}]",
|
||||
None,
|
||||
currentDate
|
||||
)
|
||||
|
||||
29
src/main/scala/gitbucket/core/service/GpgKeyService.scala
Normal file
29
src/main/scala/gitbucket/core/service/GpgKeyService.scala
Normal file
@@ -0,0 +1,29 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import java.io.ByteArrayInputStream
|
||||
|
||||
import gitbucket.core.model.GpgKey
|
||||
|
||||
import collection.JavaConverters._
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import org.bouncycastle.bcpg.ArmoredInputStream
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing
|
||||
import org.bouncycastle.openpgp.bc.BcPGPObjectFactory
|
||||
|
||||
trait GpgKeyService {
|
||||
def getGpgPublicKeys(userName: String)(implicit s: Session): List[GpgKey] =
|
||||
GpgKeys.filter(_.userName === userName.bind).sortBy(_.gpgKeyId).list
|
||||
|
||||
def addGpgPublicKey(userName: String, title: String, publicKey: String)(implicit s: Session): Unit = {
|
||||
val pubKeyOf = new BcPGPObjectFactory(new ArmoredInputStream(new ByteArrayInputStream(publicKey.getBytes)))
|
||||
pubKeyOf.iterator().asScala.foreach {
|
||||
case keyRing: PGPPublicKeyRing =>
|
||||
val key = keyRing.getPublicKey()
|
||||
GpgKeys.insert(GpgKey(userName = userName, gpgKeyId = key.getKeyID, title = title, publicKey = publicKey))
|
||||
}
|
||||
}
|
||||
|
||||
def deleteGpgPublicKey(userName: String, keyId: Int)(implicit s: Session): Unit =
|
||||
GpgKeys.filter(_.byPrimaryKey(userName, keyId)).delete
|
||||
}
|
||||
@@ -360,6 +360,8 @@ trait MergeService {
|
||||
}
|
||||
}
|
||||
|
||||
callPullRequestWebHook("closed", repository, issueId, context.loginAccount.get)
|
||||
|
||||
updatePullRequests(repository.owner, repository.name, pullreq.branch, loginAccount, "closed")
|
||||
|
||||
// call hooks
|
||||
|
||||
@@ -6,6 +6,8 @@ import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import difflib.{Delta, DiffUtils}
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.api.JsonFormat
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Implicits._
|
||||
@@ -20,7 +22,13 @@ import org.eclipse.jgit.lib.ObjectId
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
trait PullRequestService {
|
||||
self: IssuesService with CommitsService with WebHookService with WebHookPullRequestService with RepositoryService =>
|
||||
self: IssuesService
|
||||
with CommitsService
|
||||
with WebHookService
|
||||
with WebHookPullRequestService
|
||||
with RepositoryService
|
||||
with MergeService
|
||||
with ActivityService =>
|
||||
import PullRequestService._
|
||||
|
||||
def getPullRequest(owner: String, repository: String, issueId: Int)(
|
||||
@@ -81,27 +89,66 @@ trait PullRequestService {
|
||||
// .map { x => PullRequestCount(x._1, x._2) }
|
||||
|
||||
def createPullRequest(
|
||||
originUserName: String,
|
||||
originRepositoryName: String,
|
||||
originRepository: RepositoryInfo,
|
||||
issueId: Int,
|
||||
originBranch: String,
|
||||
requestUserName: String,
|
||||
requestRepositoryName: String,
|
||||
requestBranch: String,
|
||||
commitIdFrom: String,
|
||||
commitIdTo: String
|
||||
)(implicit s: Session): Unit =
|
||||
PullRequests insert PullRequest(
|
||||
originUserName,
|
||||
originRepositoryName,
|
||||
issueId,
|
||||
originBranch,
|
||||
requestUserName,
|
||||
requestRepositoryName,
|
||||
requestBranch,
|
||||
commitIdFrom,
|
||||
commitIdTo
|
||||
)
|
||||
commitIdTo: String,
|
||||
loginAccount: Account
|
||||
)(implicit s: Session, context: Context): Unit = {
|
||||
getIssue(originRepository.owner, originRepository.name, issueId.toString).foreach { baseIssue =>
|
||||
PullRequests insert PullRequest(
|
||||
originRepository.owner,
|
||||
originRepository.name,
|
||||
issueId,
|
||||
originBranch,
|
||||
requestUserName,
|
||||
requestRepositoryName,
|
||||
requestBranch,
|
||||
commitIdFrom,
|
||||
commitIdTo
|
||||
)
|
||||
|
||||
// fetch requested branch
|
||||
fetchAsPullRequest(
|
||||
originRepository.owner,
|
||||
originRepository.name,
|
||||
requestUserName,
|
||||
requestRepositoryName,
|
||||
requestBranch,
|
||||
issueId
|
||||
)
|
||||
|
||||
// record activity
|
||||
recordPullRequestActivity(
|
||||
originRepository.owner,
|
||||
originRepository.name,
|
||||
loginAccount.userName,
|
||||
issueId,
|
||||
baseIssue.title
|
||||
)
|
||||
|
||||
// call web hook
|
||||
callPullRequestWebHook("opened", originRepository, issueId, loginAccount)
|
||||
|
||||
getIssue(originRepository.owner, originRepository.name, issueId.toString) foreach { issue =>
|
||||
// extract references and create refer comment
|
||||
createReferComment(
|
||||
originRepository.owner,
|
||||
originRepository.name,
|
||||
issue,
|
||||
baseIssue.title + " " + baseIssue.content,
|
||||
loginAccount
|
||||
)
|
||||
|
||||
// call hooks
|
||||
PluginRegistry().getPullRequestHooks.foreach(_.created(issue, originRepository))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getPullRequestsByRequest(userName: String, repositoryName: String, branch: String, closed: Option[Boolean])(
|
||||
implicit s: Session
|
||||
|
||||
@@ -73,7 +73,7 @@ trait ReleaseService {
|
||||
}
|
||||
|
||||
def getReleases(owner: String, repository: String)(implicit s: Session): Seq[ReleaseTag] = {
|
||||
ReleaseTags.filter(x => x.byRepository(owner, repository)).list
|
||||
ReleaseTags.filter(x => x.byRepository(owner, repository)).sortBy(x => x.updatedDate).list
|
||||
}
|
||||
|
||||
def getRelease(owner: String, repository: String, tag: String)(implicit s: Session): Option[ReleaseTag] = {
|
||||
|
||||
@@ -29,7 +29,7 @@ trait RepositoryCommitFileService {
|
||||
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit
|
||||
)(implicit s: Session, c: JsonFormat.Context) = {
|
||||
// prepend path to the filename
|
||||
_commitFile(repository, branch, message, loginAccount)(f)
|
||||
_commitFile(repository, branch, message, loginAccount, loginAccount.fullName, loginAccount.mailAddress)(f)
|
||||
}
|
||||
|
||||
def commitFile(
|
||||
@@ -43,7 +43,35 @@ trait RepositoryCommitFileService {
|
||||
message: String,
|
||||
commit: String,
|
||||
loginAccount: Account
|
||||
)(implicit s: Session, c: JsonFormat.Context) = {
|
||||
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
||||
commitFile(
|
||||
repository,
|
||||
branch,
|
||||
path,
|
||||
newFileName,
|
||||
oldFileName,
|
||||
if (content.nonEmpty) { content.getBytes(charset) } else { Array.emptyByteArray },
|
||||
message,
|
||||
commit,
|
||||
loginAccount,
|
||||
loginAccount.fullName,
|
||||
loginAccount.mailAddress
|
||||
)
|
||||
}
|
||||
|
||||
def commitFile(
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
branch: String,
|
||||
path: String,
|
||||
newFileName: Option[String],
|
||||
oldFileName: Option[String],
|
||||
content: Array[Byte],
|
||||
message: String,
|
||||
commit: String,
|
||||
loginAccount: Account,
|
||||
fullName: String,
|
||||
mailAddress: String
|
||||
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
||||
|
||||
val newPath = newFileName.map { newFileName =>
|
||||
if (path.length == 0) newFileName else s"${path}/${newFileName}"
|
||||
@@ -52,7 +80,7 @@ trait RepositoryCommitFileService {
|
||||
if (path.length == 0) oldFileName else s"${path}/${oldFileName}"
|
||||
}
|
||||
|
||||
_commitFile(repository, branch, message, loginAccount) {
|
||||
_commitFile(repository, branch, message, loginAccount, fullName, mailAddress) {
|
||||
case (git, headTip, builder, inserter) =>
|
||||
if (headTip.getName == commit) {
|
||||
val permission = JGitUtil
|
||||
@@ -70,7 +98,7 @@ trait RepositoryCommitFileService {
|
||||
newPath.foreach { newPath =>
|
||||
builder.add(JGitUtil.createDirCacheEntry(newPath, permission.map { bits =>
|
||||
FileMode.fromBits(bits)
|
||||
} getOrElse FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, content.getBytes(charset))))
|
||||
} getOrElse FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, content)))
|
||||
}
|
||||
builder.finish()
|
||||
}
|
||||
@@ -81,10 +109,12 @@ trait RepositoryCommitFileService {
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
branch: String,
|
||||
message: String,
|
||||
loginAccount: Account
|
||||
loginAccount: Account,
|
||||
committerName: String,
|
||||
committerMailAddress: String
|
||||
)(
|
||||
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit
|
||||
)(implicit s: Session, c: JsonFormat.Context) = {
|
||||
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
||||
|
||||
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
@@ -101,8 +131,8 @@ trait RepositoryCommitFileService {
|
||||
headTip,
|
||||
builder.getDirCache.writeTree(inserter),
|
||||
headName,
|
||||
loginAccount.fullName,
|
||||
loginAccount.mailAddress,
|
||||
committerName,
|
||||
committerMailAddress,
|
||||
message
|
||||
)
|
||||
|
||||
@@ -114,7 +144,7 @@ trait RepositoryCommitFileService {
|
||||
|
||||
// call post commit hook
|
||||
val error = PluginRegistry().getReceiveHooks.flatMap { hook =>
|
||||
hook.preReceive(repository.owner, repository.name, receivePack, receiveCommand, loginAccount.userName)
|
||||
hook.preReceive(repository.owner, repository.name, receivePack, receiveCommand, committerName)
|
||||
}.headOption
|
||||
|
||||
error match {
|
||||
@@ -131,7 +161,7 @@ trait RepositoryCommitFileService {
|
||||
val refUpdate = git.getRepository.updateRef(headName)
|
||||
refUpdate.setNewObjectId(commitId)
|
||||
refUpdate.setForceUpdate(false)
|
||||
refUpdate.setRefLogIdent(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress))
|
||||
refUpdate.setRefLogIdent(new PersonIdent(committerName, committerMailAddress))
|
||||
refUpdate.update()
|
||||
|
||||
// update pull request
|
||||
@@ -139,26 +169,25 @@ trait RepositoryCommitFileService {
|
||||
|
||||
// record activity
|
||||
val commitInfo = new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
||||
recordPushActivity(repository.owner, repository.name, loginAccount.userName, branch, List(commitInfo))
|
||||
recordPushActivity(repository.owner, repository.name, committerName, branch, List(commitInfo))
|
||||
|
||||
// create issue comment by commit message
|
||||
createIssueComment(repository.owner, repository.name, commitInfo)
|
||||
|
||||
// close issue by commit message
|
||||
if (branch == repository.repository.defaultBranch) {
|
||||
closeIssuesFromMessage(message, loginAccount.userName, repository.owner, repository.name).foreach {
|
||||
issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
callIssuesWebHook("closed", repository, issue, loginAccount)
|
||||
PluginRegistry().getIssueHooks
|
||||
.foreach(_.closedByCommitComment(issue, repository, message, loginAccount))
|
||||
}
|
||||
closeIssuesFromMessage(message, committerName, repository.owner, repository.name).foreach { issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
callIssuesWebHook("closed", repository, issue, loginAccount)
|
||||
PluginRegistry().getIssueHooks
|
||||
.foreach(_.closedByCommitComment(issue, repository, message, loginAccount))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// call post commit hook
|
||||
PluginRegistry().getReceiveHooks.foreach { hook =>
|
||||
hook.postReceive(repository.owner, repository.name, receivePack, receiveCommand, loginAccount.userName)
|
||||
hook.postReceive(repository.owner, repository.name, receivePack, receiveCommand, committerName)
|
||||
}
|
||||
|
||||
val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
||||
@@ -177,6 +206,7 @@ trait RepositoryCommitFileService {
|
||||
}
|
||||
}
|
||||
}
|
||||
commitId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ trait WebHookService {
|
||||
val httpClient = HttpClientBuilder.create.useSystemProperties.addInterceptorLast(itcp).build
|
||||
logger.debug(s"start web hook invocation for ${webHook.url}")
|
||||
val httpPost = new HttpPost(webHook.url)
|
||||
logger.info(s"Content-Type: ${webHook.ctype.ctype}")
|
||||
logger.debug(s"Content-Type: ${webHook.ctype.ctype}")
|
||||
httpPost.addHeader("Content-Type", webHook.ctype.ctype)
|
||||
httpPost.addHeader("X-Github-Event", event.name)
|
||||
httpPost.addHeader("X-Github-Delivery", java.util.UUID.randomUUID().toString)
|
||||
@@ -540,11 +540,8 @@ object WebHookService {
|
||||
object WebHookCreatePayload {
|
||||
|
||||
def apply(
|
||||
git: Git,
|
||||
sender: Account,
|
||||
refName: String,
|
||||
repositoryInfo: RepositoryInfo,
|
||||
commits: List[CommitInfo],
|
||||
repositoryOwner: Account,
|
||||
ref: String,
|
||||
refType: String
|
||||
@@ -555,7 +552,7 @@ object WebHookService {
|
||||
ref_type = refType,
|
||||
description = repositoryInfo.repository.description.getOrElse(""),
|
||||
master_branch = repositoryInfo.repository.defaultBranch,
|
||||
repository = ApiRepository.forWebhookPayload(repositoryInfo, owner = ApiUser(repositoryOwner))
|
||||
repository = ApiRepository(repositoryInfo, repositoryOwner)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -597,9 +594,9 @@ object WebHookService {
|
||||
before = ObjectId.toString(oldId),
|
||||
after = ObjectId.toString(newId),
|
||||
commits = commits.map { commit =>
|
||||
ApiCommit.forWebhookPayload(git, RepositoryName(repositoryInfo), commit)
|
||||
ApiCommit(git, RepositoryName(repositoryInfo), commit)
|
||||
},
|
||||
repository = ApiRepository.forWebhookPayload(repositoryInfo, owner = ApiUser(repositoryOwner))
|
||||
repository = ApiRepository(repositoryInfo, repositoryOwner)
|
||||
)
|
||||
|
||||
def createDummyPayload(sender: Account): WebHookPushPayload =
|
||||
|
||||
@@ -25,11 +25,17 @@ class ApiAuthenticationFilter extends Filter with AccessTokenService with Accoun
|
||||
case auth if auth.startsWith("Basic ") => doBasicAuth(auth, loadSystemSettings(), request).toRight(())
|
||||
case _ => Left(())
|
||||
}
|
||||
.orElse {
|
||||
Option(req.getParameter("access_token")).map(AccessTokenService.getAccountByAccessToken(_).toRight(()))
|
||||
}
|
||||
.orElse {
|
||||
Option(request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]).map(Right(_))
|
||||
} match {
|
||||
case Some(Right(account)) => request.setAttribute(Keys.Session.LoginAccount, account); chain.doFilter(req, res)
|
||||
case None => chain.doFilter(req, res)
|
||||
case Some(Right(account)) =>
|
||||
request.setAttribute(Keys.Session.LoginAccount, account)
|
||||
updateLastLoginDate(account.userName)
|
||||
chain.doFilter(req, res)
|
||||
case None => chain.doFilter(req, res)
|
||||
case Some(Left(_)) => {
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED)
|
||||
response.setContentType("application/json; charset=utf-8")
|
||||
|
||||
@@ -7,7 +7,32 @@ import org.scalatra.ScalatraFilter
|
||||
|
||||
import scala.collection.mutable.ListBuffer
|
||||
|
||||
class CompositeScalatraFilter extends Filter {
|
||||
abstract class ControllerFilter extends Filter {
|
||||
|
||||
def process(request: ServletRequest, response: ServletResponse, checkPath: String): Boolean
|
||||
|
||||
override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = {
|
||||
val contextPath = request.getServletContext.getContextPath
|
||||
val requestPath = request.asInstanceOf[HttpServletRequest].getRequestURI.substring(contextPath.length)
|
||||
val checkPath = if (requestPath.endsWith("/")) {
|
||||
requestPath
|
||||
} else {
|
||||
requestPath + "/"
|
||||
}
|
||||
|
||||
if (!checkPath.startsWith("/upload/") && !checkPath.startsWith("/git/") && !checkPath.startsWith("/git-lfs/") &&
|
||||
!checkPath.startsWith("/assets/") && !checkPath.startsWith("/plugin-assets/")) {
|
||||
val continue = process(request, response, checkPath)
|
||||
if (!continue) {
|
||||
return ()
|
||||
}
|
||||
}
|
||||
|
||||
chain.doFilter(request, response)
|
||||
}
|
||||
}
|
||||
|
||||
class CompositeScalatraFilter extends ControllerFilter {
|
||||
|
||||
private val filters = new ListBuffer[(ScalatraFilter, String)]()
|
||||
|
||||
@@ -29,34 +54,23 @@ class CompositeScalatraFilter extends Filter {
|
||||
}
|
||||
}
|
||||
|
||||
override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = {
|
||||
val contextPath = request.getServletContext.getContextPath
|
||||
val requestPath = request.asInstanceOf[HttpServletRequest].getRequestURI.substring(contextPath.length)
|
||||
val checkPath = if (requestPath.endsWith("/")) {
|
||||
requestPath
|
||||
} else {
|
||||
requestPath + "/"
|
||||
}
|
||||
override def process(request: ServletRequest, response: ServletResponse, checkPath: String): Boolean = {
|
||||
filters
|
||||
.filter {
|
||||
case (_, path) =>
|
||||
val start = path.replaceFirst("/\\*$", "/")
|
||||
checkPath.startsWith(start)
|
||||
}
|
||||
.foreach {
|
||||
case (filter, _) =>
|
||||
val mockChain = new MockFilterChain()
|
||||
filter.doFilter(request, response, mockChain)
|
||||
if (mockChain.continue == false) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (!checkPath.startsWith("/upload/") && !checkPath.startsWith("/git/") && !checkPath.startsWith("/git-lfs/") &&
|
||||
!checkPath.startsWith("/plugin-assets/")) {
|
||||
filters
|
||||
.filter {
|
||||
case (_, path) =>
|
||||
val start = path.replaceFirst("/\\*$", "/")
|
||||
checkPath.startsWith(start)
|
||||
}
|
||||
.foreach {
|
||||
case (filter, _) =>
|
||||
val mockChain = new MockFilterChain()
|
||||
filter.doFilter(request, response, mockChain)
|
||||
if (mockChain.continue == false) {
|
||||
return ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chain.doFilter(request, response)
|
||||
true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@ class GitAuthenticationFilter extends Filter with RepositoryService with Account
|
||||
if (isUpdating) {
|
||||
if (hasDeveloperRole(repository.owner, repository.name, Some(account))) {
|
||||
request.setAttribute(Keys.Request.UserName, account.userName)
|
||||
request.setAttribute(Keys.Request.RepositoryLockKey, s"${repository.owner}/${repository.name}")
|
||||
true
|
||||
} else false
|
||||
} else if (repository.repository.isPrivate) {
|
||||
|
||||
@@ -22,8 +22,8 @@ import org.eclipse.jgit.transport.resolver._
|
||||
import org.slf4j.LoggerFactory
|
||||
import javax.servlet.ServletConfig
|
||||
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
||||
|
||||
import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
||||
import org.eclipse.jgit.internal.storage.file.FileRepository
|
||||
import org.json4s.jackson.Serialization._
|
||||
|
||||
/**
|
||||
@@ -41,7 +41,7 @@ class GitRepositoryServlet extends GitServlet with SystemSettingsService {
|
||||
setReceivePackFactory(new GitBucketReceivePackFactory())
|
||||
|
||||
val root: File = new File(Directory.RepositoryHome)
|
||||
setRepositoryResolver(new GitBucketRepositoryResolver(new FileResolver[HttpServletRequest](root, true)))
|
||||
setRepositoryResolver(new GitBucketRepositoryResolver)
|
||||
|
||||
super.init(config)
|
||||
}
|
||||
@@ -55,11 +55,24 @@ class GitRepositoryServlet extends GitServlet with SystemSettingsService {
|
||||
res.sendRedirect(baseUrl(req) + "/" + paths.dropRight(1).last + "/" + paths.last)
|
||||
|
||||
} else if (req.getMethod.toUpperCase == "POST" && req.getRequestURI.endsWith("/info/lfs/objects/batch")) {
|
||||
serviceGitLfsBatchAPI(req, res)
|
||||
|
||||
withLockRepository(req) {
|
||||
serviceGitLfsBatchAPI(req, res)
|
||||
}
|
||||
} else {
|
||||
// response for git client
|
||||
super.service(req, res)
|
||||
withLockRepository(req) {
|
||||
super.service(req, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def withLockRepository[T](req: HttpServletRequest)(f: => T): T = {
|
||||
if (req.hasAttribute(Keys.Request.RepositoryLockKey)) {
|
||||
LockUtil.lock(req.getAttribute(Keys.Request.RepositoryLockKey).asInstanceOf[String]) {
|
||||
f
|
||||
}
|
||||
} else {
|
||||
f
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,10 +151,7 @@ class GitRepositoryServlet extends GitServlet with SystemSettingsService {
|
||||
}
|
||||
}
|
||||
|
||||
class GitBucketRepositoryResolver(parent: FileResolver[HttpServletRequest])
|
||||
extends RepositoryResolver[HttpServletRequest] {
|
||||
|
||||
private val resolver = new FileResolver[HttpServletRequest](new File(Directory.GitBucketHome), true)
|
||||
class GitBucketRepositoryResolver extends RepositoryResolver[HttpServletRequest] {
|
||||
|
||||
override def open(req: HttpServletRequest, name: String): Repository = {
|
||||
// Rewrite repository path if routing is marched
|
||||
@@ -150,10 +160,10 @@ class GitBucketRepositoryResolver(parent: FileResolver[HttpServletRequest])
|
||||
.map {
|
||||
case GitRepositoryRouting(urlPattern, localPath, _) =>
|
||||
val path = urlPattern.r.replaceFirstIn(name, localPath)
|
||||
resolver.open(req, path)
|
||||
new FileRepository(new File(Directory.GitBucketHome, path))
|
||||
}
|
||||
.getOrElse {
|
||||
parent.open(req, name)
|
||||
new FileRepository(new File(Directory.RepositoryHome, name))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,6 +225,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
with AccountService
|
||||
with IssuesService
|
||||
with ActivityService
|
||||
with MergeService
|
||||
with PullRequestService
|
||||
with WebHookService
|
||||
with LabelsService
|
||||
@@ -380,11 +391,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
} yield {
|
||||
val refType = if (refName(1) == "tags") "tag" else "branch"
|
||||
WebHookCreatePayload(
|
||||
git,
|
||||
pusherAccount,
|
||||
command.getRefName,
|
||||
repositoryInfo,
|
||||
newCommits,
|
||||
ownerAccount,
|
||||
ref = branchName,
|
||||
refType = refType
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package gitbucket.core.servlet
|
||||
|
||||
import javax.servlet.http.{HttpServlet, HttpServletRequest, HttpServletResponse}
|
||||
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.util.FileUtil
|
||||
import org.apache.commons.io.IOUtils
|
||||
@@ -17,24 +16,32 @@ class PluginAssetsServlet extends HttpServlet {
|
||||
|
||||
assetsMappings
|
||||
.find { case (prefix, _, _) => path.startsWith("/plugin-assets" + prefix) }
|
||||
.flatMap {
|
||||
.foreach {
|
||||
case (prefix, resourcePath, classLoader) =>
|
||||
val resourceName = path.substring(("/plugin-assets" + prefix).length)
|
||||
Option(classLoader.getResourceAsStream(resourcePath.stripPrefix("/") + resourceName))
|
||||
}
|
||||
.map { in =>
|
||||
try {
|
||||
val bytes = IOUtils.toByteArray(in)
|
||||
resp.setContentLength(bytes.length)
|
||||
resp.setContentType(FileUtil.getMimeType(path, bytes))
|
||||
resp.setHeader("Cache-Control", "max-age=3600")
|
||||
resp.getOutputStream.write(bytes)
|
||||
} finally {
|
||||
in.close()
|
||||
}
|
||||
}
|
||||
.getOrElse {
|
||||
resp.setStatus(404)
|
||||
val ifNoneMatch = req.getHeader("If-None-Match")
|
||||
PluginRegistry.getPluginInfoFromClassLoader(classLoader).map { info =>
|
||||
val etag = s""""${info.pluginJar.lastModified}"""" // ETag must wrapped with double quote
|
||||
if (ifNoneMatch == etag) {
|
||||
resp.setStatus(304)
|
||||
} else {
|
||||
val resourceName = path.substring(("/plugin-assets" + prefix).length)
|
||||
Option(classLoader.getResourceAsStream(resourcePath.stripPrefix("/") + resourceName))
|
||||
.map { in =>
|
||||
try {
|
||||
val bytes = IOUtils.toByteArray(in)
|
||||
resp.setContentLength(bytes.length)
|
||||
resp.setContentType(FileUtil.getMimeType(path, bytes))
|
||||
resp.setHeader("ETag", etag)
|
||||
resp.getOutputStream.write(bytes)
|
||||
} finally {
|
||||
in.close()
|
||||
}
|
||||
}
|
||||
.getOrElse {
|
||||
resp.setStatus(404)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import javax.servlet.http.HttpServletRequest
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
|
||||
class PluginControllerFilter extends Filter {
|
||||
class PluginControllerFilter extends ControllerFilter {
|
||||
|
||||
private var filterConfig: FilterConfig = null
|
||||
|
||||
@@ -21,16 +21,13 @@ class PluginControllerFilter extends Filter {
|
||||
}
|
||||
}
|
||||
|
||||
override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = {
|
||||
val contextPath = request.getServletContext.getContextPath
|
||||
val requestUri = request.asInstanceOf[HttpServletRequest].getRequestURI.substring(contextPath.length)
|
||||
|
||||
override def process(request: ServletRequest, response: ServletResponse, checkPath: String): Boolean = {
|
||||
PluginRegistry()
|
||||
.getControllers()
|
||||
.filter {
|
||||
case (_, path) =>
|
||||
val start = path.replaceFirst("/\\*$", "/")
|
||||
(requestUri + "/").startsWith(start)
|
||||
checkPath.startsWith(start)
|
||||
}
|
||||
.foreach {
|
||||
case (controller, _) =>
|
||||
@@ -42,11 +39,11 @@ class PluginControllerFilter extends Filter {
|
||||
controller.doFilter(request, response, mockChain)
|
||||
|
||||
if (mockChain.continue == false) {
|
||||
return ()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
chain.doFilter(request, response)
|
||||
true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
61
src/main/scala/gitbucket/core/util/GpgUtil.scala
Normal file
61
src/main/scala/gitbucket/core/util/GpgUtil.scala
Normal file
@@ -0,0 +1,61 @@
|
||||
package gitbucket.core.util
|
||||
import java.io.ByteArrayInputStream
|
||||
|
||||
import collection.JavaConverters._
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import org.bouncycastle.bcpg.ArmoredInputStream
|
||||
import org.bouncycastle.openpgp.{PGPPublicKey, PGPPublicKeyRing, PGPSignatureList}
|
||||
import org.bouncycastle.openpgp.bc.BcPGPObjectFactory
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider
|
||||
|
||||
object GpgUtil {
|
||||
def str2GpgKeyId(keyStr: String): Option[Long] = {
|
||||
val pubKeyOf = new BcPGPObjectFactory(new ArmoredInputStream(new ByteArrayInputStream(keyStr.getBytes)))
|
||||
pubKeyOf.iterator().asScala.collectFirst {
|
||||
case keyRing: PGPPublicKeyRing =>
|
||||
keyRing.getPublicKey().getKeyID
|
||||
}
|
||||
}
|
||||
|
||||
def getGpgKey(gpgKeyId: Long)(implicit s: Session): Option[PGPPublicKey] = {
|
||||
val pubKeyOpt = GpgKeys.filter(_.byGpgKeyId(gpgKeyId)).map { _.publicKey }.firstOption
|
||||
pubKeyOpt.flatMap { pubKeyStr =>
|
||||
val pubKeyObjFactory =
|
||||
new BcPGPObjectFactory(new ArmoredInputStream(new ByteArrayInputStream(pubKeyStr.getBytes())))
|
||||
pubKeyObjFactory.nextObject() match {
|
||||
case pubKeyRing: PGPPublicKeyRing =>
|
||||
Option(pubKeyRing.getPublicKey(gpgKeyId))
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def verifySign(signInfo: JGitUtil.GpgSignInfo)(implicit s: Session): Option[JGitUtil.GpgVerifyInfo] = {
|
||||
new BcPGPObjectFactory(new ArmoredInputStream(new ByteArrayInputStream(signInfo.signArmored)))
|
||||
.iterator()
|
||||
.asScala
|
||||
.flatMap {
|
||||
case signList: PGPSignatureList =>
|
||||
signList
|
||||
.iterator()
|
||||
.asScala
|
||||
.flatMap { sign =>
|
||||
getGpgKey(sign.getKeyID)
|
||||
.map { pubKey =>
|
||||
sign.init(new BcPGPContentVerifierBuilderProvider, pubKey)
|
||||
sign.update(signInfo.target)
|
||||
(sign, pubKey)
|
||||
}
|
||||
.collect {
|
||||
case (sign, pubKey) if sign.verify() =>
|
||||
JGitUtil.GpgVerifyInfo(pubKey.getUserIDs.next, pubKey.getKeyID.toHexString.toUpperCase)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.toList
|
||||
.headOption
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package gitbucket.core.util
|
||||
|
||||
import java.io.{ByteArrayOutputStream, File, FileInputStream, InputStream}
|
||||
import java.io._
|
||||
|
||||
import gitbucket.core.service.RepositoryService
|
||||
import org.eclipse.jgit.api.Git
|
||||
@@ -75,6 +75,59 @@ object JGitUtil {
|
||||
linkUrl: Option[String]
|
||||
)
|
||||
|
||||
/**
|
||||
* The gpg commit sign data.
|
||||
* @param signArmored signature for commit
|
||||
* @param target string for verification target
|
||||
*/
|
||||
case class GpgSignInfo(signArmored: Array[Byte], target: Array[Byte])
|
||||
|
||||
/**
|
||||
* The verified gpg sign data.
|
||||
* @param signedUser
|
||||
* @param signedKeyId
|
||||
*/
|
||||
case class GpgVerifyInfo(signedUser: String, signedKeyId: String)
|
||||
|
||||
private def getSignTarget(rev: RevCommit): Array[Byte] = {
|
||||
val ascii = "ASCII"
|
||||
val os = new ByteArrayOutputStream()
|
||||
val w = new OutputStreamWriter(os, rev.getEncoding)
|
||||
os.write("tree ".getBytes(ascii))
|
||||
rev.getTree.copyTo(os)
|
||||
os.write('\n')
|
||||
|
||||
rev.getParents.foreach { p =>
|
||||
os.write("parent ".getBytes(ascii))
|
||||
p.copyTo(os)
|
||||
os.write('\n')
|
||||
}
|
||||
|
||||
os.write("author ".getBytes(ascii))
|
||||
w.write(rev.getAuthorIdent.toExternalString)
|
||||
w.flush()
|
||||
os.write('\n')
|
||||
|
||||
os.write("committer ".getBytes(ascii))
|
||||
w.write(rev.getCommitterIdent.toExternalString)
|
||||
w.flush()
|
||||
os.write('\n')
|
||||
|
||||
if (rev.getEncoding.name != "UTF-8") {
|
||||
os.write("encoding ".getBytes(ascii))
|
||||
os.write(Constants.encodeASCII(rev.getEncoding.name))
|
||||
os.write('\n')
|
||||
}
|
||||
|
||||
os.write('\n')
|
||||
|
||||
if (!rev.getFullMessage.isEmpty) {
|
||||
w.write(rev.getFullMessage)
|
||||
w.flush()
|
||||
}
|
||||
os.toByteArray
|
||||
}
|
||||
|
||||
/**
|
||||
* The commit data.
|
||||
*
|
||||
@@ -99,7 +152,9 @@ object JGitUtil {
|
||||
authorEmailAddress: String,
|
||||
commitTime: Date,
|
||||
committerName: String,
|
||||
committerEmailAddress: String
|
||||
committerEmailAddress: String,
|
||||
commitSign: Option[GpgSignInfo],
|
||||
verified: Option[GpgVerifyInfo]
|
||||
) {
|
||||
|
||||
def this(rev: org.eclipse.jgit.revwalk.RevCommit) =
|
||||
@@ -113,7 +168,11 @@ object JGitUtil {
|
||||
rev.getAuthorIdent.getEmailAddress,
|
||||
rev.getCommitterIdent.getWhen,
|
||||
rev.getCommitterIdent.getName,
|
||||
rev.getCommitterIdent.getEmailAddress
|
||||
rev.getCommitterIdent.getEmailAddress,
|
||||
Option(rev.getRawGpgSignature).map { s =>
|
||||
GpgSignInfo(s, getSignTarget(rev))
|
||||
},
|
||||
None
|
||||
)
|
||||
|
||||
val summary = getSummaryMessage(fullMessage, shortMessage)
|
||||
@@ -240,18 +299,16 @@ object JGitUtil {
|
||||
* Returns the number of commits in the specified branch or commit.
|
||||
* If the specified branch has over 10000 commits, this method returns 100001.
|
||||
*/
|
||||
def getCommitCount(owner: String, repository: String, branch: String): Int = {
|
||||
val dir = getRepositoryDir(owner, repository)
|
||||
def getCommitCount(git: Git, branch: String, max: Int = 10001): Int = {
|
||||
val dir = git.getRepository.getDirectory
|
||||
val key = dir.getAbsolutePath + "@" + branch
|
||||
val entry = cache.getEntry(key)
|
||||
|
||||
if (entry == null) {
|
||||
using(Git.open(dir)) { git =>
|
||||
val commitId = git.getRepository.resolve(branch)
|
||||
val commitCount = git.log.add(commitId).call.iterator.asScala.take(10001).size
|
||||
cache.put(key, commitCount)
|
||||
commitCount
|
||||
}
|
||||
val commitId = git.getRepository.resolve(branch)
|
||||
val commitCount = git.log.add(commitId).call.iterator.asScala.take(max).size
|
||||
cache.put(key, commitCount)
|
||||
commitCount
|
||||
} else {
|
||||
entry.getValue
|
||||
}
|
||||
@@ -1110,6 +1167,7 @@ object JGitUtil {
|
||||
/**
|
||||
* Fetch pull request contents into refs/pull/${issueId}/head and return (commitIdTo, commitIdFrom)
|
||||
*/
|
||||
// TODO should take Git instead of owner and username for testability
|
||||
def updatePullRequest(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
@@ -1154,42 +1212,40 @@ object JGitUtil {
|
||||
git.log.add(startCommit).addPath(path).setMaxCount(1).call.iterator.next
|
||||
}
|
||||
|
||||
def getBranches(owner: String, name: String, defaultBranch: String, origin: Boolean): Seq[BranchInfo] = {
|
||||
using(Git.open(getRepositoryDir(owner, name))) { git =>
|
||||
val repo = git.getRepository
|
||||
val defaultObject = repo.resolve(defaultBranch)
|
||||
def getBranches(git: Git, defaultBranch: String, origin: Boolean): Seq[BranchInfo] = {
|
||||
val repo = git.getRepository
|
||||
val defaultObject = repo.resolve(defaultBranch)
|
||||
|
||||
git.branchList.call.asScala.map { ref =>
|
||||
val walk = new RevWalk(repo)
|
||||
try {
|
||||
val defaultCommit = walk.parseCommit(defaultObject)
|
||||
val branchName = ref.getName.stripPrefix("refs/heads/")
|
||||
val branchCommit = walk.parseCommit(ref.getObjectId)
|
||||
val when = branchCommit.getCommitterIdent.getWhen
|
||||
val committer = branchCommit.getCommitterIdent.getName
|
||||
val committerEmail = branchCommit.getCommitterIdent.getEmailAddress
|
||||
val mergeInfo = if (origin && branchName == defaultBranch) {
|
||||
None
|
||||
} else {
|
||||
walk.reset()
|
||||
walk.setRevFilter(RevFilter.MERGE_BASE)
|
||||
walk.markStart(branchCommit)
|
||||
walk.markStart(defaultCommit)
|
||||
val mergeBase = walk.next()
|
||||
walk.reset()
|
||||
walk.setRevFilter(RevFilter.ALL)
|
||||
Some(
|
||||
BranchMergeInfo(
|
||||
ahead = RevWalkUtils.count(walk, branchCommit, mergeBase),
|
||||
behind = RevWalkUtils.count(walk, defaultCommit, mergeBase),
|
||||
isMerged = walk.isMergedInto(branchCommit, defaultCommit)
|
||||
)
|
||||
git.branchList.call.asScala.map { ref =>
|
||||
val walk = new RevWalk(repo)
|
||||
try {
|
||||
val defaultCommit = walk.parseCommit(defaultObject)
|
||||
val branchName = ref.getName.stripPrefix("refs/heads/")
|
||||
val branchCommit = walk.parseCommit(ref.getObjectId)
|
||||
val when = branchCommit.getCommitterIdent.getWhen
|
||||
val committer = branchCommit.getCommitterIdent.getName
|
||||
val committerEmail = branchCommit.getCommitterIdent.getEmailAddress
|
||||
val mergeInfo = if (origin && branchName == defaultBranch) {
|
||||
None
|
||||
} else {
|
||||
walk.reset()
|
||||
walk.setRevFilter(RevFilter.MERGE_BASE)
|
||||
walk.markStart(branchCommit)
|
||||
walk.markStart(defaultCommit)
|
||||
val mergeBase = walk.next()
|
||||
walk.reset()
|
||||
walk.setRevFilter(RevFilter.ALL)
|
||||
Some(
|
||||
BranchMergeInfo(
|
||||
ahead = RevWalkUtils.count(walk, branchCommit, mergeBase),
|
||||
behind = RevWalkUtils.count(walk, defaultCommit, mergeBase),
|
||||
isMerged = walk.isMergedInto(branchCommit, defaultCommit)
|
||||
)
|
||||
}
|
||||
BranchInfo(branchName, committer, when, committerEmail, mergeInfo, ref.getObjectId.name)
|
||||
} finally {
|
||||
walk.dispose()
|
||||
)
|
||||
}
|
||||
BranchInfo(branchName, committer, when, committerEmail, mergeInfo, ref.getObjectId.name)
|
||||
} finally {
|
||||
walk.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1203,7 +1259,7 @@ object JGitUtil {
|
||||
val blame = blamer.call()
|
||||
var blameMap = Map[String, JGitUtil.BlameInfo]()
|
||||
var idLine = List[(String, Int)]()
|
||||
val commits = 0.to(blame.getResultContents().size() - 1).map { i =>
|
||||
0.to(blame.getResultContents().size() - 1).map { i =>
|
||||
val c = blame.getSourceCommit(i)
|
||||
if (!blameMap.contains(c.name)) {
|
||||
blameMap += c.name -> JGitUtil.BlameInfo(
|
||||
@@ -1243,24 +1299,7 @@ object JGitUtil {
|
||||
}
|
||||
}
|
||||
|
||||
def getFileSize(git: Git, repository: RepositoryService.RepositoryInfo, treeWalk: TreeWalk): Long = {
|
||||
val attrs = treeWalk.getAttributes
|
||||
val loader = git.getRepository.open(treeWalk.getObjectId(0))
|
||||
if (attrs.containsKey("filter") && attrs.get("filter").getValue == "lfs") {
|
||||
val lfsAttrs = getLfsAttributes(loader)
|
||||
lfsAttrs.get("size").map(_.toLong).get
|
||||
} else {
|
||||
loader.getSize
|
||||
}
|
||||
}
|
||||
|
||||
def getFileSize(git: Git, repository: RepositoryService.RepositoryInfo, tree: RevTree, path: String): Long = {
|
||||
using(TreeWalk.forPath(git.getRepository, path, tree)) { treeWalk =>
|
||||
getFileSize(git, repository, treeWalk)
|
||||
}
|
||||
}
|
||||
|
||||
def openFile[T](git: Git, repository: RepositoryService.RepositoryInfo, treeWalk: TreeWalk)(
|
||||
private def openFile[T](git: Git, repository: RepositoryService.RepositoryInfo, treeWalk: TreeWalk)(
|
||||
f: InputStream => T
|
||||
): T = {
|
||||
val attrs = treeWalk.getAttributes
|
||||
@@ -1270,7 +1309,6 @@ object JGitUtil {
|
||||
if (lfsAttrs.nonEmpty) {
|
||||
val oid = lfsAttrs("oid").split(":")(1)
|
||||
|
||||
val file = new File(FileUtil.getLfsFilePath(repository.owner, repository.name, oid))
|
||||
using(new FileInputStream(FileUtil.getLfsFilePath(repository.owner, repository.name, oid))) { in =>
|
||||
f(in)
|
||||
}
|
||||
|
||||
@@ -86,6 +86,11 @@ object Keys {
|
||||
*/
|
||||
val UserName = "USER_NAME"
|
||||
|
||||
/**
|
||||
* Request key for the Lock key which is used during Git repository write access.
|
||||
*/
|
||||
val RepositoryLockKey = "REPOSITORY_LOCK_KEY"
|
||||
|
||||
/**
|
||||
* Generate request key for the request cache.
|
||||
*/
|
||||
|
||||
@@ -176,6 +176,10 @@ object Markdown {
|
||||
} else if (!enableWikiLink) {
|
||||
if (context.currentPath.contains("/blob/")) {
|
||||
urlWithRawParam
|
||||
} else if (context.currentPath.contains("/tree/")) {
|
||||
val paths = context.currentPath.split("/")
|
||||
val path = if (paths.length > 3) paths.drop(4).mkString("/") else branch
|
||||
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + path + "/" + urlWithRawParam
|
||||
} else {
|
||||
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + urlWithRawParam
|
||||
}
|
||||
|
||||
@@ -235,10 +235,10 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
.group(2)}@${m.group(3).substring(0, 7)}</a>"""
|
||||
)
|
||||
.replaceAll(
|
||||
"\\[release:([^\\s]+?)/([^\\s]+?)/([^\\s]+?)\\]",
|
||||
"\\[release:([^\\s]+?)/([^\\s]+?)/([^\\s]+?):(.+)\\]",
|
||||
(m: Match) =>
|
||||
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/releases/${encodeRefName(m.group(3))}">${m
|
||||
.group(3)}</a>"""
|
||||
.group(4)}</a>"""
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
39
src/main/twirl/gitbucket/core/account/gpg.scala.html
Normal file
39
src/main/twirl/gitbucket/core/account/gpg.scala.html
Normal file
@@ -0,0 +1,39 @@
|
||||
@(account: gitbucket.core.model.Account, gpgKeys: List[gitbucket.core.model.GpgKey])(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.ssh.SshUtil
|
||||
@gitbucket.core.html.main("GPG Keys"){
|
||||
@gitbucket.core.account.html.menu("gpg", context.loginAccount.get.userName, false){
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading strong">GPG Keys</div>
|
||||
<div class="panel-body">
|
||||
@if(gpgKeys.isEmpty){
|
||||
No keys
|
||||
}
|
||||
@gpgKeys.zipWithIndex.map { case (key, i) =>
|
||||
@if(i != 0){
|
||||
<hr>
|
||||
}
|
||||
<strong style="line-height: 30px;">@key.title</strong> (@key.gpgKeyId.toHexString.toUpperCase)
|
||||
<a href="@context.path/@account.userName/_gpg/delete/@key.keyId" class="btn btn-sm btn-danger pull-right">Delete</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<form method="POST" action="@context.path/@account.userName/_gpg" validate="true" autocomplete="off">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading strong">Add a GPG Key</div>
|
||||
<div class="panel-body">
|
||||
<fieldset class="form-group">
|
||||
<label for="title" class="strong">Title</label>
|
||||
<div><span id="error-title" class="error"></span></div>
|
||||
<input type="text" name="title" id="title" class="form-control"/>
|
||||
</fieldset>
|
||||
<fieldset class="form-group">
|
||||
<label for="publicKey" class="strong">Key</label>
|
||||
<div><span id="error-publicKey" class="error"></span></div>
|
||||
<textarea name="publicKey" id="publicKey" class="form-control" style="height: 200px;"></textarea>
|
||||
</fieldset>
|
||||
<input type="submit" class="btn btn-success" value="Add"/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,11 @@
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
<li class="menu-item-hover @if(active=="gpg"){active}">
|
||||
<a href="@context.path/@userName/_gpg">
|
||||
<i class="menu-icon octicon octicon-key"></i> <span>GPG Keys</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="menu-item-hover @if(active=="application"){active}">
|
||||
<a href="@context.path/@userName/_application">
|
||||
<i class="menu-icon octicon octicon-rocket"></i> <span>Applications</span>
|
||||
|
||||
@@ -2,12 +2,24 @@
|
||||
@import gitbucket.core.plugin.PluginRegistry
|
||||
@import gitbucket.core.view.helpers
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html prefix="og: http://ogp.me/ns#">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>@title</title>
|
||||
<meta property="og:title" content="@title" />
|
||||
<meta property="og:type" content="object" />
|
||||
<meta property="og:url" content="@context.request.getRequestURL" />
|
||||
@if(repository.isEmpty){
|
||||
<meta property="og:image" content="@context.baseUrl/assets/common/images/gitbucket_ogp.png" />
|
||||
}
|
||||
@repository.map{ r =>
|
||||
<meta property="og:image" content="@context.baseUrl/@r.owner/_avatar" />
|
||||
@r.repository.description.map{ desc =>
|
||||
<meta property="og:description" content="@desc" />
|
||||
}
|
||||
}
|
||||
<link rel="icon" href="@helpers.assets("/common/images/gitbucket.png")" type="image/vnd.microsoft.icon" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="@helpers.assets("/vendors/google-fonts/css/source-sans-pro.css")" rel="stylesheet">
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<h3>Update release for @tag.name</h3>
|
||||
}
|
||||
<span id="error-name" class="error"></span>
|
||||
<input type="text" id="release-name" name="name" class="form-control" value="@release.map { case (release, _) => @release.name }.getOrElse(tag.name)" placeholder="Title" style="margin-bottom: 6px;" autofocus/>
|
||||
<input type="text" id="release-name" name="name" class="form-control" value="@(release.map { case (release, _) => release.name }.getOrElse(tag.name))" placeholder="Title" style="margin-bottom: 6px;" autofocus/>
|
||||
<div class="pull-right">
|
||||
Previous tag: <select id="insert-changelog-tag">
|
||||
@tags.map { tag =>
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
@release.map { case (release, assets) =>
|
||||
@assets.map { asset =>
|
||||
<li>
|
||||
<i class="octicon octicon-file"></i><a href="@helpers.url(repository)/releases/@helpers.urlEncode(release.tag)/assets/@asset.fileName">@asset.label</a>
|
||||
<a href="@helpers.url(repository)/releases/@helpers.urlEncode(release.tag)/assets/@helpers.urlEncode(asset.fileName)"><i class="octicon octicon-file" data-filename="@asset.label"></i>@asset.label</a>
|
||||
<span class="label label-default">@helpers.readableSize(Some(asset.size))</span>
|
||||
</li>
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<ul style="list-style: none; padding-left: 8px;" id="attachedFiles">
|
||||
@assets.map{ asset =>
|
||||
<li>
|
||||
<i class="octicon octicon-file"></i><a href="@helpers.url(repository)/releases/@release.tag/assets/@asset.fileName">@asset.label</a>
|
||||
<a href="@helpers.url(repository)/releases/@release.tag/assets/@asset.fileName"><i class="octicon octicon-file" data-filename="@helpers.urlEncode(asset.label)"></i>@asset.label</a>
|
||||
<span class="label label-default">@helpers.readableSize(Some(asset.size))</span>
|
||||
</li>
|
||||
}
|
||||
|
||||
@@ -40,6 +40,13 @@
|
||||
@if(i != 0){ <tr> }
|
||||
<td>
|
||||
<div class="pull-right text-right">
|
||||
@if(commit.commitSign.isDefined){
|
||||
@commit.verified.map{ v =>
|
||||
<span class="gpg-verified" data-toggle="tooltip" title="Signed by @v.signedUser (@v.signedKeyId)">Verified</span>
|
||||
}.getOrElse{
|
||||
<span class="gpg-unverified">Unverified</span>
|
||||
}
|
||||
}
|
||||
@defining(getTags(commit.id)) { tags =>
|
||||
@if(tags.nonEmpty){
|
||||
<span class="muted">
|
||||
@@ -121,6 +128,8 @@
|
||||
</nav>
|
||||
<script>
|
||||
$(function () {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
|
||||
$('.toggle-check').click(function(){
|
||||
var div = $(this).next('div');
|
||||
if(div.is(':visible')){
|
||||
@@ -133,5 +142,11 @@
|
||||
});
|
||||
})
|
||||
</script>
|
||||
<style type="text/css">
|
||||
a.tag {
|
||||
color: #888888;
|
||||
margin-right: 4px;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
<i class="octicon octicon-file-directory"></i>
|
||||
}
|
||||
} else {
|
||||
<i class="octicon octicon-file-text"></i>
|
||||
<i class="octicon octicon-file-text" data-filename="@helpers.urlEncode(file.name)"></i>
|
||||
}
|
||||
</td>
|
||||
<td class="ellipsis-cell" style="width: 20%; min-width: 160px;">
|
||||
|
||||
@@ -142,6 +142,7 @@ div.content-wrapper {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: inline;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
/* ======================================================================== */
|
||||
@@ -1087,6 +1088,22 @@ div.author-info div.committer {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.gpg-verified {
|
||||
color: #3c763d;
|
||||
border: 1px solid #3c763d;
|
||||
border-radius: 3px;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.gpg-unverified {
|
||||
color: #777;
|
||||
border: 1px solid #777;
|
||||
border-radius: 3px;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
/* Diff */
|
||||
/****************************************************************************/
|
||||
|
||||
BIN
src/main/webapp/assets/common/images/gitbucket_ogp.png
Normal file
BIN
src/main/webapp/assets/common/images/gitbucket_ogp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
@@ -2,18 +2,12 @@ package gitbucket.core
|
||||
|
||||
import java.sql.DriverManager
|
||||
|
||||
import com.dimafeng.testcontainers.{MySQLContainer, PostgreSQLContainer}
|
||||
import io.github.gitbucket.solidbase.Solidbase
|
||||
import io.github.gitbucket.solidbase.model.Module
|
||||
import liquibase.database.core.{H2Database, MySQLDatabase, PostgresDatabase}
|
||||
import org.junit.runner.Description
|
||||
import org.scalatest.{FunSuite, Tag}
|
||||
import com.wix.mysql.EmbeddedMysql._
|
||||
import com.wix.mysql.config.Charset
|
||||
import com.wix.mysql.config.MysqldConfig._
|
||||
import com.wix.mysql.distribution.Version._
|
||||
import ru.yandex.qatools.embed.postgresql.PostgresStarter
|
||||
import ru.yandex.qatools.embed.postgresql.config.AbstractPostgresConfig.{Credentials, Net, Storage, Timeout}
|
||||
import ru.yandex.qatools.embed.postgresql.config.PostgresConfig
|
||||
import ru.yandex.qatools.embed.postgresql.distribution.Version.Main.PRODUCTION
|
||||
|
||||
object ExternalDBTest extends Tag("ExternalDBTest")
|
||||
|
||||
@@ -28,52 +22,46 @@ class GitBucketCoreModuleSpec extends FunSuite {
|
||||
)
|
||||
}
|
||||
|
||||
test("Migration MySQL", ExternalDBTest) {
|
||||
val config = aMysqldConfig(v5_7_latest)
|
||||
.withPort(3306)
|
||||
.withUser("sa", "sa")
|
||||
.withCharset(Charset.UTF8)
|
||||
.withServerVariable("bind-address", "127.0.0.1")
|
||||
.build()
|
||||
implicit private val suiteDescription = Description.createSuiteDescription(getClass)
|
||||
|
||||
val mysqld = anEmbeddedMysql(config)
|
||||
.addSchema("gitbucket")
|
||||
.start()
|
||||
|
||||
try {
|
||||
new Solidbase().migrate(
|
||||
DriverManager.getConnection("jdbc:mysql://localhost:3306/gitbucket?useSSL=false", "sa", "sa"),
|
||||
Thread.currentThread().getContextClassLoader(),
|
||||
new MySQLDatabase(),
|
||||
new Module(GitBucketCoreModule.getModuleId, GitBucketCoreModule.getVersions)
|
||||
)
|
||||
} finally {
|
||||
mysqld.stop()
|
||||
Seq("8.0", "5.7").foreach { tag =>
|
||||
test(s"Migration MySQL $tag", ExternalDBTest) {
|
||||
val container = new MySQLContainer() {
|
||||
override val container = new org.testcontainers.containers.MySQLContainer(s"mysql:$tag") {
|
||||
override def getDriverClassName = "org.mariadb.jdbc.Driver"
|
||||
}
|
||||
// TODO https://github.com/testcontainers/testcontainers-java/issues/736
|
||||
container.withCommand("mysqld --default-authentication-plugin=mysql_native_password")
|
||||
}
|
||||
container.starting()
|
||||
try {
|
||||
new Solidbase().migrate(
|
||||
DriverManager.getConnection(s"${container.jdbcUrl}?useSSL=false", container.username, container.password),
|
||||
Thread.currentThread().getContextClassLoader(),
|
||||
new MySQLDatabase(),
|
||||
new Module(GitBucketCoreModule.getModuleId, GitBucketCoreModule.getVersions)
|
||||
)
|
||||
} finally {
|
||||
container.finished()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test("Migration PostgreSQL", ExternalDBTest) {
|
||||
val runtime = PostgresStarter.getDefaultInstance()
|
||||
val config = new PostgresConfig(
|
||||
PRODUCTION,
|
||||
new Net("localhost", 5432),
|
||||
new Storage("gitbucket"),
|
||||
new Timeout(),
|
||||
new Credentials("sa", "sa")
|
||||
)
|
||||
Seq("11", "10").foreach { tag =>
|
||||
test(s"Migration PostgreSQL $tag", ExternalDBTest) {
|
||||
val container = PostgreSQLContainer(s"postgres:$tag")
|
||||
|
||||
val exec = runtime.prepare(config)
|
||||
val process = exec.start()
|
||||
|
||||
try {
|
||||
new Solidbase().migrate(
|
||||
DriverManager.getConnection("jdbc:postgresql://localhost:5432/gitbucket", "sa", "sa"),
|
||||
Thread.currentThread().getContextClassLoader(),
|
||||
new PostgresDatabase(),
|
||||
new Module(GitBucketCoreModule.getModuleId, GitBucketCoreModule.getVersions)
|
||||
)
|
||||
} finally {
|
||||
process.stop()
|
||||
container.starting()
|
||||
try {
|
||||
new Solidbase().migrate(
|
||||
DriverManager.getConnection(container.jdbcUrl, container.username, container.password),
|
||||
Thread.currentThread().getContextClassLoader(),
|
||||
new PostgresDatabase(),
|
||||
new Module(GitBucketCoreModule.getModuleId, GitBucketCoreModule.getVersions)
|
||||
)
|
||||
} finally {
|
||||
container.finished()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
690
src/test/scala/gitbucket/core/api/ApiSpecModels.scala
Normal file
690
src/test/scala/gitbucket/core/api/ApiSpecModels.scala
Normal file
@@ -0,0 +1,690 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import java.util.{Calendar, Date, TimeZone}
|
||||
|
||||
import gitbucket.core.model._
|
||||
import gitbucket.core.plugin.PluginInfo
|
||||
import gitbucket.core.service.ProtectedBranchService.ProtectedBranchInfo
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.util.JGitUtil.{CommitInfo, DiffInfo, FileInfo, TagInfo}
|
||||
import gitbucket.core.util.RepositoryName
|
||||
import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
||||
import org.eclipse.jgit.lib.ObjectId
|
||||
|
||||
object ApiSpecModels {
|
||||
|
||||
implicit val context = JsonFormat.Context("http://gitbucket.exmple.com", None)
|
||||
|
||||
val date1 = {
|
||||
val d = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
|
||||
d.set(2011, 3, 14, 16, 0, 49)
|
||||
d.getTime
|
||||
}
|
||||
|
||||
def date(date: String): Date = {
|
||||
val f = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
|
||||
f.setTimeZone(TimeZone.getTimeZone("UTC"))
|
||||
f.parse(date)
|
||||
}
|
||||
|
||||
// Models
|
||||
|
||||
val account = Account(
|
||||
userName = "octocat",
|
||||
fullName = "octocat",
|
||||
mailAddress = "octocat@example.com",
|
||||
password = "1234",
|
||||
isAdmin = false,
|
||||
url = None,
|
||||
registeredDate = date1,
|
||||
updatedDate = date1,
|
||||
lastLoginDate = Some(date1),
|
||||
image = None,
|
||||
isGroupAccount = false,
|
||||
isRemoved = false,
|
||||
description = None
|
||||
)
|
||||
|
||||
val sha1 = "6dcb09b5b57875f334f61aebed695e2e4193db5e"
|
||||
val repo1Name = RepositoryName("octocat/Hello-World")
|
||||
|
||||
val repository = Repository(
|
||||
userName = repo1Name.owner,
|
||||
repositoryName = repo1Name.name,
|
||||
isPrivate = false,
|
||||
description = Some("This your first repo!"),
|
||||
defaultBranch = "master",
|
||||
registeredDate = date1,
|
||||
updatedDate = date1,
|
||||
lastActivityDate = date1,
|
||||
originUserName = Some("octopus plus cat"),
|
||||
originRepositoryName = Some("Hello World"),
|
||||
parentUserName = Some("github"),
|
||||
parentRepositoryName = Some("Hello-World"),
|
||||
options = RepositoryOptions(
|
||||
issuesOption = "PUBLIC",
|
||||
externalIssuesUrl = Some("https://external.com/gitbucket"),
|
||||
wikiOption = "PUBLIC",
|
||||
externalWikiUrl = Some("https://external.com/gitbucket"),
|
||||
allowFork = true,
|
||||
mergeOptions = "merge-commit,squash,rebase",
|
||||
defaultMergeOption = "merge-commit"
|
||||
)
|
||||
)
|
||||
|
||||
val repositoryInfo = RepositoryInfo(
|
||||
owner = repo1Name.owner,
|
||||
name = repo1Name.name,
|
||||
repository = repository,
|
||||
issueCount = 1,
|
||||
pullCount = 1,
|
||||
forkedCount = 1,
|
||||
branchList = Seq("master", "develop"),
|
||||
tags = Seq(
|
||||
TagInfo(name = "v1.0", time = date("2015-05-05T23:40:27Z"), id = "id1", message = "1.0 released"),
|
||||
TagInfo(name = "v2.0", time = date("2016-05-05T23:40:27Z"), id = "id2", message = "2.0 released")
|
||||
),
|
||||
managers = Seq("myboss")
|
||||
)
|
||||
|
||||
val label = Label(
|
||||
userName = repo1Name.owner,
|
||||
repositoryName = repo1Name.name,
|
||||
labelId = 10,
|
||||
labelName = "bug",
|
||||
color = "f29513"
|
||||
)
|
||||
|
||||
val issue = Issue(
|
||||
userName = repo1Name.owner,
|
||||
repositoryName = repo1Name.name,
|
||||
issueId = 1347,
|
||||
openedUserName = "bear",
|
||||
milestoneId = None,
|
||||
priorityId = None,
|
||||
assignedUserName = None,
|
||||
title = "Found a bug",
|
||||
content = Some("I'm having a problem with this."),
|
||||
closed = false,
|
||||
registeredDate = date1,
|
||||
updatedDate = date1,
|
||||
isPullRequest = false
|
||||
)
|
||||
|
||||
val issuePR = issue.copy(
|
||||
title = "new-feature",
|
||||
content = Some("Please pull these awesome changes"),
|
||||
closed = true,
|
||||
isPullRequest = true
|
||||
)
|
||||
|
||||
val issueComment = IssueComment(
|
||||
userName = repo1Name.owner,
|
||||
repositoryName = repo1Name.name,
|
||||
issueId = issue.issueId,
|
||||
commentId = 1,
|
||||
action = "comment",
|
||||
commentedUserName = "bear",
|
||||
content = "Me too",
|
||||
registeredDate = date1,
|
||||
updatedDate = date1
|
||||
)
|
||||
|
||||
val pullRequest = PullRequest(
|
||||
userName = repo1Name.owner,
|
||||
repositoryName = repo1Name.name,
|
||||
issueId = issuePR.issueId,
|
||||
branch = "master",
|
||||
requestUserName = "bear",
|
||||
requestRepositoryName = repo1Name.name,
|
||||
requestBranch = "new-topic",
|
||||
commitIdFrom = sha1,
|
||||
commitIdTo = sha1
|
||||
)
|
||||
|
||||
val commitComment = CommitComment(
|
||||
userName = repo1Name.owner,
|
||||
repositoryName = repo1Name.name,
|
||||
commitId = sha1,
|
||||
commentId = 29724692,
|
||||
commentedUserName = "bear",
|
||||
content = "Maybe you should use more emoji on this line.",
|
||||
fileName = Some("README.md"),
|
||||
oldLine = Some(1),
|
||||
newLine = Some(1),
|
||||
registeredDate = date("2015-05-05T23:40:27Z"),
|
||||
updatedDate = date("2015-05-05T23:40:27Z"),
|
||||
issueId = Some(issuePR.issueId),
|
||||
originalCommitId = sha1,
|
||||
originalOldLine = None,
|
||||
originalNewLine = None
|
||||
)
|
||||
|
||||
val commitStatus = CommitStatus(
|
||||
commitStatusId = 1,
|
||||
userName = repo1Name.owner,
|
||||
repositoryName = repo1Name.name,
|
||||
commitId = sha1,
|
||||
context = "Default",
|
||||
state = CommitState.SUCCESS,
|
||||
targetUrl = Some("https://ci.example.com/1000/output"),
|
||||
description = Some("Build has completed successfully"),
|
||||
creator = account.userName,
|
||||
registeredDate = date1,
|
||||
updatedDate = date1
|
||||
)
|
||||
|
||||
// APIs
|
||||
|
||||
val apiUser = ApiUser(account)
|
||||
|
||||
val apiRepository = ApiRepository(
|
||||
repository = repository,
|
||||
owner = apiUser,
|
||||
forkedCount = repositoryInfo.forkedCount,
|
||||
watchers = 0
|
||||
)
|
||||
|
||||
val apiLabel = ApiLabel(
|
||||
label = label,
|
||||
repositoryName = repo1Name
|
||||
)
|
||||
|
||||
val apiIssue = ApiIssue(
|
||||
issue = issue,
|
||||
repositoryName = repo1Name,
|
||||
user = apiUser,
|
||||
labels = List(apiLabel)
|
||||
)
|
||||
|
||||
val apiIssuePR = ApiIssue(
|
||||
issue = issuePR,
|
||||
repositoryName = repo1Name,
|
||||
user = apiUser,
|
||||
labels = List(apiLabel)
|
||||
)
|
||||
|
||||
val apiComment = ApiComment(
|
||||
comment = issueComment,
|
||||
repositoryName = repo1Name,
|
||||
issueId = issueComment.issueId,
|
||||
user = apiUser,
|
||||
isPullRequest = false
|
||||
)
|
||||
|
||||
val apiCommentPR = ApiComment(
|
||||
comment = issueComment,
|
||||
repositoryName = repo1Name,
|
||||
issueId = issueComment.issueId,
|
||||
user = apiUser,
|
||||
isPullRequest = true
|
||||
)
|
||||
|
||||
val apiPullRequest = ApiPullRequest(
|
||||
issue = issuePR,
|
||||
pullRequest = pullRequest,
|
||||
headRepo = apiRepository,
|
||||
baseRepo = apiRepository,
|
||||
user = apiUser,
|
||||
labels = List(apiLabel),
|
||||
assignee = Some(apiUser),
|
||||
mergedComment = Some((issueComment, account))
|
||||
)
|
||||
|
||||
// https://developer.github.com/v3/activity/events/types/#pullrequestreviewcommentevent
|
||||
val apiPullRequestReviewComment = ApiPullRequestReviewComment(
|
||||
comment = commitComment,
|
||||
commentedUser = apiUser,
|
||||
repositoryName = repo1Name,
|
||||
issueId = commitComment.issueId.get
|
||||
)
|
||||
|
||||
val commitInfo = (id: String) =>
|
||||
CommitInfo(
|
||||
id = id,
|
||||
shortMessage = "short message",
|
||||
fullMessage = "full message",
|
||||
parents = List("1da452aa92d7db1bc093d266c80a69857718c406"),
|
||||
authorTime = date1,
|
||||
authorName = account.userName,
|
||||
authorEmailAddress = account.mailAddress,
|
||||
commitTime = date1,
|
||||
committerName = account.userName,
|
||||
committerEmailAddress = account.mailAddress,
|
||||
None,
|
||||
None
|
||||
)
|
||||
|
||||
val apiCommitListItem = ApiCommitListItem(
|
||||
commit = commitInfo(sha1),
|
||||
repositoryName = repo1Name
|
||||
)
|
||||
|
||||
val apiCommit = {
|
||||
val commit = commitInfo(sha1)
|
||||
ApiCommit(
|
||||
id = commit.id,
|
||||
message = commit.fullMessage,
|
||||
timestamp = commit.commitTime,
|
||||
added = Nil,
|
||||
removed = Nil,
|
||||
modified = List("README.md"),
|
||||
author = ApiPersonIdent.author(commit),
|
||||
committer = ApiPersonIdent.committer(commit)
|
||||
)(repo1Name)
|
||||
}
|
||||
|
||||
val apiCommits = ApiCommits(
|
||||
repositoryName = repo1Name,
|
||||
commitInfo = commitInfo(sha1),
|
||||
diffs = Seq(
|
||||
DiffInfo(
|
||||
changeType = ChangeType.MODIFY,
|
||||
oldPath = "doc/README.md",
|
||||
newPath = "doc/README.md",
|
||||
oldContent = None,
|
||||
newContent = None,
|
||||
oldIsImage = false,
|
||||
newIsImage = false,
|
||||
oldObjectId = None,
|
||||
newObjectId = Some(sha1),
|
||||
oldMode = "old_mode",
|
||||
newMode = "new_mode",
|
||||
tooLarge = false,
|
||||
patch = Some("""@@ -1 +1,2 @@
|
||||
|-body1
|
||||
|\ No newline at end of file
|
||||
|+body1
|
||||
|+body2
|
||||
|\ No newline at end of file""".stripMargin)
|
||||
)
|
||||
),
|
||||
author = account,
|
||||
committer = account,
|
||||
commentCount = 2
|
||||
)
|
||||
|
||||
val apiCommitStatus = ApiCommitStatus(
|
||||
status = commitStatus,
|
||||
creator = apiUser
|
||||
)
|
||||
|
||||
val apiCombinedCommitStatus = ApiCombinedCommitStatus(
|
||||
sha = sha1,
|
||||
statuses = Iterable((commitStatus, account)),
|
||||
repository = apiRepository
|
||||
)
|
||||
|
||||
val apiBranchProtection = ApiBranchProtection(
|
||||
info = ProtectedBranchInfo(
|
||||
owner = repo1Name.owner,
|
||||
repository = repo1Name.name,
|
||||
enabled = true,
|
||||
contexts = Seq("continuous-integration/travis-ci"),
|
||||
includeAdministrators = true
|
||||
)
|
||||
)
|
||||
|
||||
val apiBranch = ApiBranch(
|
||||
name = "master",
|
||||
commit = ApiBranchCommit(sha1),
|
||||
protection = apiBranchProtection
|
||||
)(
|
||||
repositoryName = repo1Name
|
||||
)
|
||||
|
||||
val apiBranchForList = ApiBranchForList(
|
||||
name = "master",
|
||||
commit = ApiBranchCommit(sha1)
|
||||
)
|
||||
|
||||
val apiContents = ApiContents(
|
||||
fileInfo = FileInfo(
|
||||
id = ObjectId.fromString(sha1),
|
||||
isDirectory = false,
|
||||
name = "README.md",
|
||||
path = "doc/README.md",
|
||||
message = "message",
|
||||
commitId = sha1,
|
||||
time = date1,
|
||||
author = account.userName,
|
||||
mailAddress = account.mailAddress,
|
||||
linkUrl = None
|
||||
),
|
||||
repositoryName = repo1Name,
|
||||
content = Some("README".getBytes("UTF-8"))
|
||||
)
|
||||
|
||||
val apiEndPoint = ApiEndPoint()
|
||||
|
||||
val apiError = ApiError(
|
||||
message = "A repository with this name already exists on this account",
|
||||
documentation_url = Some("https://developer.github.com/v3/repos/#create")
|
||||
)
|
||||
|
||||
val apiGroup = ApiGroup(
|
||||
account.copy(
|
||||
isAdmin = true,
|
||||
isGroupAccount = true,
|
||||
description = Some("Admin group")
|
||||
)
|
||||
)
|
||||
|
||||
val apiPlugin = ApiPlugin(
|
||||
plugin = PluginInfo(
|
||||
pluginId = "gist",
|
||||
pluginName = "Gist Plugin",
|
||||
pluginVersion = "4.16.0",
|
||||
gitbucketVersion = Some("4.30.1"),
|
||||
description = "Provides Gist feature on GitBucket.",
|
||||
pluginClass = null,
|
||||
pluginJar = new java.io.File("gitbucket-gist-plugin-gitbucket_4.30.0-SNAPSHOT-4.17.0.jar"),
|
||||
classLoader = null
|
||||
)
|
||||
)
|
||||
|
||||
val apiPusher = ApiPusher(account)
|
||||
|
||||
val apiRef = ApiRef(
|
||||
ref = "refs/heads/featureA",
|
||||
`object` = ApiObject(sha1)
|
||||
)
|
||||
|
||||
val assetFileName = "010203040a0b0c0d"
|
||||
|
||||
val apiReleaseAsset = ApiReleaseAsset(
|
||||
name = "release.zip",
|
||||
size = 100
|
||||
)(
|
||||
tag = "tag1",
|
||||
fileName = assetFileName,
|
||||
repositoryName = repo1Name
|
||||
)
|
||||
|
||||
val apiRelease = ApiRelease(
|
||||
name = "release1",
|
||||
tag_name = "tag1",
|
||||
body = Some("content"),
|
||||
author = apiUser,
|
||||
assets = Seq(apiReleaseAsset)
|
||||
)
|
||||
|
||||
// JSON String for APIs
|
||||
|
||||
val jsonUser = """{
|
||||
|"login":"octocat",
|
||||
|"email":"octocat@example.com",
|
||||
|"type":"User",
|
||||
|"site_admin":false,
|
||||
|"created_at":"2011-04-14T16:00:49Z",
|
||||
|"id":0,
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/users/octocat",
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat",
|
||||
|"avatar_url":"http://gitbucket.exmple.com/octocat/_avatar"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonRepository = s"""{
|
||||
|"name":"Hello-World",
|
||||
|"full_name":"octocat/Hello-World",
|
||||
|"description":"This your first repo!",
|
||||
|"watchers":0,
|
||||
|"forks":1,
|
||||
|"private":false,
|
||||
|"default_branch":"master",
|
||||
|"owner":$jsonUser,
|
||||
|"id":0,
|
||||
|"forks_count":1,
|
||||
|"watchers_count":0,
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World",
|
||||
|"http_url":"http://gitbucket.exmple.com/git/octocat/Hello-World.git",
|
||||
|"clone_url":"http://gitbucket.exmple.com/git/octocat/Hello-World.git",
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonLabel =
|
||||
"""{"name":"bug","color":"f29513","url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/labels/bug"}"""
|
||||
|
||||
val jsonIssue = s"""{
|
||||
|"number":1347,
|
||||
|"title":"Found a bug",
|
||||
|"user":$jsonUser,
|
||||
|"labels":[$jsonLabel],
|
||||
|"state":"open",
|
||||
|"created_at":"2011-04-14T16:00:49Z",
|
||||
|"updated_at":"2011-04-14T16:00:49Z",
|
||||
|"body":"I'm having a problem with this.",
|
||||
|"id":0,
|
||||
|"comments_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/issues/1347/comments",
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/issues/1347"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonIssuePR = s"""{
|
||||
|"number":1347,
|
||||
|"title":"new-feature",
|
||||
|"user":$jsonUser,
|
||||
|"labels":[$jsonLabel],
|
||||
|"state":"closed",
|
||||
|"created_at":"2011-04-14T16:00:49Z",
|
||||
|"updated_at":"2011-04-14T16:00:49Z",
|
||||
|"body":"Please pull these awesome changes",
|
||||
|"id":0,
|
||||
|"comments_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/issues/1347/comments",
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/pull/1347",
|
||||
|"pull_request":{
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/pulls/1347",
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/pull/1347"}
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonPullRequest = s"""{
|
||||
|"number":1347,
|
||||
|"state":"closed",
|
||||
|"updated_at":"2011-04-14T16:00:49Z",
|
||||
|"created_at":"2011-04-14T16:00:49Z",
|
||||
|"head":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e","ref":"new-topic","repo":$jsonRepository,"label":"new-topic","user":$jsonUser},
|
||||
|"base":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e","ref":"master","repo":$jsonRepository,"label":"master","user":$jsonUser},
|
||||
|"merged":true,
|
||||
|"merged_at":"2011-04-14T16:00:49Z",
|
||||
|"merged_by":$jsonUser,
|
||||
|"title":"new-feature",
|
||||
|"body":"Please pull these awesome changes",
|
||||
|"user":$jsonUser,
|
||||
|"labels":[$jsonLabel],
|
||||
|"assignee":$jsonUser,
|
||||
|"id":0,
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/pull/1347",
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/pulls/1347",
|
||||
|"commits_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/pulls/1347/commits",
|
||||
|"review_comments_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/pulls/1347/comments",
|
||||
|"review_comment_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/pulls/comments/{number}",
|
||||
|"comments_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/issues/1347/comments",
|
||||
|"statuses_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonPullRequestReviewComment = s"""{
|
||||
|"id":29724692,
|
||||
|"path":"README.md",
|
||||
|"commit_id":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
|"user":$jsonUser,
|
||||
|"body":"Maybe you should use more emoji on this line.",
|
||||
|"created_at":"2015-05-05T23:40:27Z",
|
||||
|"updated_at":"2015-05-05T23:40:27Z",
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/pulls/comments/29724692",
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/pull/1347#discussion_r29724692",
|
||||
|"pull_request_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/pulls/1347",
|
||||
|"_links":{
|
||||
|"self":{"href":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/pulls/comments/29724692"},
|
||||
|"html":{"href":"http://gitbucket.exmple.com/octocat/Hello-World/pull/1347#discussion_r29724692"},
|
||||
|"pull_request":{"href":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/pulls/1347"}}
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonComment = s"""{
|
||||
|"id":1,
|
||||
|"user":$jsonUser,
|
||||
|"body":"Me too",
|
||||
|"created_at":"2011-04-14T16:00:49Z",
|
||||
|"updated_at":"2011-04-14T16:00:49Z",
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/issues/1347#comment-1"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonCommentPR = s"""{
|
||||
|"id":1,
|
||||
|"user":$jsonUser,
|
||||
|"body":"Me too",
|
||||
|"created_at":"2011-04-14T16:00:49Z",
|
||||
|"updated_at":"2011-04-14T16:00:49Z",
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/pull/1347#comment-1"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonCommitListItem = s"""{
|
||||
|"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
|"commit":{
|
||||
|"message":"full message",
|
||||
|"author":{"name":"octocat","email":"octocat@example.com","date":"2011-04-14T16:00:49Z"},
|
||||
|"committer":{"name":"octocat","email":"octocat@example.com","date":"2011-04-14T16:00:49Z"},
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/git/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e"
|
||||
|},
|
||||
|"parents":[{
|
||||
|"sha":"1da452aa92d7db1bc093d266c80a69857718c406",
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/commits/1da452aa92d7db1bc093d266c80a69857718c406"}],
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonCommit = (id: String) => s"""{
|
||||
|"id":"$id",
|
||||
|"message":"full message",
|
||||
|"timestamp":"2011-04-14T16:00:49Z",
|
||||
|"added":[],
|
||||
|"removed":[],
|
||||
|"modified":["README.md"],
|
||||
|"author":{"name":"octocat","email":"octocat@example.com","date":"2011-04-14T16:00:49Z"},
|
||||
|"committer":{"name":"octocat","email":"octocat@example.com","date":"2011-04-14T16:00:49Z"},
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/octocat/Hello-World/commits/$id",
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/commit/$id"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonCommits = s"""{
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
|"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
|"html_url":"http://gitbucket.exmple.comoctocat/Hello-World/commit/6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
|"comment_url":"http://gitbucket.exmple.com",
|
||||
|"commit":{
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
|"author":{"name":"octocat","email":"octocat@example.com","date":"2011-04-14T16:00:49Z"},
|
||||
|"committer":{"name":"octocat","email":"octocat@example.com","date":"2011-04-14T16:00:49Z"},
|
||||
|"message":"short message",
|
||||
|"comment_count":2,
|
||||
|"tree":{"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/tree/6dcb09b5b57875f334f61aebed695e2e4193db5e","sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}
|
||||
|},
|
||||
|"author":$jsonUser,
|
||||
|"committer":$jsonUser,
|
||||
|"parents":[{
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/tree/1da452aa92d7db1bc093d266c80a69857718c406",
|
||||
|"sha":"1da452aa92d7db1bc093d266c80a69857718c406"}],
|
||||
|"stats":{"additions":2,"deletions":1,"total":3},
|
||||
|"files":[{
|
||||
|"filename":"doc/README.md",
|
||||
|"additions":2,
|
||||
|"deletions":1,
|
||||
|"changes":3,
|
||||
|"status":"modified",
|
||||
|"raw_url":"http://gitbucket.exmple.com/octocat/Hello-World/raw/6dcb09b5b57875f334f61aebed695e2e4193db5e/doc/README.md",
|
||||
|"blob_url":"http://gitbucket.exmple.com/octocat/Hello-World/blob/6dcb09b5b57875f334f61aebed695e2e4193db5e/doc/README.md",
|
||||
|"patch":"@@ -1 +1,2 @@\\n-body1\\n\\\\ No newline at end of file\\n+body1\\n+body2\\n\\\\ No newline at end of file"}]
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonCommitStatus = s"""{
|
||||
|"created_at":"2011-04-14T16:00:49Z",
|
||||
|"updated_at":"2011-04-14T16:00:49Z",
|
||||
|"state":"success",
|
||||
|"target_url":"https://ci.example.com/1000/output",
|
||||
|"description":"Build has completed successfully",
|
||||
|"id":1,
|
||||
|"context":"Default",
|
||||
|"creator":$jsonUser,
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e/statuses"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonCombinedCommitStatus = s"""{
|
||||
|"state":"success",
|
||||
|"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
|"total_count":1,
|
||||
|"statuses":[$jsonCommitStatus],
|
||||
|"repository":$jsonRepository,
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e/status"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonBranchProtection =
|
||||
"""{
|
||||
|"enabled":true,
|
||||
|"required_status_checks":{"enforcement_level":"everyone","contexts":["continuous-integration/travis-ci"]}
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonBranch = s"""{
|
||||
|"name":"master",
|
||||
|"commit":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"},
|
||||
|"protection":$jsonBranchProtection,
|
||||
|"_links":{
|
||||
|"self":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/master",
|
||||
|"html":"http://gitbucket.exmple.com/octocat/Hello-World/tree/master"}
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonBranchForList = """{"name":"master","commit":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
|
||||
|
||||
val jsonContents =
|
||||
"""{
|
||||
|"type":"file",
|
||||
|"name":"README.md",
|
||||
|"path":"doc/README.md",
|
||||
|"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
|"content":"UkVBRE1F",
|
||||
|"encoding":"base64",
|
||||
|"download_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/raw/6dcb09b5b57875f334f61aebed695e2e4193db5e/doc/README.md"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonEndPoint = """{"rate_limit_url":"http://gitbucket.exmple.com/api/v3/rate_limit"}"""
|
||||
|
||||
val jsonError = """{
|
||||
|"message":"A repository with this name already exists on this account",
|
||||
|"documentation_url":"https://developer.github.com/v3/repos/#create"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonGroup = """{
|
||||
|"login":"octocat",
|
||||
|"description":"Admin group",
|
||||
|"created_at":"2011-04-14T16:00:49Z",
|
||||
|"id":0,
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/orgs/octocat",
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat",
|
||||
|"avatar_url":"http://gitbucket.exmple.com/octocat/_avatar"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonPlugin = """{
|
||||
|"id":"gist",
|
||||
|"name":"Gist Plugin",
|
||||
|"version":"4.16.0",
|
||||
|"description":"Provides Gist feature on GitBucket.",
|
||||
|"jarFileName":"gitbucket-gist-plugin-gitbucket_4.30.0-SNAPSHOT-4.17.0.jar"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonPusher = """{"name":"octocat","email":"octocat@example.com"}"""
|
||||
|
||||
val jsonRef = """{"ref":"refs/heads/featureA","object":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
|
||||
|
||||
val jsonReleaseAsset =
|
||||
s"""{
|
||||
|"name":"release.zip",
|
||||
|"size":100,
|
||||
|"label":"release.zip",
|
||||
|"file_id":"${assetFileName}",
|
||||
|"browser_download_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/releases/tag1/assets/${assetFileName}"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonRelease =
|
||||
s"""{
|
||||
|"name":"release1",
|
||||
|"tag_name":"tag1",
|
||||
|"body":"content",
|
||||
|"author":${jsonUser},
|
||||
|"assets":[${jsonReleaseAsset}]
|
||||
|}""".stripMargin
|
||||
}
|
||||
@@ -1,448 +1,82 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.util.RepositoryName
|
||||
|
||||
import org.json4s.jackson.JsonMethods.parse
|
||||
import org.json4s._
|
||||
import org.scalatest.FunSuite
|
||||
|
||||
import java.util.{Calendar, TimeZone, Date}
|
||||
|
||||
class JsonFormatSpec extends FunSuite {
|
||||
val date1 = {
|
||||
val d = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
|
||||
d.set(2011, 3, 14, 16, 0, 49)
|
||||
d.getTime
|
||||
}
|
||||
def date(date: String): Date = {
|
||||
val f = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
|
||||
f.setTimeZone(TimeZone.getTimeZone("UTC"))
|
||||
f.parse(date)
|
||||
}
|
||||
val sha1 = "6dcb09b5b57875f334f61aebed695e2e4193db5e"
|
||||
val repo1Name = RepositoryName("octocat/Hello-World")
|
||||
implicit val context = JsonFormat.Context("http://gitbucket.exmple.com", None)
|
||||
import ApiSpecModels._
|
||||
|
||||
val apiUser =
|
||||
ApiUser(login = "octocat", email = "octocat@example.com", `type` = "User", site_admin = false, created_at = date1)
|
||||
val apiUserJson = """{
|
||||
"login":"octocat",
|
||||
"email":"octocat@example.com",
|
||||
"type":"User",
|
||||
"site_admin":false,
|
||||
"id": 0,
|
||||
"created_at":"2011-04-14T16:00:49Z",
|
||||
"url":"http://gitbucket.exmple.com/api/v3/users/octocat",
|
||||
"html_url":"http://gitbucket.exmple.com/octocat",
|
||||
"avatar_url":"http://gitbucket.exmple.com/octocat/_avatar"
|
||||
}"""
|
||||
|
||||
val repository = ApiRepository(
|
||||
name = repo1Name.name,
|
||||
full_name = repo1Name.fullName,
|
||||
description = "This your first repo!",
|
||||
watchers = 0,
|
||||
forks = 0,
|
||||
`private` = false,
|
||||
default_branch = "master",
|
||||
owner = apiUser
|
||||
)(urlIsHtmlUrl = false)
|
||||
val repositoryJson = s"""{
|
||||
"name" : "Hello-World",
|
||||
"full_name" : "octocat/Hello-World",
|
||||
"description" : "This your first repo!",
|
||||
"id": 0,
|
||||
"watchers" : 0,
|
||||
"forks" : 0,
|
||||
"private" : false,
|
||||
"default_branch" : "master",
|
||||
"owner" : $apiUserJson,
|
||||
"forks_count" : 0,
|
||||
"watchers_count" : 0,
|
||||
"url" : "${context.baseUrl}/api/v3/repos/octocat/Hello-World",
|
||||
"http_url" : "${context.baseUrl}/git/octocat/Hello-World.git",
|
||||
"clone_url" : "${context.baseUrl}/git/octocat/Hello-World.git",
|
||||
"html_url" : "${context.baseUrl}/octocat/Hello-World"
|
||||
}"""
|
||||
|
||||
val apiCommitStatus = ApiCommitStatus(
|
||||
created_at = date1,
|
||||
updated_at = date1,
|
||||
state = "success",
|
||||
target_url = Some("https://ci.example.com/1000/output"),
|
||||
description = Some("Build has completed successfully"),
|
||||
id = 1,
|
||||
context = "Default",
|
||||
creator = apiUser
|
||||
)(sha1, repo1Name)
|
||||
val apiCommitStatusJson = s"""{
|
||||
"created_at":"2011-04-14T16:00:49Z",
|
||||
"updated_at":"2011-04-14T16:00:49Z",
|
||||
"state":"success",
|
||||
"target_url":"https://ci.example.com/1000/output",
|
||||
"description":"Build has completed successfully",
|
||||
"id":1,
|
||||
"context":"Default",
|
||||
"creator":$apiUserJson,
|
||||
"url": "http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e/statuses"
|
||||
}"""
|
||||
|
||||
val apiPushCommit = ApiCommit(
|
||||
id = "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c",
|
||||
message = "Update README.md",
|
||||
timestamp = date1,
|
||||
added = Nil,
|
||||
removed = Nil,
|
||||
modified = List("README.md"),
|
||||
author = ApiPersonIdent("baxterthehacker", "baxterthehacker@users.noreply.github.com", date1),
|
||||
committer = ApiPersonIdent("baxterthehacker", "baxterthehacker@users.noreply.github.com", date1)
|
||||
)(RepositoryName("baxterthehacker", "public-repo"), true)
|
||||
val apiPushCommitJson = s"""{
|
||||
"id": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c",
|
||||
// "distinct": true,
|
||||
"message": "Update README.md",
|
||||
"timestamp": "2011-04-14T16:00:49Z",
|
||||
"url": "http://gitbucket.exmple.com/baxterthehacker/public-repo/commit/0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c",
|
||||
"author": {
|
||||
"name": "baxterthehacker",
|
||||
"email": "baxterthehacker@users.noreply.github.com",
|
||||
// "username": "baxterthehacker",
|
||||
"date" : "2011-04-14T16:00:49Z"
|
||||
},
|
||||
"committer": {
|
||||
"name": "baxterthehacker",
|
||||
"email": "baxterthehacker@users.noreply.github.com",
|
||||
// "username": "baxterthehacker",
|
||||
"date" : "2011-04-14T16:00:49Z"
|
||||
},
|
||||
"added": [
|
||||
|
||||
],
|
||||
"removed": [
|
||||
|
||||
],
|
||||
"modified": [
|
||||
"README.md"
|
||||
]
|
||||
}"""
|
||||
|
||||
val apiComment = ApiComment(id = 1, user = apiUser, body = "Me too", created_at = date1, updated_at = date1)(
|
||||
RepositoryName("octocat", "Hello-World"),
|
||||
100,
|
||||
false
|
||||
)
|
||||
val apiCommentJson = s"""{
|
||||
"id": 1,
|
||||
"body": "Me too",
|
||||
"user": $apiUserJson,
|
||||
"html_url" : "${context.baseUrl}/octocat/Hello-World/issues/100#comment-1",
|
||||
"created_at": "2011-04-14T16:00:49Z",
|
||||
"updated_at": "2011-04-14T16:00:49Z"
|
||||
}"""
|
||||
|
||||
val apiCommentPR = ApiComment(id = 1, user = apiUser, body = "Me too", created_at = date1, updated_at = date1)(
|
||||
RepositoryName("octocat", "Hello-World"),
|
||||
100,
|
||||
true
|
||||
)
|
||||
val apiCommentPRJson = s"""{
|
||||
"id": 1,
|
||||
"body": "Me too",
|
||||
"user": $apiUserJson,
|
||||
"html_url" : "${context.baseUrl}/octocat/Hello-World/pull/100#comment-1",
|
||||
"created_at": "2011-04-14T16:00:49Z",
|
||||
"updated_at": "2011-04-14T16:00:49Z"
|
||||
}"""
|
||||
|
||||
val apiPersonIdent = ApiPersonIdent("Monalisa Octocat", "support@example.com", date1)
|
||||
val apiPersonIdentJson = """ {
|
||||
"name": "Monalisa Octocat",
|
||||
"email": "support@example.com",
|
||||
"date": "2011-04-14T16:00:49Z"
|
||||
}"""
|
||||
|
||||
val apiCommitListItem = ApiCommitListItem(
|
||||
sha = sha1,
|
||||
commit = ApiCommitListItem.Commit(
|
||||
message = "Fix all the bugs",
|
||||
author = apiPersonIdent,
|
||||
committer = apiPersonIdent
|
||||
)(sha1, repo1Name),
|
||||
author = Some(apiUser),
|
||||
committer = Some(apiUser),
|
||||
parents = Seq(ApiCommitListItem.Parent("6dcb09b5b57875f334f61aebed695e2e4193db5e")(repo1Name))
|
||||
)(repo1Name)
|
||||
val apiCommitListItemJson = s"""{
|
||||
"url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
"sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
"commit": {
|
||||
"url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/git/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
"author": $apiPersonIdentJson,
|
||||
"committer": $apiPersonIdentJson,
|
||||
"message": "Fix all the bugs"
|
||||
},
|
||||
"author": $apiUserJson,
|
||||
"committer": $apiUserJson,
|
||||
"parents": [
|
||||
{
|
||||
"url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
"sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e"
|
||||
}
|
||||
]
|
||||
}"""
|
||||
|
||||
val apiCombinedCommitStatus = ApiCombinedCommitStatus(
|
||||
state = "success",
|
||||
sha = sha1,
|
||||
total_count = 2,
|
||||
statuses = List(apiCommitStatus),
|
||||
repository = repository
|
||||
)
|
||||
val apiCombinedCommitStatusJson = s"""{
|
||||
"state": "success",
|
||||
"sha": "$sha1",
|
||||
"total_count": 2,
|
||||
"statuses": [ $apiCommitStatusJson ],
|
||||
"repository": $repositoryJson,
|
||||
"url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/commits/$sha1/status"
|
||||
}"""
|
||||
|
||||
val apiLabel = ApiLabel(name = "bug", color = "f29513")(RepositoryName("octocat", "Hello-World"))
|
||||
val apiLabelJson = s"""{
|
||||
"name": "bug",
|
||||
"color": "f29513",
|
||||
"url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/labels/bug"
|
||||
}"""
|
||||
|
||||
val apiIssue = ApiIssue(
|
||||
number = 1347,
|
||||
title = "Found a bug",
|
||||
user = apiUser,
|
||||
labels = List(apiLabel),
|
||||
state = "open",
|
||||
body = "I'm having a problem with this.",
|
||||
created_at = date1,
|
||||
updated_at = date1
|
||||
)(RepositoryName("octocat", "Hello-World"), false)
|
||||
val apiIssueJson = s"""{
|
||||
"number": 1347,
|
||||
"state": "open",
|
||||
"title": "Found a bug",
|
||||
"body": "I'm having a problem with this.",
|
||||
"user": $apiUserJson,
|
||||
"id": 0,
|
||||
"labels": [$apiLabelJson],
|
||||
"comments_url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/issues/1347/comments",
|
||||
"html_url": "${context.baseUrl}/octocat/Hello-World/issues/1347",
|
||||
"created_at": "2011-04-14T16:00:49Z",
|
||||
"updated_at": "2011-04-14T16:00:49Z"
|
||||
}"""
|
||||
|
||||
val apiIssuePR = ApiIssue(
|
||||
number = 1347,
|
||||
title = "Found a bug",
|
||||
user = apiUser,
|
||||
labels = List(apiLabel),
|
||||
state = "open",
|
||||
body = "I'm having a problem with this.",
|
||||
created_at = date1,
|
||||
updated_at = date1
|
||||
)(RepositoryName("octocat", "Hello-World"), true)
|
||||
val apiIssuePRJson = s"""{
|
||||
"number": 1347,
|
||||
"state": "open",
|
||||
"title": "Found a bug",
|
||||
"body": "I'm having a problem with this.",
|
||||
"user": $apiUserJson,
|
||||
"id": 0,
|
||||
"labels": [$apiLabelJson],
|
||||
"comments_url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/issues/1347/comments",
|
||||
"html_url": "${context.baseUrl}/octocat/Hello-World/pull/1347",
|
||||
"pull_request": {
|
||||
"url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/pulls/1347",
|
||||
"html_url": "${context.baseUrl}/octocat/Hello-World/pull/1347"
|
||||
// "diff_url": "${context.baseUrl}/octocat/Hello-World/pull/1347.diff",
|
||||
// "patch_url": "${context.baseUrl}/octocat/Hello-World/pull/1347.patch"
|
||||
},
|
||||
"created_at": "2011-04-14T16:00:49Z",
|
||||
"updated_at": "2011-04-14T16:00:49Z"
|
||||
}"""
|
||||
|
||||
val apiPullRequest = ApiPullRequest(
|
||||
number = 1347,
|
||||
state = "open",
|
||||
updated_at = date1,
|
||||
created_at = date1,
|
||||
head = ApiPullRequest.Commit(sha = sha1, ref = "new-topic", repo = repository)("octocat"),
|
||||
base = ApiPullRequest.Commit(sha = sha1, ref = "master", repo = repository)("octocat"),
|
||||
mergeable = None,
|
||||
merged = false,
|
||||
merged_at = Some(date1),
|
||||
merged_by = Some(apiUser),
|
||||
title = "new-feature",
|
||||
body = "Please pull these awesome changes",
|
||||
user = apiUser,
|
||||
labels = List(apiLabel),
|
||||
assignee = Some(apiUser)
|
||||
)
|
||||
|
||||
val apiPullRequestJson = s"""{
|
||||
"number": 1347,
|
||||
"state" : "open",
|
||||
"id": 0,
|
||||
"updated_at": "2011-04-14T16:00:49Z",
|
||||
"created_at": "2011-04-14T16:00:49Z",
|
||||
// "closed_at": "2011-04-14T16:00:49Z",
|
||||
"head": {
|
||||
"sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
"ref": "new-topic",
|
||||
"repo": $repositoryJson,
|
||||
"label": "new-topic",
|
||||
"user": $apiUserJson
|
||||
},
|
||||
"base": {
|
||||
"sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
"ref": "master",
|
||||
"repo": $repositoryJson,
|
||||
"label": "master",
|
||||
"user": $apiUserJson
|
||||
},
|
||||
// "merge_commit_sha": "e5bd3914e2e596debea16f433f57875b5b90bcd6",
|
||||
// "mergeable": true,
|
||||
"merged": false,
|
||||
"merged_at": "2011-04-14T16:00:49Z",
|
||||
"merged_by": $apiUserJson,
|
||||
"title": "new-feature",
|
||||
"body": "Please pull these awesome changes",
|
||||
"user": $apiUserJson,
|
||||
"assignee": $apiUserJson,
|
||||
"labels": [$apiLabelJson],
|
||||
"html_url": "${context.baseUrl}/octocat/Hello-World/pull/1347",
|
||||
"url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/pulls/1347",
|
||||
"commits_url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/pulls/1347/commits",
|
||||
"review_comments_url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/pulls/1347/comments",
|
||||
"review_comment_url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/pulls/comments/{number}",
|
||||
"comments_url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/issues/1347/comments",
|
||||
"statuses_url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e"
|
||||
// "diff_url": "${context.baseUrl}/octocat/Hello-World/pull/1347.diff",
|
||||
// "patch_url": "${context.baseUrl}/octocat/Hello-World/pull/1347.patch",
|
||||
// "issue_url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/issues/1347",
|
||||
// "state": "open",
|
||||
// "comments": 10,
|
||||
// "commits": 3,
|
||||
// "additions": 100,
|
||||
// "deletions": 3,
|
||||
// "changed_files": 5
|
||||
}"""
|
||||
|
||||
// https://developer.github.com/v3/activity/events/types/#pullrequestreviewcommentevent
|
||||
val apiPullRequestReviewComment = ApiPullRequestReviewComment(
|
||||
id = 29724692,
|
||||
// "diff_hunk": "@@ -1 +1 @@\n-# public-repo",
|
||||
path = "README.md",
|
||||
// "position": 1,
|
||||
// "original_position": 1,
|
||||
commit_id = "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c",
|
||||
// "original_commit_id": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c",
|
||||
user = apiUser,
|
||||
body = "Maybe you should use more emoji on this line.",
|
||||
created_at = date("2015-05-05T23:40:27Z"),
|
||||
updated_at = date("2015-05-05T23:40:27Z")
|
||||
)(RepositoryName("baxterthehacker/public-repo"), 1)
|
||||
val apiPullRequestReviewCommentJson = s"""{
|
||||
"url": "http://gitbucket.exmple.com/api/v3/repos/baxterthehacker/public-repo/pulls/comments/29724692",
|
||||
"id": 29724692,
|
||||
// "diff_hunk": "@@ -1 +1 @@\\n-# public-repo",
|
||||
"path": "README.md",
|
||||
// "position": 1,
|
||||
// "original_position": 1,
|
||||
"commit_id": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c",
|
||||
// "original_commit_id": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c",
|
||||
"user": $apiUserJson,
|
||||
"body": "Maybe you should use more emoji on this line.",
|
||||
"created_at": "2015-05-05T23:40:27Z",
|
||||
"updated_at": "2015-05-05T23:40:27Z",
|
||||
"html_url": "http://gitbucket.exmple.com/baxterthehacker/public-repo/pull/1#discussion_r29724692",
|
||||
"pull_request_url": "http://gitbucket.exmple.com/api/v3/repos/baxterthehacker/public-repo/pulls/1",
|
||||
"_links": {
|
||||
"self": {
|
||||
"href": "http://gitbucket.exmple.com/api/v3/repos/baxterthehacker/public-repo/pulls/comments/29724692"
|
||||
},
|
||||
"html": {
|
||||
"href": "http://gitbucket.exmple.com/baxterthehacker/public-repo/pull/1#discussion_r29724692"
|
||||
},
|
||||
"pull_request": {
|
||||
"href": "http://gitbucket.exmple.com/api/v3/repos/baxterthehacker/public-repo/pulls/1"
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
||||
val apiBranchProtection = ApiBranchProtection(
|
||||
true,
|
||||
Some(ApiBranchProtection.Status(ApiBranchProtection.Everyone, Seq("continuous-integration/travis-ci")))
|
||||
)
|
||||
val apiBranchProtectionJson = """{
|
||||
"enabled": true,
|
||||
"required_status_checks": {
|
||||
"enforcement_level": "everyone",
|
||||
"contexts": [
|
||||
"continuous-integration/travis-ci"
|
||||
]
|
||||
}
|
||||
}"""
|
||||
|
||||
def assertJson(resultJson: String, expectJson: String) = {
|
||||
import java.util.regex.Pattern
|
||||
val json2 = Pattern.compile("""^\s*//.*$""", Pattern.MULTILINE).matcher(expectJson).replaceAll("")
|
||||
val js2 = try {
|
||||
parse(json2)
|
||||
} catch {
|
||||
case e: com.fasterxml.jackson.core.JsonParseException => {
|
||||
val p = java.lang.Math.max(e.getLocation.getCharOffset() - 10, 0).toInt
|
||||
val message = json2.substring(p, java.lang.Math.min(p + 100, json2.length))
|
||||
throw new com.fasterxml.jackson.core.JsonParseException(e.getProcessor, message + e.getMessage)
|
||||
}
|
||||
}
|
||||
val js1 = parse(resultJson)
|
||||
assert(js1 === js2)
|
||||
}
|
||||
private def expected(json: String) = json.replaceAll("\n", "")
|
||||
|
||||
test("apiUser") {
|
||||
assertJson(JsonFormat(apiUser), apiUserJson)
|
||||
assert(JsonFormat(apiUser) == expected(jsonUser))
|
||||
}
|
||||
test("repository") {
|
||||
assertJson(JsonFormat(repository), repositoryJson)
|
||||
test("apiRepository") {
|
||||
assert(JsonFormat(apiRepository) == expected(jsonRepository))
|
||||
}
|
||||
test("apiPushCommit") {
|
||||
assertJson(JsonFormat(apiPushCommit), apiPushCommitJson)
|
||||
test("apiCommit") {
|
||||
assert(JsonFormat(apiCommit) == expected(jsonCommit(sha1)))
|
||||
}
|
||||
test("apiComment") {
|
||||
assertJson(JsonFormat(apiComment), apiCommentJson)
|
||||
assertJson(JsonFormat(apiCommentPR), apiCommentPRJson)
|
||||
assert(JsonFormat(apiComment) == expected(jsonComment))
|
||||
assert(JsonFormat(apiCommentPR) == expected(jsonCommentPR))
|
||||
}
|
||||
test("apiCommitListItem") {
|
||||
assertJson(JsonFormat(apiCommitListItem), apiCommitListItemJson)
|
||||
assert(JsonFormat(apiCommitListItem) == expected(jsonCommitListItem))
|
||||
}
|
||||
test("apiCommitStatus") {
|
||||
assertJson(JsonFormat(apiCommitStatus), apiCommitStatusJson)
|
||||
assert(JsonFormat(apiCommitStatus) == expected(jsonCommitStatus))
|
||||
}
|
||||
test("apiCombinedCommitStatus") {
|
||||
assertJson(JsonFormat(apiCombinedCommitStatus), apiCombinedCommitStatusJson)
|
||||
assert(JsonFormat(apiCombinedCommitStatus) == expected(jsonCombinedCommitStatus))
|
||||
}
|
||||
test("apiLabel") {
|
||||
assertJson(JsonFormat(apiLabel), apiLabelJson)
|
||||
assert(JsonFormat(apiLabel) == expected(jsonLabel))
|
||||
}
|
||||
test("apiIssue") {
|
||||
assertJson(JsonFormat(apiIssue), apiIssueJson)
|
||||
assertJson(JsonFormat(apiIssuePR), apiIssuePRJson)
|
||||
assert(JsonFormat(apiIssue) == expected(jsonIssue))
|
||||
assert(JsonFormat(apiIssuePR) == expected(jsonIssuePR))
|
||||
}
|
||||
test("apiPullRequest") {
|
||||
assertJson(JsonFormat(apiPullRequest), apiPullRequestJson)
|
||||
assert(JsonFormat(apiPullRequest) == expected(jsonPullRequest))
|
||||
}
|
||||
test("apiPullRequestReviewComment") {
|
||||
assertJson(JsonFormat(apiPullRequestReviewComment), apiPullRequestReviewCommentJson)
|
||||
assert(JsonFormat(apiPullRequestReviewComment) == expected(jsonPullRequestReviewComment))
|
||||
}
|
||||
test("apiBranchProtection") {
|
||||
assertJson(JsonFormat(apiBranchProtection), apiBranchProtectionJson)
|
||||
assert(JsonFormat(apiBranchProtection) == expected(jsonBranchProtection))
|
||||
}
|
||||
test("apiBranch") {
|
||||
assert(JsonFormat(apiBranch) == expected(jsonBranch))
|
||||
assert(JsonFormat(apiBranchForList) == expected(jsonBranchForList))
|
||||
}
|
||||
test("apiCommits") {
|
||||
assert(JsonFormat(apiCommits) == expected(jsonCommits))
|
||||
}
|
||||
test("apiContents") {
|
||||
assert(JsonFormat(apiContents) == expected(jsonContents))
|
||||
}
|
||||
test("apiEndPoint") {
|
||||
assert(JsonFormat(apiEndPoint) == expected(jsonEndPoint))
|
||||
}
|
||||
test("apiError") {
|
||||
assert(JsonFormat(apiError) == expected(jsonError))
|
||||
}
|
||||
test("apiGroup") {
|
||||
assert(JsonFormat(apiGroup) == expected(jsonGroup))
|
||||
}
|
||||
test("apiPlugin") {
|
||||
assert(JsonFormat(apiPlugin) == expected(jsonPlugin))
|
||||
}
|
||||
test("apiPusher") {
|
||||
assert(JsonFormat(apiPusher) == expected(jsonPusher))
|
||||
}
|
||||
test("apiRef") {
|
||||
assert(JsonFormat(apiRef) == expected(jsonRef))
|
||||
}
|
||||
test("apiReleaseAsset") {
|
||||
assert(JsonFormat(apiReleaseAsset) == expected(jsonReleaseAsset))
|
||||
}
|
||||
test("apiRelease") {
|
||||
assert(JsonFormat(apiRelease) == expected(jsonRelease))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class IssuesServiceSpec extends FunSuite with ServiceSpecBase {
|
||||
|
||||
assert(getCommitStatues(1) == None)
|
||||
|
||||
val (is2, pr2) = generateNewPullRequest("user1/repo1/master", "user1/repo1/feature1")
|
||||
val (is2, pr2) = generateNewPullRequest("user1/repo1/master", "user1/repo1/feature1", loginUser = "root")
|
||||
assert(pr2.issueId == 2)
|
||||
|
||||
// if there are no statuses, state is none
|
||||
@@ -79,7 +79,7 @@ class IssuesServiceSpec extends FunSuite with ServiceSpecBase {
|
||||
assert(getCommitStatues(2) == Some(CommitStatusInfo(2, 1, None, None, None, None)))
|
||||
|
||||
// get only statuses in query issues
|
||||
val (is3, pr3) = generateNewPullRequest("user1/repo1/master", "user1/repo1/feature3")
|
||||
val (is3, pr3) = generateNewPullRequest("user1/repo1/master", "user1/repo1/feature3", loginUser = "root")
|
||||
val cs4 = dummyService.createCommitStatus(
|
||||
"user1",
|
||||
"repo1",
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.scalatest.FunSpec
|
||||
class PullRequestServiceSpec
|
||||
extends FunSpec
|
||||
with ServiceSpecBase
|
||||
with MergeService
|
||||
with PullRequestService
|
||||
with IssuesService
|
||||
with AccountService
|
||||
@@ -30,13 +31,13 @@ class PullRequestServiceSpec
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
generateNewUserWithDBRepository("user1", "repo2")
|
||||
generateNewUserWithDBRepository("user2", "repo1")
|
||||
generateNewPullRequest("user1/repo1/master", "user1/repo1/head2") // not target branch
|
||||
generateNewPullRequest("user1/repo1/head1", "user1/repo1/master") // not target branch ( swap from, to )
|
||||
generateNewPullRequest("user1/repo1/master", "user2/repo1/head1") // other user
|
||||
generateNewPullRequest("user1/repo1/master", "user1/repo2/head1") // other repository
|
||||
val r1 = swap(generateNewPullRequest("user1/repo1/master2", "user1/repo1/head1"))
|
||||
val r2 = swap(generateNewPullRequest("user1/repo1/master", "user1/repo1/head1"))
|
||||
val r3 = swap(generateNewPullRequest("user1/repo1/master4", "user1/repo1/head1"))
|
||||
generateNewPullRequest("user1/repo1/master", "user1/repo1/head2", loginUser = "root") // not target branch
|
||||
generateNewPullRequest("user1/repo1/head1", "user1/repo1/master", loginUser = "root") // not target branch ( swap from, to )
|
||||
generateNewPullRequest("user1/repo1/master", "user2/repo1/head1", loginUser = "root") // other user
|
||||
generateNewPullRequest("user1/repo1/master", "user1/repo2/head1", loginUser = "root") // other repository
|
||||
val r1 = swap(generateNewPullRequest("user1/repo1/master2", "user1/repo1/head1", loginUser = "root"))
|
||||
val r2 = swap(generateNewPullRequest("user1/repo1/master", "user1/repo1/head1", loginUser = "root"))
|
||||
val r3 = swap(generateNewPullRequest("user1/repo1/master4", "user1/repo1/head1", loginUser = "root"))
|
||||
assert(getPullRequestFromBranch("user1", "repo1", "head1", "master") == Some(r2))
|
||||
updateClosed("user1", "repo1", r2._1.issueId, true)
|
||||
assert(Seq(r1, r2).contains(getPullRequestFromBranch("user1", "repo1", "head1", "master").get))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.GitBucketCoreModule
|
||||
import gitbucket.core.util.{DatabaseConfig, FileUtil}
|
||||
import gitbucket.core.util.{DatabaseConfig, Directory, FileUtil, JGitUtil}
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import io.github.gitbucket.solidbase.Solidbase
|
||||
import liquibase.database.core.H2Database
|
||||
@@ -10,15 +10,53 @@ import gitbucket.core.model._
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
|
||||
import org.apache.commons.io.FileUtils
|
||||
|
||||
import java.sql.DriverManager
|
||||
import java.io.File
|
||||
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.service.SystemSettingsService.{Ssh, SystemSettings}
|
||||
import javax.servlet.http.{HttpServletRequest, HttpSession}
|
||||
import org.scalatest.mockito.MockitoSugar
|
||||
import org.mockito.Mockito._
|
||||
|
||||
import scala.util.Random
|
||||
|
||||
trait ServiceSpecBase {
|
||||
trait ServiceSpecBase extends MockitoSugar {
|
||||
|
||||
val request = mock[HttpServletRequest]
|
||||
val session = mock[HttpSession]
|
||||
when(request.getRequestURL).thenReturn(new StringBuffer("http://localhost:8080/path.html"))
|
||||
when(request.getRequestURI).thenReturn("/path.html")
|
||||
when(request.getContextPath).thenReturn("")
|
||||
when(request.getSession).thenReturn(session)
|
||||
|
||||
private def createSystemSettings() =
|
||||
SystemSettings(
|
||||
baseUrl = None,
|
||||
information = None,
|
||||
allowAccountRegistration = false,
|
||||
allowAnonymousAccess = true,
|
||||
isCreateRepoOptionPublic = true,
|
||||
gravatar = false,
|
||||
notification = false,
|
||||
activityLogLimit = None,
|
||||
ssh = Ssh(
|
||||
enabled = false,
|
||||
sshHost = None,
|
||||
sshPort = None
|
||||
),
|
||||
useSMTP = false,
|
||||
smtp = None,
|
||||
ldapAuthentication = false,
|
||||
ldap = None,
|
||||
oidcAuthentication = false,
|
||||
oidc = None,
|
||||
skinName = "skin-blue",
|
||||
showMailAddress = false,
|
||||
pluginNetworkInstall = false,
|
||||
pluginProxy = None
|
||||
)
|
||||
|
||||
def withTestDB[A](action: (Session) => A): A = {
|
||||
FileUtil.withTmpDir(new File(FileUtils.getTempDirectory(), Random.alphanumeric.take(10).mkString)) { dir =>
|
||||
@@ -44,12 +82,26 @@ trait ServiceSpecBase {
|
||||
def user(name: String)(implicit s: Session): Account = AccountService.getAccountByUserName(name).get
|
||||
|
||||
lazy val dummyService = new RepositoryService with AccountService with ActivityService with IssuesService
|
||||
with PullRequestService with CommitsService with CommitStatusService with LabelsService with MilestonesService
|
||||
with PrioritiesService with WebHookService with WebHookPullRequestService
|
||||
with WebHookPullRequestReviewCommentService {}
|
||||
with MergeService with PullRequestService with CommitsService with CommitStatusService with LabelsService
|
||||
with MilestonesService with PrioritiesService with WebHookService with WebHookPullRequestService
|
||||
with WebHookPullRequestReviewCommentService {
|
||||
override def fetchAsPullRequest(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
requestUserName: String,
|
||||
requestRepositoryName: String,
|
||||
requestBranch: String,
|
||||
issueId: Int
|
||||
): Unit = {}
|
||||
}
|
||||
|
||||
def generateNewUserWithDBRepository(userName: String, repositoryName: String)(implicit s: Session): Account = {
|
||||
val ac = AccountService.getAccountByUserName(userName).getOrElse(generateNewAccount(userName))
|
||||
val dir = Directory.getRepositoryDir(userName, repositoryName)
|
||||
if (dir.exists()) {
|
||||
FileUtils.deleteQuietly(dir)
|
||||
}
|
||||
JGitUtil.initRepository(dir)
|
||||
dummyService.insertRepository(repositoryName, userName, None, false)
|
||||
ac
|
||||
}
|
||||
@@ -70,22 +122,25 @@ trait ServiceSpecBase {
|
||||
)
|
||||
}
|
||||
|
||||
def generateNewPullRequest(base: String, request: String, loginUser: String = null)(
|
||||
def generateNewPullRequest(base: String, request: String, loginUser: String)(
|
||||
implicit s: Session
|
||||
): (Issue, PullRequest) = {
|
||||
implicit val context = Context(createSystemSettings(), None, this.request)
|
||||
val Array(baseUserName, baseRepositoryName, baesBranch) = base.split("/")
|
||||
val Array(requestUserName, requestRepositoryName, requestBranch) = request.split("/")
|
||||
val issueId = generateNewIssue(baseUserName, baseRepositoryName, Option(loginUser).getOrElse(requestUserName))
|
||||
val baseRepository = dummyService.getRepository(baseUserName, baseRepositoryName)
|
||||
val loginAccount = dummyService.getAccountByUserName(loginUser)
|
||||
dummyService.createPullRequest(
|
||||
originUserName = baseUserName,
|
||||
originRepositoryName = baseRepositoryName,
|
||||
originRepository = baseRepository.get,
|
||||
issueId = issueId,
|
||||
originBranch = baesBranch,
|
||||
requestUserName = requestUserName,
|
||||
requestRepositoryName = requestRepositoryName,
|
||||
requestBranch = requestBranch,
|
||||
commitIdFrom = baesBranch,
|
||||
commitIdTo = requestBranch
|
||||
commitIdTo = requestBranch,
|
||||
loginAccount = loginAccount.get
|
||||
)
|
||||
dummyService.getPullRequest(baseUserName, baseRepositoryName, issueId).get
|
||||
}
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.api.JsonFormat
|
||||
import gitbucket.core.service.WebHookService._
|
||||
import org.scalatest.{Assertion, FunSuite}
|
||||
|
||||
class WebHookJsonFormatSpec extends FunSuite {
|
||||
import gitbucket.core.api.ApiSpecModels._
|
||||
|
||||
private def assert(payload: WebHookPayload, expected: String): Assertion = {
|
||||
val json = JsonFormat(payload)
|
||||
assert(json == expected.replaceAll("\n", ""))
|
||||
}
|
||||
|
||||
test("WebHookCreatePayload") {
|
||||
val payload = WebHookCreatePayload(
|
||||
sender = account,
|
||||
repositoryInfo = repositoryInfo,
|
||||
repositoryOwner = account,
|
||||
ref = "v1.0",
|
||||
refType = "tag"
|
||||
)
|
||||
val expected = s"""{
|
||||
|"sender":$jsonUser,
|
||||
|"description":"This your first repo!",
|
||||
|"ref":"v1.0",
|
||||
|"ref_type":"tag",
|
||||
|"master_branch":"master",
|
||||
|"repository":$jsonRepository,
|
||||
|"pusher_type":"user"
|
||||
|}""".stripMargin
|
||||
assert(payload, expected)
|
||||
}
|
||||
|
||||
test("WebHookPushPayload") {
|
||||
import gitbucket.core.util.GitSpecUtil._
|
||||
import org.eclipse.jgit.lib.{Constants, ObjectId}
|
||||
|
||||
withTestRepository { git =>
|
||||
createFile(git, Constants.HEAD, "README.md", "body1", message = "initial")
|
||||
createFile(git, Constants.HEAD, "README.md", "body1\nbody2", message = "modified")
|
||||
|
||||
val branchId = git.getRepository.resolve("master")
|
||||
|
||||
val payload = WebHookPushPayload(
|
||||
git = git,
|
||||
sender = account,
|
||||
refName = "refs/heads/master",
|
||||
repositoryInfo = repositoryInfo,
|
||||
commits = List(commitInfo(branchId.name)),
|
||||
repositoryOwner = account,
|
||||
newId = ObjectId.fromString(sha1),
|
||||
oldId = ObjectId.fromString(sha1)
|
||||
)
|
||||
val expected = s"""{
|
||||
|"pusher":{"name":"octocat","email":"octocat@example.com"},
|
||||
|"sender":$jsonUser,
|
||||
|"ref":"refs/heads/master",
|
||||
|"before":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
|"after":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
|"commits":[${jsonCommit(branchId.name)}],
|
||||
|"repository":$jsonRepository,
|
||||
|"compare":"http://gitbucket.exmple.com/octocat/Hello-World/commit/6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
|"head_commit":${jsonCommit(branchId.name)}
|
||||
|}""".stripMargin
|
||||
assert(payload, expected)
|
||||
}
|
||||
}
|
||||
|
||||
test("WebHookIssuesPayload") {
|
||||
val payload = WebHookIssuesPayload(
|
||||
action = "edited",
|
||||
number = 1347,
|
||||
repository = apiRepository,
|
||||
issue = apiIssue,
|
||||
sender = apiUser
|
||||
)
|
||||
val expected = s"""{
|
||||
|"action":"edited",
|
||||
|"number":1347,
|
||||
|"repository":$jsonRepository,
|
||||
|"issue":$jsonIssue,
|
||||
|"sender":$jsonUser
|
||||
|}""".stripMargin
|
||||
assert(payload, expected)
|
||||
}
|
||||
|
||||
test("WebHookPullRequestPayload") {
|
||||
val payload = WebHookPullRequestPayload(
|
||||
action = "closed",
|
||||
issue = issuePR,
|
||||
issueUser = account,
|
||||
assignee = Some(account),
|
||||
pullRequest = pullRequest,
|
||||
headRepository = repositoryInfo,
|
||||
headOwner = account,
|
||||
baseRepository = repositoryInfo,
|
||||
baseOwner = account,
|
||||
labels = List(apiLabel),
|
||||
sender = account,
|
||||
mergedComment = Some((issueComment, account))
|
||||
)
|
||||
val expected = s"""{
|
||||
|"action":"closed",
|
||||
|"number":1347,
|
||||
|"repository":$jsonRepository,
|
||||
|"pull_request":$jsonPullRequest,
|
||||
|"sender":$jsonUser
|
||||
|}""".stripMargin
|
||||
assert(payload, expected)
|
||||
}
|
||||
|
||||
test("WebHookIssueCommentPayload") {
|
||||
val payload = WebHookIssueCommentPayload(
|
||||
issue = issue,
|
||||
issueUser = account,
|
||||
comment = issueComment,
|
||||
commentUser = account,
|
||||
repository = repositoryInfo,
|
||||
repositoryUser = account,
|
||||
sender = account,
|
||||
labels = List(label)
|
||||
)
|
||||
val expected = s"""{
|
||||
|"action":"created",
|
||||
|"repository":$jsonRepository,
|
||||
|"issue":$jsonIssue,
|
||||
|"comment":$jsonComment,
|
||||
|"sender":$jsonUser
|
||||
|}""".stripMargin
|
||||
assert(payload, expected)
|
||||
}
|
||||
|
||||
test("WebHookPullRequestReviewCommentPayload") {
|
||||
val payload = WebHookPullRequestReviewCommentPayload(
|
||||
action = "create",
|
||||
comment = commitComment,
|
||||
issue = issuePR,
|
||||
issueUser = account,
|
||||
assignee = Some(account),
|
||||
pullRequest = pullRequest,
|
||||
headRepository = repositoryInfo,
|
||||
headOwner = account,
|
||||
baseRepository = repositoryInfo,
|
||||
baseOwner = account,
|
||||
labels = List(apiLabel),
|
||||
sender = account,
|
||||
mergedComment = Some((issueComment, account))
|
||||
)
|
||||
val expected = s"""{
|
||||
|"action":"create",
|
||||
|"comment":$jsonPullRequestReviewComment,
|
||||
|"pull_request":$jsonPullRequest,
|
||||
|"repository":$jsonRepository,
|
||||
|"sender":$jsonUser
|
||||
|}""".stripMargin
|
||||
assert(payload, expected)
|
||||
}
|
||||
|
||||
test("WebHookGollumPayload") {
|
||||
val payload = WebHookGollumPayload(
|
||||
pages = Seq(("edited", "Home", sha1)),
|
||||
repository = repositoryInfo,
|
||||
repositoryUser = account,
|
||||
sender = account
|
||||
)
|
||||
val expected = s"""{
|
||||
|"pages":[{"page_name":"Home","title":"Home","action":"edited","sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e","html_url":"http://gitbucket.exmple.com/octocat/Hello-World/wiki/Home"}],
|
||||
|"repository":$jsonRepository,
|
||||
|"sender":$jsonUser
|
||||
|}""".stripMargin
|
||||
assert(payload, expected)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,8 +6,8 @@ import gitbucket.core.model.WebHookContentType
|
||||
|
||||
class WebHookServiceSpec extends FunSuite with ServiceSpecBase {
|
||||
lazy val service = new WebHookPullRequestService with AccountService with ActivityService with RepositoryService
|
||||
with PullRequestService with IssuesService with CommitsService with LabelsService with MilestonesService
|
||||
with PrioritiesService with WebHookPullRequestReviewCommentService
|
||||
with MergeService with PullRequestService with IssuesService with CommitsService with LabelsService
|
||||
with MilestonesService with PrioritiesService with WebHookPullRequestReviewCommentService
|
||||
|
||||
test("WebHookPullRequestService.getPullRequestsByRequestForWebhook") {
|
||||
withTestDB { implicit session =>
|
||||
@@ -19,7 +19,7 @@ class WebHookServiceSpec extends FunSuite with ServiceSpecBase {
|
||||
val (issue3, pullreq3) = generateNewPullRequest("user3/repo3/master3", "user2/repo2/master2", loginUser = "root")
|
||||
val (issue32, pullreq32) =
|
||||
generateNewPullRequest("user3/repo3/master32", "user2/repo2/master2", loginUser = "root")
|
||||
generateNewPullRequest("user2/repo2/master2", "user1/repo1/master2")
|
||||
generateNewPullRequest("user2/repo2/master2", "user1/repo1/master2", loginUser = "root")
|
||||
service.addWebHook("user1", "repo1", "webhook1-1", Set(WebHook.PullRequest), WebHookContentType.FORM, Some("key"))
|
||||
service.addWebHook("user1", "repo1", "webhook1-2", Set(WebHook.PullRequest), WebHookContentType.FORM, Some("key"))
|
||||
service.addWebHook("user2", "repo2", "webhook2-1", Set(WebHook.PullRequest), WebHookContentType.FORM, Some("key"))
|
||||
|
||||
@@ -15,6 +15,7 @@ import java.nio.file._
|
||||
import java.io.File
|
||||
|
||||
object GitSpecUtil {
|
||||
|
||||
def withTestFolder[U](f: File => U): U = {
|
||||
val folder = new File(System.getProperty("java.io.tmpdir"), "test-" + System.nanoTime)
|
||||
if (!folder.mkdirs()) {
|
||||
@@ -26,7 +27,9 @@ object GitSpecUtil {
|
||||
FileUtils.deleteQuietly(folder)
|
||||
}
|
||||
}
|
||||
|
||||
def withTestRepository[U](f: Git => U): U = withTestFolder(folder => using(Git.open(createTestRepository(folder)))(f))
|
||||
|
||||
def createTestRepository(dir: File): File = {
|
||||
RepositoryCache.clear()
|
||||
FileUtils.deleteQuietly(dir)
|
||||
@@ -34,13 +37,14 @@ object GitSpecUtil {
|
||||
JGitUtil.initRepository(dir)
|
||||
dir
|
||||
}
|
||||
|
||||
def createFile(
|
||||
git: Git,
|
||||
branch: String,
|
||||
name: String,
|
||||
content: String,
|
||||
autorName: String = "dummy",
|
||||
autorEmail: String = "dummy@example.com",
|
||||
authorName: String = "dummy",
|
||||
authorEmail: String = "dummy@example.com",
|
||||
message: String = "test commit"
|
||||
): Unit = {
|
||||
val builder = DirCache.newInCore.builder()
|
||||
@@ -67,13 +71,14 @@ object GitSpecUtil {
|
||||
headId,
|
||||
builder.getDirCache.writeTree(inserter),
|
||||
branch,
|
||||
autorName,
|
||||
autorEmail,
|
||||
authorName,
|
||||
authorEmail,
|
||||
message
|
||||
)
|
||||
inserter.flush()
|
||||
inserter.close()
|
||||
}
|
||||
|
||||
def getFile(git: Git, branch: String, path: String) = {
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
|
||||
val objectId = using(new TreeWalk(git.getRepository)) { walk =>
|
||||
@@ -89,6 +94,7 @@ object GitSpecUtil {
|
||||
}
|
||||
JGitUtil.getContentInfo(git, path, objectId)
|
||||
}
|
||||
|
||||
def mergeAndCommit(git: Git, into: String, branch: String, message: String = null): Unit = {
|
||||
val repository = git.getRepository
|
||||
val merger = MergeStrategy.RECURSIVE.newMerger(repository, true)
|
||||
|
||||
@@ -1,10 +1,270 @@
|
||||
package gitbucket.core.util
|
||||
|
||||
import GitSpecUtil._
|
||||
import gitbucket.core.util.SyntaxSugars.using
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.eclipse.jgit.diff.DiffEntry
|
||||
import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
||||
import org.eclipse.jgit.lib.Constants
|
||||
import org.eclipse.jgit.treewalk.TreeWalk
|
||||
import org.scalatest.FunSuite
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
class JGitUtilSpec extends FunSuite {
|
||||
|
||||
test("isEmpty") {
|
||||
withTestRepository { git =>
|
||||
assert(JGitUtil.isEmpty(git) == true)
|
||||
|
||||
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
||||
assert(JGitUtil.isEmpty(git) == false)
|
||||
}
|
||||
}
|
||||
|
||||
test("getDiffs") {
|
||||
withTestRepository { git =>
|
||||
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
||||
|
||||
val branchId = git.getRepository.resolve("master")
|
||||
val commit = JGitUtil.getRevCommitFromId(git, branchId)
|
||||
|
||||
createFile(git, Constants.HEAD, "LICENSE", "Apache License", message = "commit1")
|
||||
createFile(git, Constants.HEAD, "README.md", "body1\nbody2", message = "commit1")
|
||||
|
||||
// latest commit
|
||||
val diff1 = JGitUtil.getDiffs(git, None, "master", false, true)
|
||||
assert(diff1.size == 1)
|
||||
assert(diff1(0).changeType == ChangeType.MODIFY)
|
||||
assert(diff1(0).oldPath == "README.md")
|
||||
assert(diff1(0).newPath == "README.md")
|
||||
assert(diff1(0).tooLarge == false)
|
||||
assert(diff1(0).patch == Some("""@@ -1 +1,2 @@
|
||||
|-body1
|
||||
|\ No newline at end of file
|
||||
|+body1
|
||||
|+body2
|
||||
|\ No newline at end of file""".stripMargin))
|
||||
|
||||
// from specified commit
|
||||
val diff2 = JGitUtil.getDiffs(git, Some(commit.getName), "master", false, true)
|
||||
assert(diff2.size == 2)
|
||||
assert(diff2(0).changeType == ChangeType.ADD)
|
||||
assert(diff2(0).oldPath == "/dev/null")
|
||||
assert(diff2(0).newPath == "LICENSE")
|
||||
assert(diff2(0).tooLarge == false)
|
||||
assert(diff2(0).patch == Some("""+++ b/LICENSE
|
||||
|@@ -0,0 +1 @@
|
||||
|+Apache License
|
||||
|\ No newline at end of file""".stripMargin))
|
||||
|
||||
assert(diff2(1).changeType == ChangeType.MODIFY)
|
||||
assert(diff2(1).oldPath == "README.md")
|
||||
assert(diff2(1).newPath == "README.md")
|
||||
assert(diff2(1).tooLarge == false)
|
||||
assert(diff2(1).patch == Some("""@@ -1 +1,2 @@
|
||||
|-body1
|
||||
|\ No newline at end of file
|
||||
|+body1
|
||||
|+body2
|
||||
|\ No newline at end of file""".stripMargin))
|
||||
}
|
||||
}
|
||||
|
||||
test("getRevCommitFromId") {
|
||||
withTestRepository { git =>
|
||||
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
||||
|
||||
// branch name
|
||||
val branchId = git.getRepository.resolve("master")
|
||||
val commit1 = JGitUtil.getRevCommitFromId(git, branchId)
|
||||
|
||||
// commit id
|
||||
val commitName = commit1.getName
|
||||
val commitId = git.getRepository.resolve(commitName)
|
||||
val commit2 = JGitUtil.getRevCommitFromId(git, commitId)
|
||||
|
||||
// tag name
|
||||
JGitUtil.createTag(git, "1.0", None, commitName)
|
||||
val tagId = git.getRepository.resolve("1.0")
|
||||
val commit3 = JGitUtil.getRevCommitFromId(git, tagId)
|
||||
|
||||
// all refer same commit
|
||||
assert(commit1 == commit2)
|
||||
assert(commit1 == commit3)
|
||||
assert(commit2 == commit3)
|
||||
}
|
||||
}
|
||||
|
||||
test("getCommitCount and getAllCommitIds") {
|
||||
withTestRepository { git =>
|
||||
// getCommitCount
|
||||
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
||||
assert(JGitUtil.getCommitCount(git, "master") == 1)
|
||||
|
||||
createFile(git, Constants.HEAD, "README.md", "body2", message = "commit2")
|
||||
assert(JGitUtil.getCommitCount(git, "master") == 2)
|
||||
|
||||
// maximum limit
|
||||
(3 to 10).foreach { i =>
|
||||
createFile(git, Constants.HEAD, "README.md", "body" + i, message = "commit" + i)
|
||||
}
|
||||
assert(JGitUtil.getCommitCount(git, "master", 5) == 5)
|
||||
|
||||
// actual commit count
|
||||
val gitLog = git.log.add(git.getRepository.resolve("master")).all
|
||||
assert(gitLog.call.asScala.toSeq.size == 10)
|
||||
|
||||
// getAllCommitIds
|
||||
val allCommits = JGitUtil.getAllCommitIds(git)
|
||||
assert(allCommits.size == 10)
|
||||
}
|
||||
}
|
||||
|
||||
test("createBranch, branchesOfCommit and getBranches") {
|
||||
withTestRepository { git =>
|
||||
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
||||
|
||||
// createBranch
|
||||
assert(JGitUtil.createBranch(git, "master", "test1") == Right("Branch created."))
|
||||
assert(JGitUtil.createBranch(git, "master", "test2") == Right("Branch created."))
|
||||
assert(JGitUtil.createBranch(git, "master", "test2") == Left("Sorry, that branch already exists."))
|
||||
|
||||
// verify
|
||||
val branches = git.branchList.call()
|
||||
assert(branches.size == 3)
|
||||
assert(branches.get(0).getName == "refs/heads/master")
|
||||
assert(branches.get(1).getName == "refs/heads/test1")
|
||||
assert(branches.get(2).getName == "refs/heads/test2")
|
||||
|
||||
// getBranchesOfCommit
|
||||
val commit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve("master"))
|
||||
val branchesOfCommit = JGitUtil.getBranchesOfCommit(git, commit.getName)
|
||||
assert(branchesOfCommit.size == 3)
|
||||
assert(branchesOfCommit(0) == "master")
|
||||
assert(branchesOfCommit(1) == "test1")
|
||||
assert(branchesOfCommit(2) == "test2")
|
||||
}
|
||||
}
|
||||
|
||||
test("getBranches") {
|
||||
withTestRepository { git =>
|
||||
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
||||
JGitUtil.createBranch(git, "master", "test1")
|
||||
|
||||
createFile(git, Constants.HEAD, "README.md", "body2", message = "commit2")
|
||||
JGitUtil.createBranch(git, "master", "test2")
|
||||
|
||||
// getBranches
|
||||
val branches = JGitUtil.getBranches(git, "master", true)
|
||||
assert(branches.size == 3)
|
||||
|
||||
assert(branches(0).name == "master")
|
||||
assert(branches(0).committerName == "dummy")
|
||||
assert(branches(0).committerEmailAddress == "dummy@example.com")
|
||||
|
||||
assert(branches(1).name == "test1")
|
||||
assert(branches(1).committerName == "dummy")
|
||||
assert(branches(1).committerEmailAddress == "dummy@example.com")
|
||||
|
||||
assert(branches(2).name == "test2")
|
||||
assert(branches(2).committerName == "dummy")
|
||||
assert(branches(2).committerEmailAddress == "dummy@example.com")
|
||||
|
||||
assert(branches(0).commitId != branches(1).commitId)
|
||||
assert(branches(0).commitId == branches(2).commitId)
|
||||
}
|
||||
}
|
||||
|
||||
test("createTag, getTagsOfCommit and getTagsOnCommit") {
|
||||
withTestRepository { git =>
|
||||
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
||||
|
||||
// createTag
|
||||
assert(JGitUtil.createTag(git, "1.0", Some("test1"), "master") == Right("Tag added."))
|
||||
assert(
|
||||
JGitUtil.createTag(git, "1.0", Some("test2"), "master") == Left("Sorry, some Git operation error occurs.")
|
||||
)
|
||||
|
||||
// record current commit
|
||||
val commit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve("master"))
|
||||
|
||||
// createTag
|
||||
createFile(git, Constants.HEAD, "LICENSE", "Apache License", message = "commit2")
|
||||
assert(JGitUtil.createTag(git, "1.1", Some("test3"), "master") == Right("Tag added."))
|
||||
|
||||
// verify
|
||||
val allTags = git.tagList().call().asScala
|
||||
assert(allTags.size == 2)
|
||||
assert(allTags(0).getName == "refs/tags/1.0")
|
||||
assert(allTags(1).getName == "refs/tags/1.1")
|
||||
|
||||
// getTagsOfCommit
|
||||
val tagsOfCommit = JGitUtil.getTagsOfCommit(git, commit.getName)
|
||||
assert(tagsOfCommit.size == 2)
|
||||
assert(tagsOfCommit(0) == "1.1")
|
||||
assert(tagsOfCommit(1) == "1.0")
|
||||
|
||||
// getTagsOnCommit
|
||||
val tagsOnCommit = JGitUtil.getTagsOnCommit(git, "master")
|
||||
assert(tagsOnCommit.size == 1)
|
||||
assert(tagsOnCommit(0) == "1.1")
|
||||
}
|
||||
}
|
||||
|
||||
test("openFile for non-LFS file") {
|
||||
withTestRepository { git =>
|
||||
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
||||
createFile(git, Constants.HEAD, "LICENSE", "Apache License", message = "commit2")
|
||||
|
||||
val objectId = git.getRepository.resolve("master")
|
||||
val commit = JGitUtil.getRevCommitFromId(git, objectId)
|
||||
|
||||
// Since Non-LFS file doesn't need RepositoryInfo give null
|
||||
assert(JGitUtil.openFile(git, null, commit.getTree, "README.md") { in =>
|
||||
IOUtils.toString(in, "UTF-8")
|
||||
} == "body1")
|
||||
|
||||
assert(JGitUtil.openFile(git, null, commit.getTree, "LICENSE") { in =>
|
||||
IOUtils.toString(in, "UTF-8")
|
||||
} == "Apache License")
|
||||
}
|
||||
}
|
||||
|
||||
test("getContentFromPath") {
|
||||
withTestRepository { git =>
|
||||
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
||||
createFile(git, Constants.HEAD, "LARGE_FILE", "body1" * 1000000, message = "commit1")
|
||||
|
||||
val objectId = git.getRepository.resolve("master")
|
||||
val commit = JGitUtil.getRevCommitFromId(git, objectId)
|
||||
|
||||
val content1 = JGitUtil.getContentFromPath(git, commit.getTree, "README.md", true)
|
||||
assert(content1.map(x => new String(x, "UTF-8")) == Some("body1"))
|
||||
|
||||
val content2 = JGitUtil.getContentFromPath(git, commit.getTree, "LARGE_FILE", false)
|
||||
assert(content2.isEmpty)
|
||||
|
||||
val content3 = JGitUtil.getContentFromPath(git, commit.getTree, "LARGE_FILE", true)
|
||||
assert(content3.map(x => new String(x, "UTF-8")) == Some("body1" * 1000000))
|
||||
}
|
||||
}
|
||||
|
||||
test("getBlame") {
|
||||
withTestRepository { git =>
|
||||
createFile(git, Constants.HEAD, "README.md", "body1\nbody2\nbody3", message = "commit1")
|
||||
createFile(git, Constants.HEAD, "README.md", "body0\nbody2\nbody3", message = "commit2")
|
||||
|
||||
val blames = JGitUtil.getBlame(git, "master", "README.md").toSeq
|
||||
|
||||
assert(blames.size == 2)
|
||||
assert(blames(0).message == "commit2")
|
||||
assert(blames(0).lines == Set(0))
|
||||
assert(blames(1).message == "commit1")
|
||||
assert(blames(1).lines == Set(1, 2))
|
||||
}
|
||||
}
|
||||
|
||||
test("getFileList(git: Git, revision: String, path)") {
|
||||
withTestRepository { git =>
|
||||
def list(branch: String, path: String) =
|
||||
@@ -170,4 +430,19 @@ class JGitUtilSpec extends FunSuite {
|
||||
assert(list("master", "test") == List(("text2.txt", "commit2", false)))
|
||||
}
|
||||
}
|
||||
|
||||
test("getLfsObjects") {
|
||||
val str = """version https://git-lfs.github.com/spec/v1
|
||||
|oid sha256:aa8a7a4903572ccd1571c03f442661a983d79b53bbb7bcdd50769429f0b24ab8
|
||||
|size 178643""".stripMargin
|
||||
|
||||
val attrs = JGitUtil.getLfsObjects(str)
|
||||
assert(attrs("oid") == "sha256:aa8a7a4903572ccd1571c03f442661a983d79b53bbb7bcdd50769429f0b24ab8")
|
||||
assert(attrs("size") == "178643")
|
||||
}
|
||||
|
||||
test("getContentInfo") {
|
||||
// TODO
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import gitbucket.core.controller.Context
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import org.scalatest.FunSpec
|
||||
import org.scalatest.mockito.MockitoSugar
|
||||
import java.util.Date
|
||||
import java.util.TimeZone
|
||||
|
||||
class HelpersSpec extends FunSpec with MockitoSugar {
|
||||
|
||||
@@ -64,4 +66,77 @@ class HelpersSpec extends FunSpec with MockitoSugar {
|
||||
assert(after == """<a href="http://exa"mple.com">http://exa"mple.com</a>""")
|
||||
}
|
||||
}
|
||||
|
||||
describe("datetimeAgo") {
|
||||
it("should render a time within a minute") {
|
||||
val time = System.currentTimeMillis()
|
||||
val datetime = datetimeAgo(new Date(time))
|
||||
assert(datetime == "just now")
|
||||
}
|
||||
|
||||
it("should render a time 1 minute ago") {
|
||||
val time = System.currentTimeMillis() - (60 * 1000)
|
||||
val datetime = datetimeAgo(new Date(time))
|
||||
assert(datetime == "1 minute ago")
|
||||
}
|
||||
|
||||
it("should render a time 2 minute ago") {
|
||||
val time = System.currentTimeMillis() - (60 * 1000 * 2)
|
||||
val datetime = datetimeAgo(new Date(time))
|
||||
assert(datetime == "2 minutes ago")
|
||||
}
|
||||
}
|
||||
|
||||
describe("datetimeRFC3339") {
|
||||
it("should format date as RFC3339 format") {
|
||||
val time = 1546961077224L
|
||||
val datetime = datetimeRFC3339(new Date(time))
|
||||
assert(datetime == "2019-01-08T15:24:37Z")
|
||||
}
|
||||
}
|
||||
|
||||
describe("date") {
|
||||
it("should format date as yyyy-MM-dd with default timezone") {
|
||||
val defaultTimeZone = TimeZone.getDefault
|
||||
|
||||
try {
|
||||
val time = 1546961077247L
|
||||
TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
|
||||
val datetimeUTC = date(new Date(time))
|
||||
assert(datetimeUTC == "2019-01-08")
|
||||
|
||||
TimeZone.setDefault(TimeZone.getTimeZone("JST"))
|
||||
val datetimeJST = date(new Date(time))
|
||||
assert(datetimeJST == "2019-01-09")
|
||||
|
||||
} finally {
|
||||
TimeZone.setDefault(defaultTimeZone)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe("hashDate") {
|
||||
it("should format date as yyyyMMDDHHmmss with default timezone") {
|
||||
val defaultTimeZone = TimeZone.getDefault
|
||||
|
||||
try {
|
||||
val time = 1546961077247L
|
||||
TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
|
||||
val hash = hashDate(new Date(time))
|
||||
assert(hash == "20190108152437")
|
||||
} finally {
|
||||
TimeZone.setDefault(defaultTimeZone)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe("hashQuery") {
|
||||
it("should return same value for multiple calls") {
|
||||
val time = 1546961077247L
|
||||
val hash1 = hashQuery
|
||||
Thread.sleep(500)
|
||||
val hash2 = hashQuery
|
||||
assert(hash1 == hash2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package gitbucket.core.view
|
||||
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import org.scalatest.FunSpec
|
||||
import org.scalatest.mockito.MockitoSugar
|
||||
import org.mockito.Mockito._
|
||||
|
||||
class MarkdownSpec extends FunSpec {
|
||||
class MarkdownSpec extends FunSpec with MockitoSugar {
|
||||
|
||||
import Markdown._
|
||||
|
||||
@@ -89,4 +93,69 @@ tasks
|
||||
assert(after == " -[ ] aaaa")
|
||||
}
|
||||
}
|
||||
|
||||
describe("toHtml") {
|
||||
it("should fix url at the repository root") {
|
||||
val repository = mock[RepositoryInfo]
|
||||
val context = mock[Context]
|
||||
when(context.currentPath).thenReturn("/user/repo")
|
||||
when(repository.httpUrl(context)).thenReturn("http://localhost:8080/git/user/repo.git")
|
||||
|
||||
val html = Markdown.toHtml(
|
||||
markdown = "[ChangeLog](CHANGELOG.md)",
|
||||
repository = repository,
|
||||
branch = "master",
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true
|
||||
)(context)
|
||||
|
||||
assert(
|
||||
html == """<p><a href="http://localhost:8080/user/repo/blob/master/CHANGELOG.md">ChangeLog</a></p>"""
|
||||
)
|
||||
}
|
||||
|
||||
it("should fix sub directory url at the file list") {
|
||||
val repository = mock[RepositoryInfo]
|
||||
val context = mock[Context]
|
||||
when(context.currentPath).thenReturn("/user/repo/tree/master/sub/dir")
|
||||
when(repository.httpUrl(context)).thenReturn("http://localhost:8080/git/user/repo.git")
|
||||
|
||||
val html = Markdown.toHtml(
|
||||
markdown = "[ChangeLog](CHANGELOG.md)",
|
||||
repository = repository,
|
||||
branch = "master",
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true
|
||||
)(context)
|
||||
|
||||
assert(
|
||||
html == """<p><a href="http://localhost:8080/user/repo/blob/master/sub/dir/CHANGELOG.md">ChangeLog</a></p>"""
|
||||
)
|
||||
}
|
||||
|
||||
it("should fix sub directory url at the blob view") {
|
||||
val repository = mock[RepositoryInfo]
|
||||
val context = mock[Context]
|
||||
when(context.currentPath).thenReturn("/user/repo/blob/master/sub/dir/README.md")
|
||||
when(repository.httpUrl(context)).thenReturn("http://localhost:8080/git/user/repo.git")
|
||||
|
||||
val html = Markdown.toHtml(
|
||||
markdown = "[ChangeLog](CHANGELOG.md)",
|
||||
repository = repository,
|
||||
branch = "master",
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true
|
||||
)(context)
|
||||
|
||||
assert(
|
||||
html == """<p><a href="CHANGELOG.md">ChangeLog</a></p>"""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user