mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-05 13:05:50 +01:00
Compare commits
355 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c65599d995 | ||
|
|
22ae1df4b1 | ||
|
|
6b22342166 | ||
|
|
53f6190267 | ||
|
|
f73daaef44 | ||
|
|
d99e382dfe | ||
|
|
aefbee2093 | ||
|
|
11fb0a7edf | ||
|
|
fe959aecff | ||
|
|
9b33655bd4 | ||
|
|
33acad85db | ||
|
|
6bfe3ea760 | ||
|
|
1532fd71d0 | ||
|
|
c14a732e2a | ||
|
|
a1372034ed | ||
|
|
98914269b7 | ||
|
|
d5e455336b | ||
|
|
7b84f25c56 | ||
|
|
2ca20af502 | ||
|
|
78df2accfc | ||
|
|
7a282fb67e | ||
|
|
db679967af | ||
|
|
9e98d30612 | ||
|
|
a47065e4a9 | ||
|
|
94d18c471c | ||
|
|
f8f3019228 | ||
|
|
c3d90b8593 | ||
|
|
62c1299f29 | ||
|
|
b75db98cad | ||
|
|
3592b3d13c | ||
|
|
ca814e2c08 | ||
|
|
48b6a590bf | ||
|
|
285ef02a17 | ||
|
|
18375c741e | ||
|
|
21030344cc | ||
|
|
a494027217 | ||
|
|
7bca01af59 | ||
|
|
acf3fa9980 | ||
|
|
c0ce0f8d19 | ||
|
|
56e7168461 | ||
|
|
c2d0d94f05 | ||
|
|
fc22cfbbdd | ||
|
|
d62adbf649 | ||
|
|
dba5539e3e | ||
|
|
f0a8b3bb17 | ||
|
|
f52e7e1bdd | ||
|
|
58ba26f21e | ||
|
|
bf7b30630c | ||
|
|
b5cac0308e | ||
|
|
373ea39048 | ||
|
|
427f5eec5f | ||
|
|
a4e9903e00 | ||
|
|
0d900a892c | ||
|
|
dc6fdaf482 | ||
|
|
b79498ed9f | ||
|
|
69e8f628df | ||
|
|
d3d8e3ce5f | ||
|
|
0499c47f4b | ||
|
|
7fd0cdd7d8 | ||
|
|
49eaf79e01 | ||
|
|
3a96c30aa8 | ||
|
|
6d550fa485 | ||
|
|
7f9d69bb51 | ||
|
|
709fab9ccc | ||
|
|
fd13a2db79 | ||
|
|
840d81f7bd | ||
|
|
5d08f4d339 | ||
|
|
ef48b2d5ef | ||
|
|
f54e4f337f | ||
|
|
743965d3b8 | ||
|
|
0e787eddfd | ||
|
|
442c0d575e | ||
|
|
485516be2e | ||
|
|
6b2fbb3bf0 | ||
|
|
e510b1c26b | ||
|
|
8d35494169 | ||
|
|
cf1504bae7 | ||
|
|
9bb4e473b9 | ||
|
|
d67afebadc | ||
|
|
417886161c | ||
|
|
1b85d511e9 | ||
|
|
45d84f63c1 | ||
|
|
fff60b2704 | ||
|
|
c9339aec9e | ||
|
|
7c98ae1341 | ||
|
|
01c2291715 | ||
|
|
2e03f081d9 | ||
|
|
0cbafdd884 | ||
|
|
d5a9c2c15d | ||
|
|
1496591244 | ||
|
|
f5acce3901 | ||
|
|
5568a0ad8e | ||
|
|
26a18287c7 | ||
|
|
b0f819b9bd | ||
|
|
ebff7baf07 | ||
|
|
cf9a55d896 | ||
|
|
72f7b659f4 | ||
|
|
87192d025b | ||
|
|
fd181b9a0c | ||
|
|
9c4cc12a02 | ||
|
|
44497b559e | ||
|
|
09c50a149b | ||
|
|
88beb68e01 | ||
|
|
0da358311b | ||
|
|
cf97b63dab | ||
|
|
4b5f22144e | ||
|
|
0d342a6863 | ||
|
|
458820a09d | ||
|
|
135c34ef0f | ||
|
|
8187c5a013 | ||
|
|
6ff48c8130 | ||
|
|
d37c70cd8d | ||
|
|
8abf357405 | ||
|
|
c93ac71634 | ||
|
|
408180f071 | ||
|
|
4e98abfe5c | ||
|
|
efbb404bd4 | ||
|
|
66f409bfad | ||
|
|
44ec64fb4b | ||
|
|
fb27bd29e8 | ||
|
|
c26ca9d463 | ||
|
|
8c36ba33f4 | ||
|
|
fe1e18b495 | ||
|
|
29e632af04 | ||
|
|
2b9daae62b | ||
|
|
8a11f85dd1 | ||
|
|
b09c72b106 | ||
|
|
43456e817a | ||
|
|
7876a60106 | ||
|
|
38e71001cb | ||
|
|
a1307b7464 | ||
|
|
fd413d36ad | ||
|
|
509dfc57ca | ||
|
|
95bdd6228e | ||
|
|
fd1430371a | ||
|
|
437f944c6e | ||
|
|
f64b6e10bb | ||
|
|
5925bd3772 | ||
|
|
fe8d4616db | ||
|
|
99b40974c3 | ||
|
|
c463590ede | ||
|
|
82163eebc2 | ||
|
|
f1e427f926 | ||
|
|
c21dcdca80 | ||
|
|
2ce51472c3 | ||
|
|
514b1aeec1 | ||
|
|
3f34622fe0 | ||
|
|
8c6d5b8178 | ||
|
|
1971c29fd0 | ||
|
|
40ca9b6682 | ||
|
|
81aeed6f6c | ||
|
|
bf50b1bf82 | ||
|
|
1094e8ca2d | ||
|
|
860ccce89b | ||
|
|
59e5993eba | ||
|
|
c08627e5d6 | ||
|
|
3e0bb46699 | ||
|
|
9a972e40ef | ||
|
|
99ad0db1f6 | ||
|
|
12d11bc80c | ||
|
|
dc98079b55 | ||
|
|
88f56126a6 | ||
|
|
313c9976c8 | ||
|
|
5b6a1d0adc | ||
|
|
6db43e6ca7 | ||
|
|
46177e814c | ||
|
|
2fe79baed8 | ||
|
|
02e17c76a7 | ||
|
|
4d8acfd286 | ||
|
|
4a5c287b8f | ||
|
|
0d4047b4ee | ||
|
|
2b730ef180 | ||
|
|
ecbe7228b9 | ||
|
|
e36d0f65d6 | ||
|
|
30818fb797 | ||
|
|
6d7685fcce | ||
|
|
2cec5be3d9 | ||
|
|
038b70ff0b | ||
|
|
23a5f7dcf9 | ||
|
|
baa27d6090 | ||
|
|
f71acfcbe8 | ||
|
|
c65e843491 | ||
|
|
9103c88f0e | ||
|
|
90170f0fcc | ||
|
|
2e3de336cb | ||
|
|
da50d317e7 | ||
|
|
7a91e14b03 | ||
|
|
58eff0a1a3 | ||
|
|
8559f3e354 | ||
|
|
7606097a4d | ||
|
|
dfbace3d26 | ||
|
|
d61ab632f1 | ||
|
|
c3b341d945 | ||
|
|
59f063627c | ||
|
|
b4aba76005 | ||
|
|
81afea350d | ||
|
|
5c5da60dd6 | ||
|
|
89c69cdfc2 | ||
|
|
0789010248 | ||
|
|
37c23f615f | ||
|
|
b4d3573a84 | ||
|
|
5161ece63b | ||
|
|
5b7955cee6 | ||
|
|
60099e2b0d | ||
|
|
f04c486251 | ||
|
|
7b23bbf9ba | ||
|
|
0a532d9774 | ||
|
|
208b98285c | ||
|
|
56c9aa32a4 | ||
|
|
35080a9f33 | ||
|
|
6c41505c91 | ||
|
|
05bfaafe32 | ||
|
|
7534a88607 | ||
|
|
d7817d3d88 | ||
|
|
37780d467d | ||
|
|
ad4af67b30 | ||
|
|
29b0c22b0e | ||
|
|
c400678550 | ||
|
|
3c40e93346 | ||
|
|
247b664654 | ||
|
|
b3db0a6a7b | ||
|
|
0a03e41f1c | ||
|
|
a79f105eea | ||
|
|
74f3f6bf2e | ||
|
|
a4bf73724d | ||
|
|
8d91253ede | ||
|
|
b7355af49a | ||
|
|
7ec85cbf99 | ||
|
|
a6790b049d | ||
|
|
dce747b1e8 | ||
|
|
c22ee8acfd | ||
|
|
30b8b738b6 | ||
|
|
b916595da3 | ||
|
|
1accafa8b1 | ||
|
|
11700f4cb4 | ||
|
|
c9de8dd323 | ||
|
|
fd694e38aa | ||
|
|
8822c36b5f | ||
|
|
e614e31162 | ||
|
|
ad47ad4269 | ||
|
|
90b63090cc | ||
|
|
345685ed7c | ||
|
|
1d9fe5770e | ||
|
|
0c50545cbd | ||
|
|
53cbc36a01 | ||
|
|
85b2053004 | ||
|
|
eba240de65 | ||
|
|
1e5114cd54 | ||
|
|
90cb5de5f0 | ||
|
|
11d33e9389 | ||
|
|
c71e9331ae | ||
|
|
ec307b84d3 | ||
|
|
f37b5fa682 | ||
|
|
8cb1ac734d | ||
|
|
05ff2a854c | ||
|
|
d956ade5e3 | ||
|
|
73228506a5 | ||
|
|
2525bbafa8 | ||
|
|
338946dd3a | ||
|
|
2d225641ee | ||
|
|
3c727fe678 | ||
|
|
523ea0d437 | ||
|
|
9eff8f248b | ||
|
|
d50c858a26 | ||
|
|
6f4e94ba9a | ||
|
|
f2750c20a2 | ||
|
|
2da2b426a1 | ||
|
|
5a0bc127b7 | ||
|
|
23a482bbba | ||
|
|
6c2fce1b16 | ||
|
|
5d7346db91 | ||
|
|
443498433d | ||
|
|
a58ce07736 | ||
|
|
1903c3990c | ||
|
|
90441c8eec | ||
|
|
601919bcc6 | ||
|
|
6903b096f5 | ||
|
|
e44fed09fa | ||
|
|
8ed0c8a170 | ||
|
|
31118ac285 | ||
|
|
c851b7582f | ||
|
|
102a02d527 | ||
|
|
1968bf871a | ||
|
|
5cd4e48173 | ||
|
|
111d212cb5 | ||
|
|
d648d34393 | ||
|
|
bbaa5b38e7 | ||
|
|
3c50a78be2 | ||
|
|
82cc1fa530 | ||
|
|
8c0581973e | ||
|
|
bfa15f5d75 | ||
|
|
57e87b581d | ||
|
|
5aa6f5bce3 | ||
|
|
9ba098a805 | ||
|
|
b2d8567c26 | ||
|
|
19d97c93ce | ||
|
|
cad2daa2f9 | ||
|
|
585f0b5769 | ||
|
|
dd23d1109b | ||
|
|
5e84221d39 | ||
|
|
faf3e6c26b | ||
|
|
969da2c63b | ||
|
|
ba61891510 | ||
|
|
a581871a89 | ||
|
|
d96e1fa503 | ||
|
|
b66812d76c | ||
|
|
ae32016856 | ||
|
|
56aec15e68 | ||
|
|
d0c8e33ec5 | ||
|
|
7ab260e688 | ||
|
|
0d2c923664 | ||
|
|
e7a47fe3a4 | ||
|
|
e454f78c5a | ||
|
|
192e4ade3e | ||
|
|
733797cb6f | ||
|
|
f0e4157a46 | ||
|
|
2ad6948bb4 | ||
|
|
dd46d649a6 | ||
|
|
bbe8a9b9e4 | ||
|
|
376b109602 | ||
|
|
1d085d52bb | ||
|
|
d1f42e0ed7 | ||
|
|
101f8598ed | ||
|
|
da62f6f8fb | ||
|
|
5750286b5d | ||
|
|
f2ca4fb64b | ||
|
|
5f6b577cbf | ||
|
|
62004c279c | ||
|
|
838c7fb991 | ||
|
|
93786f0fd6 | ||
|
|
f3514e5625 | ||
|
|
a2b0ee0c24 | ||
|
|
2bcab30529 | ||
|
|
91cda6d245 | ||
|
|
a82e579d57 | ||
|
|
94421c7a63 | ||
|
|
6431d25409 | ||
|
|
c8686f4b34 | ||
|
|
43097b4c1c | ||
|
|
539751a1d9 | ||
|
|
ff3205b6c7 | ||
|
|
40e36e3f8b | ||
|
|
3d1c9bc9de | ||
|
|
5a5bf34fe0 | ||
|
|
af7043f4bf | ||
|
|
249b27593e | ||
|
|
1201271949 | ||
|
|
19e74a4fe1 | ||
|
|
130aa1e515 | ||
|
|
e92d1eae5a | ||
|
|
8b44a00299 | ||
|
|
f5c1c0703d | ||
|
|
5036b1a1aa | ||
|
|
41e49423b2 | ||
|
|
3cc7bd3cdb |
@@ -1,6 +1,11 @@
|
|||||||
language: scala
|
language: scala
|
||||||
sudo: false
|
sudo: true
|
||||||
script:
|
script:
|
||||||
- sbt test
|
- sbt test
|
||||||
jdk:
|
jdk:
|
||||||
- oraclejdk8
|
- oraclejdk8
|
||||||
|
before_script:
|
||||||
|
- sudo apt-get install libaio1
|
||||||
|
- sudo /etc/init.d/mysql stop
|
||||||
|
- sudo /etc/init.d/postgresql stop
|
||||||
|
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -187,7 +187,7 @@
|
|||||||
same "printed page" as the copyright notice for easier
|
same "printed page" as the copyright notice for easier
|
||||||
identification within third-party archives.
|
identification within third-party archives.
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
Copyright 2013-2016 GitBucket Team
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
77
README.md
77
README.md
@@ -1,7 +1,10 @@
|
|||||||
GitBucket [](https://gitter.im/gitbucket/gitbucket) [](https://travis-ci.org/gitbucket/gitbucket)
|
GitBucket [](https://gitter.im/gitbucket/gitbucket) [](https://travis-ci.org/gitbucket/gitbucket)
|
||||||
=========
|
=========
|
||||||
|
|
||||||
GitBucket is a GitHub clone powered by Scala which has easy installation and high extensibility.
|
GitBucket is a Git platform powered by Scala offering:
|
||||||
|
- easy installation
|
||||||
|
- high extensibility by plugins
|
||||||
|
- API compatibility with Github
|
||||||
|
|
||||||
Features
|
Features
|
||||||
--------
|
--------
|
||||||
@@ -46,6 +49,8 @@ GitBucket has the plug-in system to extend GitBucket from outside of GitBucket.
|
|||||||
- [gitbucket-desktopnotify-plugin](https://github.com/yoshiyoshifujii/gitbucket-desktopnotify-plugin)
|
- [gitbucket-desktopnotify-plugin](https://github.com/yoshiyoshifujii/gitbucket-desktopnotify-plugin)
|
||||||
- [gitbucket-commitgraphs-plugin](https://github.com/yoshiyoshifujii/gitbucket-commitgraphs-plugin)
|
- [gitbucket-commitgraphs-plugin](https://github.com/yoshiyoshifujii/gitbucket-commitgraphs-plugin)
|
||||||
- [gitbucket-asciidoctor-plugin](https://github.com/lefou/gitbucket-asciidoctor-plugin)
|
- [gitbucket-asciidoctor-plugin](https://github.com/lefou/gitbucket-asciidoctor-plugin)
|
||||||
|
- [gitbucket-network-plugin](https://github.com/mrkm4ntr/gitbucket-network-plugin)
|
||||||
|
- [gitbucket-emoji-plugin](https://github.com/gitbucket/gitbucket-emoji-plugin)
|
||||||
|
|
||||||
You can find community plugins other than them at [gitbucket community plugins](http://gitbucket-plugins.github.io/).
|
You can find community plugins other than them at [gitbucket community plugins](http://gitbucket-plugins.github.io/).
|
||||||
|
|
||||||
@@ -56,10 +61,76 @@ Support
|
|||||||
- Make sure check whether there is a same question or request in the past.
|
- Make sure check whether there is a same question or request in the past.
|
||||||
- When raise a new issue, write subject in **English** at least.
|
- When raise a new issue, write subject in **English** at least.
|
||||||
- We can also support in Japaneses other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
|
- We can also support in Japaneses other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
|
||||||
- First priority of GitBucket is easy installation and reproduce GitHub behavior, so we might reject if your request is against it.
|
- First priority of GitBucket is easy installation and API compatibility with GitHub, so we might reject if your request is against it.
|
||||||
|
|
||||||
Release Notes
|
Release Notes
|
||||||
--------
|
-------------
|
||||||
|
### 4.5 - 29 Sep 2016
|
||||||
|
- Attach files by dropping into textarea
|
||||||
|
- Issues / Pull requests switcher in dashboard
|
||||||
|
- HikariCP could be configured in `GITBUCKET_HOME/database.conf`
|
||||||
|
- Improve Cookie security
|
||||||
|
- Display commit count on the history button
|
||||||
|
- Improve mobile view
|
||||||
|
|
||||||
|
### 4.4 - 28 Aug 2016
|
||||||
|
- Import a SQL dump file to the database
|
||||||
|
- `go get` support in private repositories
|
||||||
|
- Sort milestones by due date
|
||||||
|
- apache-sshd has been updated to 1.2.0
|
||||||
|
|
||||||
|
### 4.3 - 30 Jul 2016
|
||||||
|
- Emoji support by [gitbucket-emoji-plugin](https://github.com/gitbucket/gitbucket-emoji-plugin)
|
||||||
|
- User name suggestion
|
||||||
|
- Add new web APIs and basic authentication support for API access
|
||||||
|
- Root Endpoint
|
||||||
|
- [List endpoints](https://developer.github.com/v3/#root-endpoint)
|
||||||
|
- [List Branches](https://developer.github.com/v3/repos/branches/#list-branches)
|
||||||
|
- [Get contents](https://developer.github.com/v3/repos/contents/#get-contents)
|
||||||
|
- [Get a Reference](https://developer.github.com/v3/git/refs/#get-a-reference)
|
||||||
|
- [List Collaborators](https://developer.github.com/v3/repos/collaborators/#list-collaborators)
|
||||||
|
- [List user repositories](https://developer.github.com/v3/repos/#list-user-repositories)
|
||||||
|
- [Get a group](https://developer.github.com/v3/orgs/#get-an-organization)
|
||||||
|
- [List group repositories](https://developer.github.com/v3/repos/#list-organization-repositories)
|
||||||
|
- Add new extension points
|
||||||
|
- `assetsMapping` : Supplies resources in plugin classpath as web assets
|
||||||
|
- `suggestionProvider` : Provides suggestion in the Markdown editing textarea
|
||||||
|
- `textDecorator` : Decorate text nodes in HTML which is converted from Markdown
|
||||||
|
|
||||||
|
### 4.2.1 - 3 Jul 2016
|
||||||
|
- Fix migration bug
|
||||||
|
|
||||||
|
This is hotfix for a critical bug in migration. If you are new installation, use 4.2.0. But if you have an exisiting installation and it had been updated to 4.0 from 3.x, you must update to 4.2.1.
|
||||||
|
|
||||||
|
### 4.2 - 2 Jul 2016
|
||||||
|
- New UI based on [AdminLTE](https://github.com/almasaeed2010/AdminLTE)
|
||||||
|
- git gc
|
||||||
|
- Issues and Wiki have been possible to be disabled
|
||||||
|
- SMTP configuration test mail
|
||||||
|
|
||||||
|
### 4.1 - 4 Jun 2016
|
||||||
|
- Generic ssh user
|
||||||
|
- Improve branch protection UI
|
||||||
|
- Default value of pull request title
|
||||||
|
|
||||||
|
### 4.0 - 30 Apr 2016
|
||||||
|
- MySQL and PostgreSQL support
|
||||||
|
- Data export and import
|
||||||
|
- Migration system has been switched to [solidbase](https://github.com/gitbucket/solidbase)
|
||||||
|
|
||||||
|
**Note:** You can upgrade to GitBucket 4.0 from 3.14. If your GitBucket is 3.13 or before, you have to upgrade 3.14 at first.
|
||||||
|
|
||||||
|
### 3.14 - 30 Apr 2016
|
||||||
|
- File attachment and search for wiki pages
|
||||||
|
- New extension points to add menus
|
||||||
|
- Content-Type of webhooks has been choosable
|
||||||
|
|
||||||
|
### 3.13 - 1 Apr 2016
|
||||||
|
- Refresh user interface for wide screen
|
||||||
|
- Add `pull_request` key in list issues API for pull requests
|
||||||
|
- Add `X-Hub-Signature` security to webhooks
|
||||||
|
- Provide SHA-256 checksum for `gitbucket.war`
|
||||||
|
|
||||||
### 3.12 - 27 Feb 2016
|
### 3.12 - 27 Feb 2016
|
||||||
- New GitHub UI
|
- New GitHub UI
|
||||||
- Improve mobile view
|
- Improve mobile view
|
||||||
|
|||||||
109
build.sbt
109
build.sbt
@@ -1,8 +1,8 @@
|
|||||||
val Organization = "gitbucket"
|
val Organization = "io.github.gitbucket"
|
||||||
val Name = "gitbucket"
|
val Name = "gitbucket"
|
||||||
val GitBucketVersion = "3.12.0"
|
val GitBucketVersion = "4.5.0"
|
||||||
val ScalatraVersion = "2.4.0"
|
val ScalatraVersion = "2.4.1"
|
||||||
val JettyVersion = "9.3.6.v20151106"
|
val JettyVersion = "9.3.9.v20160517"
|
||||||
|
|
||||||
lazy val root = (project in file(".")).enablePlugins(SbtTwirl, JettyPlugin)
|
lazy val root = (project in file(".")).enablePlugins(SbtTwirl, JettyPlugin)
|
||||||
|
|
||||||
@@ -10,54 +10,63 @@ sourcesInBase := false
|
|||||||
organization := Organization
|
organization := Organization
|
||||||
name := Name
|
name := Name
|
||||||
version := GitBucketVersion
|
version := GitBucketVersion
|
||||||
scalaVersion := "2.11.7"
|
scalaVersion := "2.11.8"
|
||||||
|
|
||||||
// dependency settings
|
// dependency settings
|
||||||
resolvers ++= Seq(
|
resolvers ++= Seq(
|
||||||
Classpaths.typesafeReleases,
|
Classpaths.typesafeReleases,
|
||||||
"sonatype-snapshot" at "https://oss.sonatype.org/content/repositories/snapshots/"
|
"amateras" at "http://amateras.sourceforge.jp/mvn/",
|
||||||
|
"sonatype-snapshot" at "https://oss.sonatype.org/content/repositories/snapshots/",
|
||||||
|
"amateras-snapshot" at "http://amateras.sourceforge.jp/mvn-snapshot/"
|
||||||
)
|
)
|
||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.1.1.201511131810-r",
|
"org.scala-lang.modules" %% "scala-java8-compat" % "0.7.0",
|
||||||
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.1.1.201511131810-r",
|
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.1.2.201602141800-r",
|
||||||
|
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.1.2.201602141800-r",
|
||||||
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
||||||
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
||||||
"org.json4s" %% "json4s-jackson" % "3.3.0",
|
"org.json4s" %% "json4s-jackson" % "3.3.0",
|
||||||
"io.github.gitbucket" %% "scalatra-forms" % "1.0.0",
|
"io.github.gitbucket" %% "scalatra-forms" % "1.0.0",
|
||||||
"commons-io" % "commons-io" % "2.4",
|
"commons-io" % "commons-io" % "2.4",
|
||||||
"io.github.gitbucket" % "markedj" % "1.0.7",
|
"io.github.gitbucket" % "solidbase" % "1.0.0",
|
||||||
"org.apache.commons" % "commons-compress" % "1.10",
|
"io.github.gitbucket" % "markedj" % "1.0.9",
|
||||||
|
"org.apache.commons" % "commons-compress" % "1.11",
|
||||||
"org.apache.commons" % "commons-email" % "1.4",
|
"org.apache.commons" % "commons-email" % "1.4",
|
||||||
"org.apache.httpcomponents" % "httpclient" % "4.5.1",
|
"org.apache.httpcomponents" % "httpclient" % "4.5.1",
|
||||||
"org.apache.sshd" % "apache-sshd" % "1.0.0",
|
"org.apache.sshd" % "apache-sshd" % "1.2.0",
|
||||||
"org.apache.tika" % "tika-core" % "1.11",
|
"org.apache.tika" % "tika-core" % "1.13",
|
||||||
"com.typesafe.slick" %% "slick" % "2.1.0",
|
"com.typesafe.slick" %% "slick" % "2.1.0",
|
||||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||||
"com.h2database" % "h2" % "1.4.190",
|
"com.h2database" % "h2" % "1.4.192",
|
||||||
"ch.qos.logback" % "logback-classic" % "1.1.1",
|
"mysql" % "mysql-connector-java" % "5.1.39",
|
||||||
"com.mchange" % "c3p0" % "0.9.5.2",
|
"org.postgresql" % "postgresql" % "9.4.1208",
|
||||||
|
"ch.qos.logback" % "logback-classic" % "1.1.7",
|
||||||
|
"com.zaxxer" % "HikariCP" % "2.4.6",
|
||||||
"com.typesafe" % "config" % "1.3.0",
|
"com.typesafe" % "config" % "1.3.0",
|
||||||
"com.typesafe.akka" %% "akka-actor" % "2.3.14",
|
"com.typesafe.akka" %% "akka-actor" % "2.3.15",
|
||||||
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.0.0",
|
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.0.0",
|
||||||
"com.enragedginger" %% "akka-quartz-scheduler" % "1.4.0-akka-2.3.x" exclude("c3p0","c3p0"),
|
"com.enragedginger" %% "akka-quartz-scheduler" % "1.4.0-akka-2.3.x" exclude("c3p0","c3p0"),
|
||||||
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
||||||
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
||||||
"junit" % "junit" % "4.12" % "test",
|
"junit" % "junit" % "4.12" % "test",
|
||||||
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test",
|
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test",
|
||||||
"org.scalaz" %% "scalaz-core" % "7.2.0" % "test"
|
"org.scalaz" %% "scalaz-core" % "7.2.4" % "test",
|
||||||
|
"com.wix" % "wix-embedded-mysql" % "1.0.3" % "test",
|
||||||
|
"ru.yandex.qatools.embed" % "postgresql-embedded" % "1.14" % "test"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Twirl settings
|
|
||||||
play.twirl.sbt.Import.TwirlKeys.templateImports += "gitbucket.core._"
|
|
||||||
|
|
||||||
// Compiler settings
|
// Compiler settings
|
||||||
scalacOptions := Seq("-deprecation", "-language:postfixOps")
|
scalacOptions := Seq("-deprecation", "-language:postfixOps", "-Ybackend:GenBCode", "-Ydelambdafy:method", "-target:jvm-1.8")
|
||||||
javacOptions in compile ++= Seq("-target", "8", "-source", "8")
|
javacOptions in compile ++= Seq("-target", "8", "-source", "8")
|
||||||
javaOptions in Jetty += "-Dlogback.configurationFile=/logback-dev.xml"
|
javaOptions in Jetty += "-Dlogback.configurationFile=/logback-dev.xml"
|
||||||
testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "junitxml", "console")
|
|
||||||
|
// Test settings
|
||||||
|
//testOptions in Test += Tests.Argument("-l", "ExternalDBTest")
|
||||||
javaOptions in Test += "-Dgitbucket.home=target/gitbucket_home_for_test"
|
javaOptions in Test += "-Dgitbucket.home=target/gitbucket_home_for_test"
|
||||||
testOptions in Test += Tests.Setup( () => new java.io.File("target/gitbucket_home_for_test").mkdir() )
|
testOptions in Test += Tests.Setup( () => new java.io.File("target/gitbucket_home_for_test").mkdir() )
|
||||||
fork in Test := true
|
fork in Test := true
|
||||||
|
|
||||||
|
// Packaging options
|
||||||
packageOptions += Package.MainClass("JettyLauncher")
|
packageOptions += Package.MainClass("JettyLauncher")
|
||||||
|
|
||||||
// Assembly settings
|
// Assembly settings
|
||||||
@@ -72,12 +81,13 @@ assemblyMergeStrategy in assembly := {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// JRebel
|
// JRebel
|
||||||
|
Seq(jrebelSettings: _*)
|
||||||
|
|
||||||
jrebel.webLinks += (target in webappPrepare).value
|
jrebel.webLinks += (target in webappPrepare).value
|
||||||
jrebel.enabled := System.getenv().get("JREBEL") != null
|
jrebel.enabled := System.getenv().get("JREBEL") != null
|
||||||
javaOptions in Jetty ++= Option(System.getenv().get("JREBEL")).toSeq.flatMap { path =>
|
javaOptions in Jetty ++= Option(System.getenv().get("JREBEL")).toSeq.flatMap { path =>
|
||||||
Seq("-noverify", "-XX:+UseConcMarkSweepGC", "-XX:+CMSClassUnloadingEnabled", s"-javaagent:${path}")
|
Seq("-noverify", "-XX:+UseConcMarkSweepGC", "-XX:+CMSClassUnloadingEnabled", s"-javaagent:${path}")
|
||||||
}
|
}
|
||||||
jrebelSettings
|
|
||||||
|
|
||||||
// Create executable war file
|
// Create executable war file
|
||||||
val executableConfig = config("executable").hide
|
val executableConfig = config("executable").hide
|
||||||
@@ -160,3 +170,56 @@ Keys.artifact in (Compile, executableKey) ~= {
|
|||||||
}
|
}
|
||||||
addArtifact(Keys.artifact in (Compile, executableKey), executableKey)
|
addArtifact(Keys.artifact in (Compile, executableKey), executableKey)
|
||||||
*/
|
*/
|
||||||
|
publishTo <<= version { (v: String) =>
|
||||||
|
val nexus = "https://oss.sonatype.org/"
|
||||||
|
if (v.trim.endsWith("SNAPSHOT")) Some("snapshots" at nexus + "content/repositories/snapshots")
|
||||||
|
else Some("releases" at nexus + "service/local/staging/deploy/maven2")
|
||||||
|
}
|
||||||
|
publishMavenStyle := true
|
||||||
|
pomIncludeRepository := { _ => false }
|
||||||
|
artifact in Keys.`package` := Artifact(moduleName.value)
|
||||||
|
pomExtra := (
|
||||||
|
<url>https://github.com/gitbucket/gitbucket</url>
|
||||||
|
<licenses>
|
||||||
|
<license>
|
||||||
|
<name>The Apache Software License, Version 2.0</name>
|
||||||
|
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||||
|
</license>
|
||||||
|
</licenses>
|
||||||
|
<scm>
|
||||||
|
<url>https://github.com/gitbucket/gitbucket</url>
|
||||||
|
<connection>scm:git:https://github.com/gitbucket/gitbucket.git</connection>
|
||||||
|
</scm>
|
||||||
|
<developers>
|
||||||
|
<developer>
|
||||||
|
<id>takezoe</id>
|
||||||
|
<name>Naoki Takezoe</name>
|
||||||
|
<url>https://github.com/takezoe</url>
|
||||||
|
</developer>
|
||||||
|
<developer>
|
||||||
|
<id>shimamoto</id>
|
||||||
|
<name>Takako Shimamoto</name>
|
||||||
|
<url>https://github.com/shimamoto</url>
|
||||||
|
</developer>
|
||||||
|
<developer>
|
||||||
|
<id>tanacasino</id>
|
||||||
|
<name>Tomofumi Tanaka</name>
|
||||||
|
<url>https://github.com/tanacasino</url>
|
||||||
|
</developer>
|
||||||
|
<developer>
|
||||||
|
<id>mrkm4ntr</id>
|
||||||
|
<name>Shintaro Murakami</name>
|
||||||
|
<url>https://github.com/mrkm4ntr</url>
|
||||||
|
</developer>
|
||||||
|
<developer>
|
||||||
|
<id>nazoking</id>
|
||||||
|
<name>nazoking</name>
|
||||||
|
<url>https://github.com/nazoking</url>
|
||||||
|
</developer>
|
||||||
|
<developer>
|
||||||
|
<id>McFoggy</id>
|
||||||
|
<name>Matthieu Brouillard</name>
|
||||||
|
<url>https://github.com/McFoggy</url>
|
||||||
|
</developer>
|
||||||
|
</developers>
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,37 +1,54 @@
|
|||||||
Automatic Schema Updating
|
Automatic Schema Updating
|
||||||
========
|
========
|
||||||
GitBucket uses H2 database to manage project and account data. GitBucket updates database schema automatically in the first run after the upgrading.
|
GitBucket updates database schema automatically using [Solidbase](https://github.com/gitbucket/solidbase) in the first run after the upgrading.
|
||||||
|
|
||||||
To release a new version of GitBucket, add the version definition to the [gitbucket.core.servlet.AutoUpdate](https://github.com/gitbucket/gitbucket/blob/master/src/main/scala/gitbucket/core/servlet/AutoUpdate.scala) at first.
|
To release a new version of GitBucket, add the version definition to the [gitbucket.core.GitBucketCoreModule](https://github.com/gitbucket/gitbucket/blob/master/src/main/scala/gitbucket/core/GitBucketCoreModule.scala) at first.
|
||||||
|
|
||||||
```scala
|
```scala
|
||||||
object AutoUpdate {
|
object GitBucketCoreModule extends Module("gitbucket-core",
|
||||||
...
|
new Version("4.0.0",
|
||||||
/**
|
new LiquibaseMigration("update/gitbucket-core_4.0.xml"),
|
||||||
* The history of versions. A head of this sequence is the current GitBucket version.
|
new SqlMigration("update/gitbucket-core_4.0.sql")
|
||||||
*/
|
),
|
||||||
val versions = Seq(
|
new Version("4.1.0"),
|
||||||
Version(1, 0)
|
new Version("4.2.0",
|
||||||
|
new LiquibaseMigration("update/gitbucket-core_4.2.xml")
|
||||||
)
|
)
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
Next, add a SQL file which updates database schema into [/src/main/resources/update/](https://github.com/gitbucket/gitbucket/tree/master/src/main/resources/update) as ```MAJOR_MINOR.sql```.
|
|
||||||
|
|
||||||
GitBucket stores the current version to ```GITBUCKET_HOME/version``` and checks it at start-up. If the stored version differs from the actual version, it executes differences of SQL files between the stored version and the actual version. And ```GITBUCKET_HOME/version``` is updated by the actual version.
|
|
||||||
|
|
||||||
We can also add any Scala code for upgrade GitBucket which modifies resources other than database. Override ```Version.update``` like below:
|
|
||||||
|
|
||||||
```scala
|
|
||||||
val versions = Seq(
|
|
||||||
new Version(1, 3){
|
|
||||||
override def update(conn: Connection): Unit = {
|
|
||||||
super.update(conn)
|
|
||||||
// Add any code here!
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Version(1, 2),
|
|
||||||
Version(1, 1),
|
|
||||||
Version(1, 0)
|
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Next, add a XML file which updates database schema into [/src/main/resources/update/](https://github.com/gitbucket/gitbucket/tree/master/src/main/resources/update) with a filenane defined in `GitBucketCoreModule`.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<changeSet>
|
||||||
|
<addColumn tableName="REPOSITORY">
|
||||||
|
<column name="ENABLE_WIKI" type="boolean" nullable="false" defaultValueBoolean="true"/>
|
||||||
|
<column name="ENABLE_ISSUES" type="boolean" nullable="false" defaultValueBoolean="true"/>
|
||||||
|
<column name="EXTERNAL_WIKI_URL" type="varchar(200)" nullable="true"/>
|
||||||
|
<column name="EXTERNAL_ISSUES_URL" type="varchar(200)" nullable="true"/>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
```
|
||||||
|
|
||||||
|
Solidbase stores the current version to `VERSIONS` table and checks it at start-up. If the stored version differs from the actual version, it executes differences between the stored version and the actual version.
|
||||||
|
|
||||||
|
We can add the SQL file instead of the XML file using `SqlMigration`. It try to load a SQL file from classpath as following order:
|
||||||
|
|
||||||
|
1. Specified path (if specified)
|
||||||
|
2. `${moduleId}_${version}_${database}.sql`
|
||||||
|
3. `${moduleId}_${version}.sql`
|
||||||
|
|
||||||
|
Also we can add any code by extending `Migration`:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
object GitBucketCoreModule extends Module("gitbucket-core",
|
||||||
|
new Version("4.0.0", new Migration(){
|
||||||
|
override def migrate(moduleId: String, version: String, context: java.util.Map[String, String]): Unit = {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
See more details [README of Solidbase](https://github.com/gitbucket/solidbase).
|
||||||
|
|||||||
@@ -11,22 +11,24 @@ Note to update version number in files below:
|
|||||||
```scala
|
```scala
|
||||||
val Organization = "gitbucket"
|
val Organization = "gitbucket"
|
||||||
val Name = "gitbucket"
|
val Name = "gitbucket"
|
||||||
val GitBucketVersion = "3.12.0" // <---- update version!!
|
val GitBucketVersion = "4.0.0" // <---- update version!!
|
||||||
val ScalatraVersion = "2.4.0"
|
val ScalatraVersion = "2.4.0"
|
||||||
val JettyVersion = "9.3.6.v20151106"
|
val JettyVersion = "9.3.6.v20151106"
|
||||||
```
|
```
|
||||||
|
|
||||||
### src/main/scala/gitbucket/core/servlet/AutoUpdate.scala
|
### src/main/scala/gitbucket/core/GitBucketCoreModule.scala
|
||||||
|
|
||||||
```scala
|
```scala
|
||||||
object AutoUpdate {
|
object GitBucketCoreModule extends Module("gitbucket-core",
|
||||||
|
new Version("4.0.0",
|
||||||
/**
|
new LiquibaseMigration("update/gitbucket-core_4.0.xml"),
|
||||||
* The history of versions. A head of this sequence is the current GitBucket version.
|
new SqlMigration("update/gitbucket-core_4.0.sql")
|
||||||
*/
|
),
|
||||||
val versions = Seq(
|
// add new version definition
|
||||||
new Version(3, 12), // <---- add this line!!
|
new Version("4.1.0",
|
||||||
new Version(3, 11),
|
new LiquibaseMigration("update/gitbucket-core_4.1.xml")
|
||||||
|
)
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
Generate release files
|
Generate release files
|
||||||
@@ -39,14 +41,15 @@ Note: Release operation requires [Ant](http://ant.apache.org/) and [Maven](https
|
|||||||
Run `sbt executable`. The release war file and fingerprint are generated into `target/executable/gitbucket.war`.
|
Run `sbt executable`. The release war file and fingerprint are generated into `target/executable/gitbucket.war`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$sbt executable
|
$ sbt executable
|
||||||
```
|
```
|
||||||
|
|
||||||
### Deploy assembly jar file
|
### Deploy assembly jar file
|
||||||
|
|
||||||
For plug-in development, we have to publish the assembly jar file to the public Maven repository by `release/deploy-assembly-jar.sh`.
|
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
|
```bash
|
||||||
$ cd release/
|
$ sbt publish-signed
|
||||||
$ ./deploy-assembly-jar.sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Then operate release sequence at https://oss.sonatype.org/.
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
sbt.version=0.13.9
|
sbt.version=0.13.12
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.4")
|
|||||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.12.0")
|
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.12.0")
|
||||||
addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "2.1.0")
|
addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "2.1.0")
|
||||||
addSbtPlugin("fi.gekkio.sbtplugins" % "sbt-jrebel-plugin" % "0.10.0")
|
addSbtPlugin("fi.gekkio.sbtplugins" % "sbt-jrebel-plugin" % "0.10.0")
|
||||||
|
addSbtPlugin("com.typesafe.sbt" % "sbt-pgp" % "0.8.3")
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
. ./env.sh
|
|
||||||
|
|
||||||
cd ../
|
|
||||||
./sbt.sh clean assembly
|
|
||||||
|
|
||||||
cd release
|
|
||||||
|
|
||||||
if [[ "$GITBUCKET_VERSION" =~ -SNAPSHOT$ ]]; then
|
|
||||||
MVN_DEPLOY_PATH=mvn-snapshot
|
|
||||||
else
|
|
||||||
MVN_DEPLOY_PATH=mvn
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo $MVN_DEPLOY_PATH
|
|
||||||
|
|
||||||
mvn deploy:deploy-file \
|
|
||||||
-DgroupId=gitbucket\
|
|
||||||
-DartifactId=gitbucket-assembly\
|
|
||||||
-Dversion=$GITBUCKET_VERSION\
|
|
||||||
-Dpackaging=jar\
|
|
||||||
-Dfile=../target/scala-2.11/gitbucket-assembly-$GITBUCKET_VERSION.jar\
|
|
||||||
-DrepositoryId=sourceforge.jp\
|
|
||||||
-Durl=scp://shell.sourceforge.jp/home/groups/a/am/amateras/htdocs/$MVN_DEPLOY_PATH/
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
export GITBUCKET_VERSION=`cat ../build.sbt | grep 'val GitBucketVersion' | cut -d \" -f 2`
|
|
||||||
echo "GITBUCKET_VERSION: $GITBUCKET_VERSION"
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<?xml version="1.0"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<groupId>jp.sf.amateras</groupId>
|
|
||||||
<artifactId>gitbucket-assembly</artifactId>
|
|
||||||
<version>0.0.1</version>
|
|
||||||
<build>
|
|
||||||
<extensions>
|
|
||||||
<extension>
|
|
||||||
<groupId>org.apache.maven.wagon</groupId>
|
|
||||||
<artifactId>wagon-ssh</artifactId>
|
|
||||||
<version>1.0-beta-6</version>
|
|
||||||
</extension>
|
|
||||||
</extensions>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
||||||
Binary file not shown.
2
sbt.bat
2
sbt.bat
@@ -1,2 +1,2 @@
|
|||||||
set SCRIPT_DIR=%~dp0
|
set SCRIPT_DIR=%~dp0
|
||||||
java %JAVA_OPTS% -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar "%SCRIPT_DIR%\sbt-launch-0.13.9.jar" %*
|
java %JAVA_OPTS% -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar "%SCRIPT_DIR%\sbt-launch-0.13.12.jar" %*
|
||||||
|
|||||||
2
sbt.sh
2
sbt.sh
@@ -1,2 +1,2 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
java $JAVA_OPTS -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar `dirname $0`/sbt-launch-0.13.9.jar "$@"
|
java $JAVA_OPTS -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar `dirname $0`/sbt-launch-0.13.12.jar "$@"
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import org.eclipse.jetty.webapp.WebAppContext;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
import java.security.ProtectionDomain;
|
import java.security.ProtectionDomain;
|
||||||
|
|
||||||
public class JettyLauncher {
|
public class JettyLauncher {
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
String host = null;
|
String host = null;
|
||||||
int port = 8080;
|
int port = 8080;
|
||||||
|
InetSocketAddress address = null;
|
||||||
String contextPath = "/";
|
String contextPath = "/";
|
||||||
boolean forceHttps = false;
|
boolean forceHttps = false;
|
||||||
|
|
||||||
@@ -29,7 +31,13 @@ public class JettyLauncher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Server server = new Server(port);
|
if(host != null) {
|
||||||
|
address = new InetSocketAddress(host, port);
|
||||||
|
} else {
|
||||||
|
address = new InetSocketAddress(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
Server server = new Server(address);
|
||||||
|
|
||||||
// SelectChannelConnector connector = new SelectChannelConnector();
|
// SelectChannelConnector connector = new SelectChannelConnector();
|
||||||
// if(host != null) {
|
// if(host != null) {
|
||||||
@@ -43,10 +51,9 @@ public class JettyLauncher {
|
|||||||
WebAppContext context = new WebAppContext();
|
WebAppContext context = new WebAppContext();
|
||||||
|
|
||||||
File tmpDir = new File(getGitBucketHome(), "tmp");
|
File tmpDir = new File(getGitBucketHome(), "tmp");
|
||||||
if(tmpDir.exists()){
|
if(!tmpDir.exists()){
|
||||||
deleteDirectory(tmpDir);
|
|
||||||
}
|
|
||||||
tmpDir.mkdirs();
|
tmpDir.mkdirs();
|
||||||
|
}
|
||||||
context.setTempDirectory(tmpDir);
|
context.setTempDirectory(tmpDir);
|
||||||
|
|
||||||
ProtectionDomain domain = JettyLauncher.class.getProtectionDomain();
|
ProtectionDomain domain = JettyLauncher.class.getProtectionDomain();
|
||||||
@@ -61,6 +68,8 @@ public class JettyLauncher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
server.setHandler(context);
|
server.setHandler(context);
|
||||||
|
server.setStopAtShutdown(true);
|
||||||
|
server.setStopTimeout(7_000);
|
||||||
server.start();
|
server.start();
|
||||||
server.join();
|
server.join();
|
||||||
}
|
}
|
||||||
|
|||||||
52
src/main/java/org/postgresql/Driver2.java
Normal file
52
src/main/java/org/postgresql/Driver2.java
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package org.postgresql;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationHandler;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps the PostgreSQL JDBC driver to convert the returning column names to lower case.
|
||||||
|
*/
|
||||||
|
public class Driver2 extends Driver {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public java.sql.Connection connect(String url, Properties info) throws SQLException {
|
||||||
|
Connection conn = super.connect(url, info);
|
||||||
|
|
||||||
|
Object proxy = Proxy.newProxyInstance(
|
||||||
|
conn.getClass().getClassLoader(),
|
||||||
|
new Class[]{ Connection.class },
|
||||||
|
new ConnectionProxyHandler(conn)
|
||||||
|
);
|
||||||
|
|
||||||
|
return Connection.class.cast(proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static class ConnectionProxyHandler implements InvocationHandler {
|
||||||
|
|
||||||
|
private Connection conn;
|
||||||
|
|
||||||
|
public ConnectionProxyHandler(Connection conn){
|
||||||
|
this.conn = conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||||
|
if(method.getName().equals("prepareStatement")){
|
||||||
|
if(args != null && args.length == 2 && args[1].getClass().isArray()){
|
||||||
|
String[] keys = (String[]) args[1];
|
||||||
|
for(int i = 0; i < keys.length; i++){
|
||||||
|
keys[i] = keys[i].toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return method.invoke(conn, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
db {
|
|
||||||
driver = "org.h2.Driver"
|
|
||||||
url = "jdbc:h2:${DatabaseHome};MVCC=true"
|
|
||||||
user = "sa"
|
|
||||||
password = "sa"
|
|
||||||
}
|
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<!--
|
<!--
|
||||||
<logger name="service.WebHookService" level="DEBUG" />
|
<logger name="service.WebHookService" level="DEBUG" />
|
||||||
<logger name="servlet" level="DEBUG" />
|
<logger name="servlet" level="DEBUG" />
|
||||||
-->
|
|
||||||
<logger name="scala.slick.jdbc.JdbcBackend.statement" level="DEBUG" />
|
<logger name="scala.slick.jdbc.JdbcBackend.statement" level="DEBUG" />
|
||||||
|
-->
|
||||||
|
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
c3p0 {
|
|
||||||
privilegeSpawnedThreads=true
|
|
||||||
contextClassLoaderSource=library
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
CREATE TABLE ACCOUNT(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
MAIL_ADDRESS VARCHAR(100) NOT NULL,
|
|
||||||
PASSWORD VARCHAR(40) NOT NULL,
|
|
||||||
ADMINISTRATOR BOOLEAN NOT NULL,
|
|
||||||
URL VARCHAR(200),
|
|
||||||
REGISTERED_DATE TIMESTAMP NOT NULL,
|
|
||||||
UPDATED_DATE TIMESTAMP NOT NULL,
|
|
||||||
LAST_LOGIN_DATE TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE REPOSITORY(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
PRIVATE BOOLEAN NOT NULL,
|
|
||||||
DESCRIPTION TEXT,
|
|
||||||
DEFAULT_BRANCH VARCHAR(100),
|
|
||||||
REGISTERED_DATE TIMESTAMP NOT NULL,
|
|
||||||
UPDATED_DATE TIMESTAMP NOT NULL,
|
|
||||||
LAST_ACTIVITY_DATE TIMESTAMP NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE COLLABORATOR(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
COLLABORATOR_NAME VARCHAR(100) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE ISSUE(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
ISSUE_ID INT NOT NULL,
|
|
||||||
OPENED_USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
MILESTONE_ID INT,
|
|
||||||
ASSIGNED_USER_NAME VARCHAR(100),
|
|
||||||
TITLE TEXT NOT NULL,
|
|
||||||
CONTENT TEXT,
|
|
||||||
CLOSED BOOLEAN NOT NULL,
|
|
||||||
REGISTERED_DATE TIMESTAMP NOT NULL,
|
|
||||||
UPDATED_DATE TIMESTAMP NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE ISSUE_ID(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
ISSUE_ID INT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE ISSUE_COMMENT(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
ISSUE_ID INT NOT NULL,
|
|
||||||
COMMENT_ID INT AUTO_INCREMENT,
|
|
||||||
ACTION VARCHAR(10),
|
|
||||||
COMMENTED_USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
CONTENT TEXT NOT NULL,
|
|
||||||
REGISTERED_DATE TIMESTAMP NOT NULL,
|
|
||||||
UPDATED_DATE TIMESTAMP NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE LABEL(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
LABEL_ID INT AUTO_INCREMENT,
|
|
||||||
LABEL_NAME VARCHAR(100) NOT NULL,
|
|
||||||
COLOR CHAR(6) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE ISSUE_LABEL(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
ISSUE_ID INT NOT NULL,
|
|
||||||
LABEL_ID INT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE MILESTONE(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
MILESTONE_ID INT AUTO_INCREMENT,
|
|
||||||
TITLE VARCHAR(100) NOT NULL,
|
|
||||||
DESCRIPTION TEXT,
|
|
||||||
DUE_DATE TIMESTAMP,
|
|
||||||
CLOSED_DATE TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE ACCOUNT ADD CONSTRAINT IDX_ACCOUNT_PK PRIMARY KEY (USER_NAME);
|
|
||||||
ALTER TABLE ACCOUNT ADD CONSTRAINT IDX_ACCOUNT_1 UNIQUE (MAIL_ADDRESS);
|
|
||||||
|
|
||||||
ALTER TABLE REPOSITORY ADD CONSTRAINT IDX_REPOSITORY_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE REPOSITORY ADD CONSTRAINT IDX_REPOSITORY_FK0 FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME);
|
|
||||||
|
|
||||||
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_FK1 FOREIGN KEY (COLLABORATOR_NAME) REFERENCES ACCOUNT (USER_NAME);
|
|
||||||
|
|
||||||
ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_PK PRIMARY KEY (ISSUE_ID, USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK1 FOREIGN KEY (OPENED_USER_NAME) REFERENCES ACCOUNT (USER_NAME);
|
|
||||||
ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK2 FOREIGN KEY (MILESTONE_ID) REFERENCES MILESTONE (MILESTONE_ID);
|
|
||||||
|
|
||||||
ALTER TABLE ISSUE_ID ADD CONSTRAINT IDX_ISSUE_ID_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE ISSUE_ID ADD CONSTRAINT IDX_ISSUE_ID_FK1 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
|
|
||||||
ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_PK PRIMARY KEY (COMMENT_ID);
|
|
||||||
ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_1 UNIQUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID, COMMENT_ID);
|
|
||||||
ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID) REFERENCES ISSUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID);
|
|
||||||
|
|
||||||
ALTER TABLE LABEL ADD CONSTRAINT IDX_LABEL_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, LABEL_ID);
|
|
||||||
ALTER TABLE LABEL ADD CONSTRAINT IDX_LABEL_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
|
|
||||||
ALTER TABLE ISSUE_LABEL ADD CONSTRAINT IDX_ISSUE_LABEL_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID, LABEL_ID);
|
|
||||||
ALTER TABLE ISSUE_LABEL ADD CONSTRAINT IDX_ISSUE_LABEL_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID) REFERENCES ISSUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID);
|
|
||||||
|
|
||||||
ALTER TABLE MILESTONE ADD CONSTRAINT IDX_MILESTONE_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, MILESTONE_ID);
|
|
||||||
ALTER TABLE MILESTONE ADD CONSTRAINT IDX_MILESTONE_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
|
|
||||||
INSERT INTO ACCOUNT (
|
|
||||||
USER_NAME,
|
|
||||||
MAIL_ADDRESS,
|
|
||||||
PASSWORD,
|
|
||||||
ADMINISTRATOR,
|
|
||||||
URL,
|
|
||||||
REGISTERED_DATE,
|
|
||||||
UPDATED_DATE,
|
|
||||||
LAST_LOGIN_DATE
|
|
||||||
) VALUES (
|
|
||||||
'root',
|
|
||||||
'root@localhost',
|
|
||||||
'dc76e9f0c0006e8f919e0c515c66dbba3982f785',
|
|
||||||
true,
|
|
||||||
'https://github.com/gitbucket/gitbucket',
|
|
||||||
SYSDATE,
|
|
||||||
SYSDATE,
|
|
||||||
NULL
|
|
||||||
);
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
-- Fix COLLABORATOR constraints
|
|
||||||
ALTER TABLE COLLABORATOR DROP CONSTRAINT IDX_COLLABORATOR_FK1 IF EXISTS;
|
|
||||||
ALTER TABLE COLLABORATOR DROP CONSTRAINT IDX_COLLABORATOR_FK0 IF EXISTS;
|
|
||||||
ALTER TABLE COLLABORATOR DROP CONSTRAINT IDX_COLLABORATOR_PK IF EXISTS;
|
|
||||||
|
|
||||||
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, COLLABORATOR_NAME);
|
|
||||||
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_FK1 FOREIGN KEY (COLLABORATOR_NAME) REFERENCES ACCOUNT (USER_NAME);
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
ALTER TABLE GROUP_MEMBER ADD COLUMN MANAGER BOOLEAN DEFAULT FALSE;
|
|
||||||
|
|
||||||
CREATE TABLE SSH_KEY (
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
SSH_KEY_ID INT AUTO_INCREMENT,
|
|
||||||
TITLE VARCHAR(100) NOT NULL,
|
|
||||||
PUBLIC_KEY TEXT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE SSH_KEY ADD CONSTRAINT IDX_SSH_KEY_PK PRIMARY KEY (USER_NAME, SSH_KEY_ID);
|
|
||||||
ALTER TABLE SSH_KEY ADD CONSTRAINT IDX_SSH_KEY_FK0 FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME);
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
DROP TABLE COMMIT_LOG;
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
CREATE TABLE ACTIVITY(
|
|
||||||
ACTIVITY_ID INT AUTO_INCREMENT,
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
ACTIVITY_USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
ACTIVITY_TYPE VARCHAR(100) NOT NULL,
|
|
||||||
MESSAGE TEXT NOT NULL,
|
|
||||||
ADDITIONAL_INFO TEXT,
|
|
||||||
ACTIVITY_DATE TIMESTAMP NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE COMMIT_LOG (
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
COMMIT_ID VARCHAR(40) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE ACTIVITY ADD CONSTRAINT IDX_ACTIVITY_PK PRIMARY KEY (ACTIVITY_ID);
|
|
||||||
ALTER TABLE ACTIVITY ADD CONSTRAINT IDX_ACTIVITY_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE ACTIVITY ADD CONSTRAINT IDX_ACTIVITY_FK1 FOREIGN KEY (ACTIVITY_USER_NAME) REFERENCES ACCOUNT (USER_NAME);
|
|
||||||
|
|
||||||
ALTER TABLE COMMIT_LOG ADD CONSTRAINT IDX_COMMIT_LOG_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, COMMIT_ID);
|
|
||||||
ALTER TABLE COMMIT_LOG ADD CONSTRAINT IDX_COMMIT_LOG_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
ALTER TABLE ACCOUNT ADD COLUMN IMAGE VARCHAR(100);
|
|
||||||
|
|
||||||
UPDATE ISSUE_COMMENT SET ACTION = 'comment' WHERE ACTION IS NULL;
|
|
||||||
|
|
||||||
ALTER TABLE ISSUE_COMMENT ALTER COLUMN ACTION VARCHAR(20) NOT NULL;
|
|
||||||
|
|
||||||
UPDATE ISSUE_COMMENT SET ACTION = 'close_comment' WHERE ACTION = 'close';
|
|
||||||
UPDATE ISSUE_COMMENT SET ACTION = 'reopen_comment' WHERE ACTION = 'reopen';
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
CREATE TABLE GROUP_MEMBER(
|
|
||||||
GROUP_NAME VARCHAR(100) NOT NULL,
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE GROUP_MEMBER ADD CONSTRAINT IDX_GROUP_MEMBER_PK PRIMARY KEY (GROUP_NAME, USER_NAME);
|
|
||||||
ALTER TABLE GROUP_MEMBER ADD CONSTRAINT IDX_GROUP_MEMBER_FK0 FOREIGN KEY (GROUP_NAME) REFERENCES ACCOUNT (USER_NAME);
|
|
||||||
ALTER TABLE GROUP_MEMBER ADD CONSTRAINT IDX_GROUP_MEMBER_FK1 FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME);
|
|
||||||
|
|
||||||
ALTER TABLE ACCOUNT ADD COLUMN GROUP_ACCOUNT BOOLEAN NOT NULL DEFAULT FALSE;
|
|
||||||
|
|
||||||
CREATE OR REPLACE VIEW ISSUE_OUTLINE_VIEW AS
|
|
||||||
SELECT
|
|
||||||
A.USER_NAME,
|
|
||||||
A.REPOSITORY_NAME,
|
|
||||||
A.ISSUE_ID,
|
|
||||||
NVL(B.COMMENT_COUNT, 0) AS COMMENT_COUNT
|
|
||||||
FROM ISSUE A
|
|
||||||
LEFT OUTER JOIN (
|
|
||||||
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM ISSUE_COMMENT
|
|
||||||
WHERE ACTION IN ('comment', 'close_comment', 'reopen_comment')
|
|
||||||
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
|
|
||||||
) B
|
|
||||||
ON (A.USER_NAME = B.USER_NAME AND A.REPOSITORY_NAME = B.REPOSITORY_NAME AND A.ISSUE_ID = B.ISSUE_ID);
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
ALTER TABLE REPOSITORY ADD COLUMN ORIGIN_USER_NAME VARCHAR(100);
|
|
||||||
ALTER TABLE REPOSITORY ADD COLUMN ORIGIN_REPOSITORY_NAME VARCHAR(100);
|
|
||||||
ALTER TABLE REPOSITORY ADD COLUMN PARENT_USER_NAME VARCHAR(100);
|
|
||||||
ALTER TABLE REPOSITORY ADD COLUMN PARENT_REPOSITORY_NAME VARCHAR(100);
|
|
||||||
|
|
||||||
CREATE TABLE PULL_REQUEST(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
ISSUE_ID INT NOT NULL,
|
|
||||||
BRANCH VARCHAR(100) NOT NULL,
|
|
||||||
REQUEST_USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REQUEST_REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REQUEST_BRANCH VARCHAR(100) NOT NULL,
|
|
||||||
COMMIT_ID_FROM VARCHAR(40) NOT NULL,
|
|
||||||
COMMIT_ID_TO VARCHAR(40) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE PULL_REQUEST ADD CONSTRAINT IDX_PULL_REQUEST_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID);
|
|
||||||
ALTER TABLE PULL_REQUEST ADD CONSTRAINT IDX_PULL_REQUEST_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID) REFERENCES ISSUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID);
|
|
||||||
|
|
||||||
ALTER TABLE ISSUE ADD COLUMN PULL_REQUEST BOOLEAN NOT NULL DEFAULT FALSE;
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
CREATE TABLE WEB_HOOK (
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
URL VARCHAR(200) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE WEB_HOOK ADD CONSTRAINT IDX_WEB_HOOK_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, URL);
|
|
||||||
ALTER TABLE WEB_HOOK ADD CONSTRAINT IDX_WEB_HOOK_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
ALTER TABLE ACCOUNT ADD COLUMN FULL_NAME VARCHAR(100);
|
|
||||||
|
|
||||||
UPDATE ACCOUNT SET FULL_NAME = USER_NAME WHERE FULL_NAME IS NULL;
|
|
||||||
|
|
||||||
ALTER TABLE ACCOUNT ALTER COLUMN FULL_NAME SET NOT NULL;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE ACCOUNT ADD COLUMN REMOVED BOOLEAN DEFAULT FALSE;
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
CREATE TABLE PLUGIN (
|
|
||||||
PLUGIN_ID VARCHAR(100) NOT NULL,
|
|
||||||
VERSION VARCHAR(100) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE PLUGIN ADD CONSTRAINT IDX_PLUGIN_PK PRIMARY KEY (PLUGIN_ID);
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
CREATE TABLE COMMIT_COMMENT (
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
COMMIT_ID VARCHAR(100) NOT NULL,
|
|
||||||
COMMENT_ID INT AUTO_INCREMENT,
|
|
||||||
COMMENTED_USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
CONTENT TEXT NOT NULL,
|
|
||||||
FILE_NAME NVARCHAR(100),
|
|
||||||
OLD_LINE_NUMBER INT,
|
|
||||||
NEW_LINE_NUMBER INT,
|
|
||||||
REGISTERED_DATE TIMESTAMP NOT NULL,
|
|
||||||
UPDATED_DATE TIMESTAMP NOT NULL,
|
|
||||||
PULL_REQUEST BOOLEAN NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE COMMIT_COMMENT ADD CONSTRAINT IDX_COMMIT_COMMENT_PK PRIMARY KEY (COMMENT_ID);
|
|
||||||
ALTER TABLE COMMIT_COMMENT ADD CONSTRAINT IDX_COMMIT_COMMENT_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE COMMIT_COMMENT ADD CONSTRAINT IDX_COMMIT_COMMENT_1 UNIQUE (USER_NAME, REPOSITORY_NAME, COMMIT_ID, COMMENT_ID);
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE COMMIT_COMMENT ALTER COLUMN FILE_NAME NVARCHAR(260);
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
DROP TABLE IF EXISTS ACCESS_TOKEN;
|
|
||||||
|
|
||||||
CREATE TABLE ACCESS_TOKEN (
|
|
||||||
ACCESS_TOKEN_ID INT NOT NULL AUTO_INCREMENT,
|
|
||||||
TOKEN_HASH VARCHAR(40) NOT NULL,
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
NOTE TEXT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE ACCESS_TOKEN ADD CONSTRAINT IDX_ACCESS_TOKEN_PK PRIMARY KEY (ACCESS_TOKEN_ID);
|
|
||||||
ALTER TABLE ACCESS_TOKEN ADD CONSTRAINT IDX_ACCESS_TOKEN_FK0 FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME)
|
|
||||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
ALTER TABLE ACCESS_TOKEN ADD CONSTRAINT IDX_ACCESS_TOKEN_TOKEN_HASH UNIQUE(TOKEN_HASH);
|
|
||||||
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS COMMIT_STATUS;
|
|
||||||
CREATE TABLE COMMIT_STATUS(
|
|
||||||
COMMIT_STATUS_ID INT AUTO_INCREMENT,
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
COMMIT_ID VARCHAR(40) NOT NULL,
|
|
||||||
CONTEXT VARCHAR(255) NOT NULL, -- context is too long (maximum is 255 characters)
|
|
||||||
STATE VARCHAR(10) NOT NULL, -- pending, success, error, or failure
|
|
||||||
TARGET_URL VARCHAR(200),
|
|
||||||
DESCRIPTION TEXT,
|
|
||||||
CREATOR VARCHAR(100) NOT NULL,
|
|
||||||
REGISTERED_DATE TIMESTAMP NOT NULL, -- CREATED_AT
|
|
||||||
UPDATED_DATE TIMESTAMP NOT NULL -- UPDATED_AT
|
|
||||||
);
|
|
||||||
ALTER TABLE COMMIT_STATUS ADD CONSTRAINT IDX_COMMIT_STATUS_PK PRIMARY KEY (COMMIT_STATUS_ID);
|
|
||||||
ALTER TABLE COMMIT_STATUS ADD CONSTRAINT IDX_COMMIT_STATUS_1
|
|
||||||
UNIQUE (USER_NAME, REPOSITORY_NAME, COMMIT_ID, CONTEXT);
|
|
||||||
ALTER TABLE COMMIT_STATUS ADD CONSTRAINT IDX_COMMIT_STATUS_FK1
|
|
||||||
FOREIGN KEY (USER_NAME, REPOSITORY_NAME)
|
|
||||||
REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME)
|
|
||||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
ALTER TABLE COMMIT_STATUS ADD CONSTRAINT IDX_COMMIT_STATUS_FK2
|
|
||||||
FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME)
|
|
||||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
ALTER TABLE COMMIT_STATUS ADD CONSTRAINT IDX_COMMIT_STATUS_FK3
|
|
||||||
FOREIGN KEY (CREATOR) REFERENCES ACCOUNT (USER_NAME)
|
|
||||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
DROP TABLE IF EXISTS PROTECTED_BRANCH;
|
|
||||||
|
|
||||||
CREATE TABLE PROTECTED_BRANCH(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
BRANCH VARCHAR(100) NOT NULL,
|
|
||||||
STATUS_CHECK_ADMIN BOOLEAN NOT NULL DEFAULT false
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE PROTECTED_BRANCH ADD CONSTRAINT IDX_PROTECTED_BRANCH_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, BRANCH);
|
|
||||||
ALTER TABLE PROTECTED_BRANCH ADD CONSTRAINT IDX_PROTECTED_BRANCH_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME)
|
|
||||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS PROTECTED_BRANCH_REQUIRE_CONTEXT;
|
|
||||||
CREATE TABLE PROTECTED_BRANCH_REQUIRE_CONTEXT(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
BRANCH VARCHAR(100) NOT NULL,
|
|
||||||
CONTEXT VARCHAR(255) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE PROTECTED_BRANCH_REQUIRE_CONTEXT ADD CONSTRAINT IDX_PROTECTED_BRANCH_REQUIRE_CONTEXT_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, BRANCH, CONTEXT);
|
|
||||||
ALTER TABLE PROTECTED_BRANCH_REQUIRE_CONTEXT ADD CONSTRAINT IDX_PROTECTED_BRANCH_REQUIRE_CONTEXT_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, BRANCH) REFERENCES PROTECTED_BRANCH (USER_NAME, REPOSITORY_NAME, BRANCH)
|
|
||||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE WEB_HOOK ADD COLUMN TOKEN VARCHAR(100);
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
DROP TABLE IF EXISTS WEB_HOOK_EVENT;
|
|
||||||
|
|
||||||
CREATE TABLE WEB_HOOK_EVENT(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
URL VARCHAR(200) NOT NULL,
|
|
||||||
EVENT VARCHAR(30) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE WEB_HOOK_EVENT ADD CONSTRAINT IDX_WEB_HOOK_EVENT_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, URL, EVENT);
|
|
||||||
ALTER TABLE WEB_HOOK_EVENT ADD CONSTRAINT IDX_WEB_HOOK_EVENT_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, URL) REFERENCES WEB_HOOK (USER_NAME, REPOSITORY_NAME, URL)
|
|
||||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
CREATE TEMPORARY TABLE TMP_EVENTS (EVENT VARCHAR(30));
|
|
||||||
|
|
||||||
INSERT INTO TMP_EVENTS VALUES ('push'),('issue_comment'),('issues'),('pull_request');
|
|
||||||
|
|
||||||
INSERT INTO WEB_HOOK_EVENT (USER_NAME, REPOSITORY_NAME, URL, EVENT)
|
|
||||||
SELECT USER_NAME, REPOSITORY_NAME, URL, EVENT
|
|
||||||
FROM WEB_HOOK, TMP_EVENTS;
|
|
||||||
|
|
||||||
DROP TABLE TMP_EVENTS;
|
|
||||||
|
|
||||||
ALTER TABLE COMMIT_COMMENT ADD COLUMN ISSUE_ID INT;
|
|
||||||
|
|
||||||
CREATE OR REPLACE VIEW ISSUE_OUTLINE_VIEW AS
|
|
||||||
SELECT
|
|
||||||
A.USER_NAME,
|
|
||||||
A.REPOSITORY_NAME,
|
|
||||||
A.ISSUE_ID,
|
|
||||||
NVL(B.COMMENT_COUNT, 0) + NVL(C.COMMENT_COUNT, 0) AS COMMENT_COUNT
|
|
||||||
FROM ISSUE A
|
|
||||||
LEFT OUTER JOIN (
|
|
||||||
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM ISSUE_COMMENT
|
|
||||||
WHERE ACTION IN ('comment', 'close_comment', 'reopen_comment')
|
|
||||||
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
|
|
||||||
) B
|
|
||||||
ON (A.USER_NAME = B.USER_NAME AND A.REPOSITORY_NAME = B.REPOSITORY_NAME AND A.ISSUE_ID = B.ISSUE_ID)
|
|
||||||
LEFT OUTER JOIN (
|
|
||||||
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM COMMIT_COMMENT
|
|
||||||
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
|
|
||||||
) C
|
|
||||||
ON (A.USER_NAME = C.USER_NAME AND A.REPOSITORY_NAME = C.REPOSITORY_NAME AND A.ISSUE_ID = C.ISSUE_ID);
|
|
||||||
|
|
||||||
|
|
||||||
UPDATE COMMIT_COMMENT C SET (ISSUE_ID) = (
|
|
||||||
SELECT MAX(P.ISSUE_ID)
|
|
||||||
FROM PULL_REQUEST P
|
|
||||||
WHERE
|
|
||||||
C.USER_NAME = P.USER_NAME AND
|
|
||||||
C.REPOSITORY_NAME = P.REPOSITORY_NAME AND
|
|
||||||
C.COMMIT_ID = P.COMMIT_ID_TO
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE COMMIT_COMMENT DROP COLUMN PULL_REQUEST;
|
|
||||||
18
src/main/resources/update/gitbucket-core_4.0.sql
Normal file
18
src/main/resources/update/gitbucket-core_4.0.sql
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
CREATE OR REPLACE VIEW ISSUE_OUTLINE_VIEW AS
|
||||||
|
SELECT
|
||||||
|
A.USER_NAME,
|
||||||
|
A.REPOSITORY_NAME,
|
||||||
|
A.ISSUE_ID,
|
||||||
|
COALESCE(B.COMMENT_COUNT, 0) + COALESCE(C.COMMENT_COUNT, 0) AS COMMENT_COUNT
|
||||||
|
FROM ISSUE A
|
||||||
|
LEFT OUTER JOIN (
|
||||||
|
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM ISSUE_COMMENT
|
||||||
|
WHERE ACTION IN ('comment', 'close_comment', 'reopen_comment')
|
||||||
|
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
|
||||||
|
) B
|
||||||
|
ON (A.USER_NAME = B.USER_NAME AND A.REPOSITORY_NAME = B.REPOSITORY_NAME AND A.ISSUE_ID = B.ISSUE_ID)
|
||||||
|
LEFT OUTER JOIN (
|
||||||
|
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM COMMIT_COMMENT
|
||||||
|
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
|
||||||
|
) C
|
||||||
|
ON (A.USER_NAME = C.USER_NAME AND A.REPOSITORY_NAME = C.REPOSITORY_NAME AND A.ISSUE_ID = C.ISSUE_ID);
|
||||||
351
src/main/resources/update/gitbucket-core_4.0.xml
Normal file
351
src/main/resources/update/gitbucket-core_4.0.xml
Normal file
@@ -0,0 +1,351 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<changeSet>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- ACCOUNT -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="ACCOUNT">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="MAIL_ADDRESS" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="PASSWORD" type="varchar(40)" nullable="false"/>
|
||||||
|
<column name="ADMINISTRATOR" type="boolean" nullable="false"/>
|
||||||
|
<column name="URL" type="varchar(200)" nullable="true"/>
|
||||||
|
<column name="REGISTERED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="LAST_LOGIN_DATE" type="datetime" nullable="true"/>
|
||||||
|
<column name="IMAGE" type="varchar(100)" nullable="true"/>
|
||||||
|
<column name="GROUP_ACCOUNT" type="boolean" nullable="false"/>
|
||||||
|
<column name="FULL_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REMOVED" type="boolean" nullable="true"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_ACCOUNT_PK" tableName="ACCOUNT" columnNames="USER_NAME"/>
|
||||||
|
<addUniqueConstraint constraintName="IDX_ACCOUNT_1" tableName="ACCOUNT" columnNames="MAIL_ADDRESS"/>
|
||||||
|
|
||||||
|
<insert tableName="ACCOUNT">
|
||||||
|
<column name="USER_NAME" value="root"/>
|
||||||
|
<column name="FULL_NAME" value="root"/>
|
||||||
|
<column name="MAIL_ADDRESS" value="root@localhost"/>
|
||||||
|
<column name="PASSWORD" value="dc76e9f0c0006e8f919e0c515c66dbba3982f785"/>
|
||||||
|
<column name="ADMINISTRATOR" valueBoolean="true"/>
|
||||||
|
<column name="URL" value="https://github.com/gitbucket/gitbucket"/>
|
||||||
|
<column name="GROUP_ACCOUNT" valueBoolean="false"/>
|
||||||
|
<column name="REMOVED" valueBoolean="false"/>
|
||||||
|
<column name="REGISTERED_DATE" valueDate="${currentDateTime}"/>
|
||||||
|
<column name="UPDATED_DATE" valueDate="${currentDateTime}"/>
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- REPOSITORY -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="REPOSITORY">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="PRIVATE" type="boolean" nullable="false"/>
|
||||||
|
<column name="DESCRIPTION" type="text" nullable="true"/>
|
||||||
|
<column name="DEFAULT_BRANCH" type="varchar(100)" nullable="true"/>
|
||||||
|
<column name="REGISTERED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="LAST_ACTIVITY_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="ORIGIN_USER_NAME" type="varchar(100)" nullable="true"/>
|
||||||
|
<column name="ORIGIN_REPOSITORY_NAME" type="varchar(100)" nullable="true"/>
|
||||||
|
<column name="PARENT_USER_NAME" type="varchar(100)" nullable="true"/>
|
||||||
|
<column name="PARENT_REPOSITORY_NAME" type="varchar(100)" nullable="true"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_REPOSITORY_PK" tableName="REPOSITORY" columnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_REPOSITORY_FK0" baseTableName="REPOSITORY" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- ACCESS_TOKEN -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="ACCESS_TOKEN">
|
||||||
|
<column name="ACCESS_TOKEN_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="TOKEN_HASH" type="varchar(40)" nullable="false"/>
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="NOTE" type="text" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_ACCESS_TOKEN_PK" tableName="ACCESS_TOKEN" columnNames="ACCESS_TOKEN_ID"/>
|
||||||
|
<addUniqueConstraint constraintName="IDX_ACCESS_TOKEN_TOKEN_HASH" tableName="ACCESS_TOKEN" columnNames="TOKEN_HASH"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ACCESS_TOKEN_FK0" baseTableName="ACCESS_TOKEN" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- ACTIVITY -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="ACTIVITY">
|
||||||
|
<column name="ACTIVITY_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="ACTIVITY_USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="ACTIVITY_TYPE" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="MESSAGE" type="text" nullable="false"/>
|
||||||
|
<column name="ADDITIONAL_INFO" type="text" nullable="true"/>
|
||||||
|
<column name="ACTIVITY_DATE" type="datetime" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_ACTIVITY_PK" tableName="ACTIVITY" columnNames="ACTIVITY_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ACTIVITY_FK1" baseTableName="ACTIVITY" baseColumnNames="ACTIVITY_USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ACTIVITY_FK0" baseTableName="ACTIVITY" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- COLLABORATOR -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="COLLABORATOR">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="COLLABORATOR_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_COLLABORATOR_PK" tableName="COLLABORATOR" columnNames="USER_NAME, REPOSITORY_NAME, COLLABORATOR_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_COLLABORATOR_FK1" baseTableName="COLLABORATOR" baseColumnNames="COLLABORATOR_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_COLLABORATOR_FK0" baseTableName="COLLABORATOR" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- COMMIT_COMMENT -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="COMMIT_COMMENT">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="COMMIT_ID" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="COMMENT_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="COMMENTED_USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="CONTENT" type="text" nullable="false"/>
|
||||||
|
<column name="FILE_NAME" type="varchar(260)" nullable="true"/>
|
||||||
|
<column name="OLD_LINE_NUMBER" type="int" nullable="true"/>
|
||||||
|
<column name="NEW_LINE_NUMBER" type="int" nullable="true"/>
|
||||||
|
<column name="REGISTERED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="ISSUE_ID" type="int" nullable="true"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_COMMIT_COMMENT_PK" tableName="COMMIT_COMMENT" columnNames="COMMENT_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_COMMIT_COMMENT_FK0" baseTableName="COMMIT_COMMENT" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- COMMIT_STATUS -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="COMMIT_STATUS">
|
||||||
|
<column name="COMMIT_STATUS_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="COMMIT_ID" type="varchar(40)" nullable="false"/>
|
||||||
|
<column name="CONTEXT" type="varchar(255)" nullable="false"/>
|
||||||
|
<column name="STATE" type="varchar(10)" nullable="false"/>
|
||||||
|
<column name="TARGET_URL" type="varchar(200)" nullable="true"/>
|
||||||
|
<column name="DESCRIPTION" type="text" nullable="true"/>
|
||||||
|
<column name="CREATOR" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REGISTERED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_COMMIT_STATUS_PK" tableName="COMMIT_STATUS" columnNames="COMMIT_STATUS_ID"/>
|
||||||
|
<addUniqueConstraint constraintName="IDX_COMMIT_STATUS_1" tableName="COMMIT_STATUS" columnNames="USER_NAME, REPOSITORY_NAME, COMMIT_ID, CONTEXT"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_COMMIT_STATUS_FK3" baseTableName="COMMIT_STATUS" baseColumnNames="CREATOR" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_COMMIT_STATUS_FK2" baseTableName="COMMIT_STATUS" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_COMMIT_STATUS_FK1" baseTableName="COMMIT_STATUS" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- GROUP_MEMBER -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="GROUP_MEMBER">
|
||||||
|
<column name="GROUP_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="MANAGER" type="boolean" nullable="true"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_GROUP_MEMBER_PK" tableName="GROUP_MEMBER" columnNames="GROUP_NAME, USER_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_GROUP_MEMBER_FK1" baseTableName="GROUP_MEMBER" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_GROUP_MEMBER_FK0" baseTableName="GROUP_MEMBER" baseColumnNames="GROUP_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- LABEL -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="LABEL">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="LABEL_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="LABEL_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="COLOR" type="char(6)" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_LABEL_PK" tableName="LABEL" columnNames="USER_NAME, REPOSITORY_NAME, LABEL_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_LABEL_FK0" baseTableName="LABEL" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- MILESTONE -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="MILESTONE">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="MILESTONE_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="TITLE" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="DESCRIPTION" type="text" nullable="true"/>
|
||||||
|
<column name="DUE_DATE" type="datetime" nullable="true"/>
|
||||||
|
<column name="CLOSED_DATE" type="datetime" nullable="true"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_MILESTONE_PK" tableName="MILESTONE" columnNames="USER_NAME, REPOSITORY_NAME, MILESTONE_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_MILESTONE_FK0" baseTableName="MILESTONE" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- ISSUE -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="ISSUE">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="ISSUE_ID" type="int" nullable="false"/>
|
||||||
|
<column name="OPENED_USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="MILESTONE_ID" type="int" nullable="true"/>
|
||||||
|
<column name="ASSIGNED_USER_NAME" type="varchar(100)" nullable="true"/>
|
||||||
|
<column name="TITLE" type="text" nullable="false"/>
|
||||||
|
<column name="CONTENT" type="text" nullable="true"/>
|
||||||
|
<column name="CLOSED" type="boolean" nullable="false"/>
|
||||||
|
<column name="REGISTERED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="PULL_REQUEST" type="boolean" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_ISSUE_PK" tableName="ISSUE" columnNames="ISSUE_ID, USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
<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"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- ISSUE_COMMENT -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="ISSUE_COMMENT">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="ISSUE_ID" type="int" nullable="false"/>
|
||||||
|
<column name="COMMENT_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="ACTION" type="varchar(20)" nullable="false"/>
|
||||||
|
<column name="COMMENTED_USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="CONTENT" type="text" nullable="false"/>
|
||||||
|
<column name="REGISTERED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_ISSUE_COMMENT_PK" tableName="ISSUE_COMMENT" columnNames="COMMENT_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ISSUE_COMMENT_FK0" baseTableName="ISSUE_COMMENT" baseColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID" referencedTableName="ISSUE" referencedColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- ISSUE_ID -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="ISSUE_ID">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="ISSUE_ID" type="int" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_ISSUE_ID_PK" tableName="ISSUE_ID" columnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ISSUE_ID_FK1" baseTableName="ISSUE_ID" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- ISSUE_ID -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="ISSUE_LABEL">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="ISSUE_ID" type="int" nullable="false"/>
|
||||||
|
<column name="LABEL_ID" type="int" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_ISSUE_LABEL_PK" tableName="ISSUE_LABEL" columnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID, LABEL_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ISSUE_LABEL_FK0" baseTableName="ISSUE_LABEL" baseColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID" referencedTableName="ISSUE" referencedColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- PLUGIN -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="PLUGIN">
|
||||||
|
<column name="PLUGIN_ID" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="VERSION" type="varchar(100)" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_PLUGIN_PK" tableName="PLUGIN" columnNames="PLUGIN_ID"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- PULL_REQUEST -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="PULL_REQUEST">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="ISSUE_ID" type="int" nullable="false"/>
|
||||||
|
<column name="BRANCH" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REQUEST_USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REQUEST_REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REQUEST_BRANCH" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="COMMIT_ID_FROM" type="varchar(40)" nullable="false"/>
|
||||||
|
<column name="COMMIT_ID_TO" type="varchar(40)" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_PULL_REQUEST_PK" tableName="PULL_REQUEST" columnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_PULL_REQUEST_FK0" baseTableName="PULL_REQUEST" baseColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID" referencedTableName="ISSUE" referencedColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- SSH_KEY -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="SSH_KEY">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="SSH_KEY_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="TITLE" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="PUBLIC_KEY" type="text" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_SSH_KEY_PK" tableName="SSH_KEY" columnNames="USER_NAME, SSH_KEY_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_SSH_KEY_FK0" baseTableName="SSH_KEY" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- WEB_HOOK -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="WEB_HOOK">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="URL" type="varchar(200)" nullable="false"/>
|
||||||
|
<column name="TOKEN" type="varchar(100)" nullable="true"/>
|
||||||
|
<column name="CTYPE" type="varchar(10)" nullable="true"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_WEB_HOOK_PK" tableName="WEB_HOOK" columnNames="USER_NAME, REPOSITORY_NAME, URL"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_WEB_HOOK_FK0" baseTableName="WEB_HOOK" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- WEB_HOOK_EVENT -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="WEB_HOOK_EVENT">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="URL" type="varchar(200)" nullable="false"/>
|
||||||
|
<column name="EVENT" type="varchar(30)" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_WEB_HOOK_EVENT_PK" tableName="WEB_HOOK_EVENT" columnNames="USER_NAME, REPOSITORY_NAME, URL, EVENT"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_WEB_HOOK_EVENT_FK0" baseTableName="WEB_HOOK_EVENT" baseColumnNames="USER_NAME, REPOSITORY_NAME, URL" referencedTableName="WEB_HOOK" referencedColumnNames="USER_NAME, REPOSITORY_NAME, URL" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- PROTECTED_BRANCH -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="PROTECTED_BRANCH">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="BRANCH" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="STATUS_CHECK_ADMIN" type="boolean" nullable="false" defaultValueBoolean="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_PROTECTED_BRANCH_PK" tableName="PROTECTED_BRANCH" columnNames="USER_NAME, REPOSITORY_NAME, BRANCH"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_PROTECTED_BRANCH_FK0" baseTableName="PROTECTED_BRANCH" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- PROTECTED_BRANCH_REQUIRE_CONTEXT -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="PROTECTED_BRANCH_REQUIRE_CONTEXT">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="BRANCH" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="CONTEXT" type="varchar(255)" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<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>
|
||||||
10
src/main/resources/update/gitbucket-core_4.2.xml
Normal file
10
src/main/resources/update/gitbucket-core_4.2.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<changeSet>
|
||||||
|
<addColumn tableName="REPOSITORY">
|
||||||
|
<column name="ENABLE_ISSUES" type="boolean" nullable="false" defaultValueBoolean="true"/>
|
||||||
|
<column name="EXTERNAL_ISSUES_URL" type="varchar(200)" nullable="true"/>
|
||||||
|
<column name="ENABLE_WIKI" type="boolean" nullable="false" defaultValueBoolean="true"/>
|
||||||
|
<column name="ALLOW_WIKI_EDITING" type="boolean" nullable="false" defaultValueBoolean="false"/>
|
||||||
|
<column name="EXTERNAL_WIKI_URL" type="varchar(200)" nullable="true"/>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
@@ -1,24 +1,30 @@
|
|||||||
|
|
||||||
import gitbucket.core.controller._
|
import gitbucket.core.controller._
|
||||||
import gitbucket.core.plugin.PluginRegistry
|
import gitbucket.core.plugin.PluginRegistry
|
||||||
import gitbucket.core.servlet.{AccessTokenAuthenticationFilter, BasicAuthenticationFilter, Database, TransactionFilter}
|
import gitbucket.core.servlet.{ApiAuthenticationFilter, Database, GitAuthenticationFilter, TransactionFilter}
|
||||||
import gitbucket.core.util.Directory
|
import gitbucket.core.util.Directory
|
||||||
|
|
||||||
import java.util.EnumSet
|
import java.util.EnumSet
|
||||||
import javax.servlet._
|
import javax.servlet._
|
||||||
|
|
||||||
|
import gitbucket.core.service.SystemSettingsService
|
||||||
import org.scalatra._
|
import org.scalatra._
|
||||||
|
|
||||||
|
|
||||||
class ScalatraBootstrap extends LifeCycle {
|
class ScalatraBootstrap extends LifeCycle with SystemSettingsService {
|
||||||
override def init(context: ServletContext) {
|
override def init(context: ServletContext) {
|
||||||
|
|
||||||
|
val settings = loadSystemSettings()
|
||||||
|
if(settings.baseUrl.exists(_.startsWith("https://"))) {
|
||||||
|
context.getSessionCookieConfig.setSecure(true)
|
||||||
|
}
|
||||||
|
|
||||||
// Register TransactionFilter and BasicAuthenticationFilter at first
|
// Register TransactionFilter and BasicAuthenticationFilter at first
|
||||||
context.addFilter("transactionFilter", new TransactionFilter)
|
context.addFilter("transactionFilter", new TransactionFilter)
|
||||||
context.getFilterRegistration("transactionFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
context.getFilterRegistration("transactionFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
||||||
context.addFilter("basicAuthenticationFilter", new BasicAuthenticationFilter)
|
context.addFilter("gitAuthenticationFilter", new GitAuthenticationFilter)
|
||||||
context.getFilterRegistration("basicAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*")
|
context.getFilterRegistration("gitAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*")
|
||||||
context.addFilter("accessTokenAuthenticationFilter", new AccessTokenAuthenticationFilter)
|
context.addFilter("apiAuthenticationFilter", new ApiAuthenticationFilter)
|
||||||
context.getFilterRegistration("accessTokenAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/api/v3/*")
|
context.getFilterRegistration("apiAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/api/v3/*")
|
||||||
// Register controllers
|
// Register controllers
|
||||||
context.mount(new AnonymousAccessController, "/*")
|
context.mount(new AnonymousAccessController, "/*")
|
||||||
|
|
||||||
|
|||||||
19
src/main/scala/gitbucket/core/GitBucketCoreModule.scala
Normal file
19
src/main/scala/gitbucket/core/GitBucketCoreModule.scala
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package gitbucket.core
|
||||||
|
|
||||||
|
import io.github.gitbucket.solidbase.migration.{SqlMigration, LiquibaseMigration}
|
||||||
|
import io.github.gitbucket.solidbase.model.{Version, Module}
|
||||||
|
|
||||||
|
object GitBucketCoreModule extends Module("gitbucket-core",
|
||||||
|
new Version("4.0.0",
|
||||||
|
new LiquibaseMigration("update/gitbucket-core_4.0.xml"),
|
||||||
|
new SqlMigration("update/gitbucket-core_4.0.sql")
|
||||||
|
),
|
||||||
|
new Version("4.1.0"),
|
||||||
|
new Version("4.2.0",
|
||||||
|
new LiquibaseMigration("update/gitbucket-core_4.2.xml")
|
||||||
|
),
|
||||||
|
new Version("4.2.1"),
|
||||||
|
new Version("4.3.0"),
|
||||||
|
new Version("4.4.0"),
|
||||||
|
new Version("4.5.0")
|
||||||
|
)
|
||||||
@@ -14,3 +14,10 @@ case class ApiBranch(
|
|||||||
"self" -> ApiPath(s"/api/v3/repos/${repositoryName.fullName}/branches/${name}"),
|
"self" -> ApiPath(s"/api/v3/repos/${repositoryName.fullName}/branches/${name}"),
|
||||||
"html" -> ApiPath(s"/${repositoryName.fullName}/tree/${name}"))
|
"html" -> ApiPath(s"/${repositoryName.fullName}/tree/${name}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case class ApiBranchCommit(sha: String)
|
||||||
|
|
||||||
|
case class ApiBranchForList(
|
||||||
|
name: String,
|
||||||
|
commit: ApiBranchCommit
|
||||||
|
)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ object ApiBranchProtection{
|
|||||||
|
|
||||||
def apply(info: ProtectedBranchService.ProtectedBranchInfo): ApiBranchProtection = ApiBranchProtection(
|
def apply(info: ProtectedBranchService.ProtectedBranchInfo): ApiBranchProtection = ApiBranchProtection(
|
||||||
enabled = info.enabled,
|
enabled = info.enabled,
|
||||||
required_status_checks = Some(Status(EnforcementLevel(info.enabled, info.includeAdministrators), info.contexts)))
|
required_status_checks = Some(Status(EnforcementLevel(info.enabled && info.contexts.nonEmpty, info.includeAdministrators), info.contexts)))
|
||||||
val statusNone = Status(Off, Seq.empty)
|
val statusNone = Status(Off, Seq.empty)
|
||||||
case class Status(enforcement_level: EnforcementLevel, contexts: Seq[String])
|
case class Status(enforcement_level: EnforcementLevel, contexts: Seq[String])
|
||||||
sealed class EnforcementLevel(val name: String)
|
sealed class EnforcementLevel(val name: String)
|
||||||
@@ -44,4 +44,3 @@ object ApiBranchProtection{
|
|||||||
}
|
}
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
11
src/main/scala/gitbucket/core/api/ApiContents.scala
Normal file
11
src/main/scala/gitbucket/core/api/ApiContents.scala
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
import gitbucket.core.util.JGitUtil.FileInfo
|
||||||
|
|
||||||
|
case class ApiContents(`type`: String, name: String)
|
||||||
|
|
||||||
|
object ApiContents{
|
||||||
|
def apply(fileInfo: FileInfo): ApiContents =
|
||||||
|
if(fileInfo.isDirectory) ApiContents("dir", fileInfo.name)
|
||||||
|
else ApiContents("file", fileInfo.name)
|
||||||
|
}
|
||||||
3
src/main/scala/gitbucket/core/api/ApiEndPoint.scala
Normal file
3
src/main/scala/gitbucket/core/api/ApiEndPoint.scala
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
case class ApiEndPoint(rate_limit_url: ApiPath = ApiPath("/api/v3/rate_limit"))
|
||||||
@@ -20,6 +20,16 @@ case class ApiIssue(
|
|||||||
body: String)(repositoryName: RepositoryName, isPullRequest: Boolean){
|
body: String)(repositoryName: RepositoryName, isPullRequest: Boolean){
|
||||||
val comments_url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/issues/${number}/comments")
|
val comments_url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/issues/${number}/comments")
|
||||||
val html_url = ApiPath(s"/${repositoryName.fullName}/${if(isPullRequest){ "pull" }else{ "issues" }}/${number}")
|
val html_url = ApiPath(s"/${repositoryName.fullName}/${if(isPullRequest){ "pull" }else{ "issues" }}/${number}")
|
||||||
|
val pull_request = if (isPullRequest) {
|
||||||
|
Some(Map(
|
||||||
|
"url" -> ApiPath(s"/api/v3/repos/${repositoryName.fullName}/pulls/${number}"),
|
||||||
|
"html_url" -> ApiPath(s"/${repositoryName.fullName}/pull/${number}")
|
||||||
|
// "diff_url" -> ApiPath(s"/${repositoryName.fullName}/pull/${number}.diff"),
|
||||||
|
// "patch_url" -> ApiPath(s"/${repositoryName.fullName}/pull/${number}.patch")
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object ApiIssue{
|
object ApiIssue{
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ case class ApiPullRequest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
object ApiPullRequest{
|
object ApiPullRequest{
|
||||||
def apply(issue: Issue, pullRequest: PullRequest, headRepo: ApiRepository, baseRepo: ApiRepository, user: ApiUser): ApiPullRequest = ApiPullRequest(
|
def apply(issue: Issue, pullRequest: PullRequest, headRepo: ApiRepository, baseRepo: ApiRepository, user: ApiUser): ApiPullRequest =
|
||||||
|
ApiPullRequest(
|
||||||
number = issue.issueId,
|
number = issue.issueId,
|
||||||
updated_at = issue.updatedDate,
|
updated_at = issue.updatedDate,
|
||||||
created_at = issue.registeredDate,
|
created_at = issue.registeredDate,
|
||||||
|
|||||||
5
src/main/scala/gitbucket/core/api/ApiRef.scala
Normal file
5
src/main/scala/gitbucket/core/api/ApiRef.scala
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
case class ApiObject(sha: String)
|
||||||
|
|
||||||
|
case class ApiRef(ref: String, `object`: ApiObject)
|
||||||
@@ -18,7 +18,7 @@ case class ApiRepository(
|
|||||||
val watchers_count = watchers
|
val watchers_count = watchers
|
||||||
val url = if(urlIsHtmlUrl){
|
val url = if(urlIsHtmlUrl){
|
||||||
ApiPath(s"/${full_name}")
|
ApiPath(s"/${full_name}")
|
||||||
}else{
|
} else {
|
||||||
ApiPath(s"/api/v3/repos/${full_name}")
|
ApiPath(s"/api/v3/repos/${full_name}")
|
||||||
}
|
}
|
||||||
val http_url = ApiPath(s"/git/${full_name}.git")
|
val http_url = ApiPath(s"/git/${full_name}.git")
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ case class ApiUser(
|
|||||||
created_at: Date) {
|
created_at: Date) {
|
||||||
val url = ApiPath(s"/api/v3/users/${login}")
|
val url = ApiPath(s"/api/v3/users/${login}")
|
||||||
val html_url = ApiPath(s"/${login}")
|
val html_url = ApiPath(s"/${login}")
|
||||||
|
val avatar_url = ApiPath(s"/${login}/_avatar")
|
||||||
// val followers_url = ApiPath(s"/api/v3/users/${login}/followers")
|
// val followers_url = ApiPath(s"/api/v3/users/${login}/followers")
|
||||||
// val following_url = ApiPath(s"/api/v3/users/${login}/following{/other_user}")
|
// val following_url = ApiPath(s"/api/v3/users/${login}/following{/other_user}")
|
||||||
// val gists_url = ApiPath(s"/api/v3/users/${login}/gists{/gist_id}")
|
// val gists_url = ApiPath(s"/api/v3/users/${login}/gists{/gist_id}")
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
case class PersonalTokenForm(note: String)
|
case class PersonalTokenForm(note: String)
|
||||||
|
|
||||||
val newForm = mapping(
|
val newForm = mapping(
|
||||||
"userName" -> trim(label("User name" , text(required, maxlength(100), identifier, uniqueUserName))),
|
"userName" -> trim(label("User name" , text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||||
"password" -> trim(label("Password" , text(required, maxlength(20)))),
|
"password" -> trim(label("Password" , text(required, maxlength(20)))),
|
||||||
"fullName" -> trim(label("Full Name" , text(required, maxlength(100)))),
|
"fullName" -> trim(label("Full Name" , text(required, maxlength(100)))),
|
||||||
"mailAddress" -> trim(label("Mail Address" , text(required, maxlength(100), uniqueMailAddress()))),
|
"mailAddress" -> trim(label("Mail Address" , text(required, maxlength(100), uniqueMailAddress()))),
|
||||||
@@ -68,7 +68,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
case class EditGroupForm(groupName: String, url: Option[String], fileId: Option[String], members: String, clearImage: Boolean)
|
case class EditGroupForm(groupName: String, url: Option[String], fileId: Option[String], members: String, clearImage: Boolean)
|
||||||
|
|
||||||
val newGroupForm = mapping(
|
val newGroupForm = mapping(
|
||||||
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier, uniqueUserName))),
|
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||||
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
||||||
"fileId" -> trim(label("File ID" ,optional(text()))),
|
"fileId" -> trim(label("File ID" ,optional(text()))),
|
||||||
"members" -> trim(label("Members" ,text(required, members)))
|
"members" -> trim(label("Members" ,text(required, members)))
|
||||||
@@ -155,7 +155,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
get("/:userName/_edit")(oneselfOnly {
|
get("/:userName/_edit")(oneselfOnly {
|
||||||
val userName = params("userName")
|
val userName = params("userName")
|
||||||
getAccountByUserName(userName).map { x =>
|
getAccountByUserName(userName).map { x =>
|
||||||
html.edit(x, flash.get("info"))
|
html.edit(x, flash.get("info"), flash.get("error"))
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -178,7 +178,11 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
get("/:userName/_delete")(oneselfOnly {
|
get("/:userName/_delete")(oneselfOnly {
|
||||||
val userName = params("userName")
|
val userName = params("userName")
|
||||||
|
|
||||||
getAccountByUserName(userName, true).foreach { account =>
|
getAccountByUserName(userName, true).map { account =>
|
||||||
|
if(isLastAdministrator(account)){
|
||||||
|
flash += "error" -> "Account can't be removed because this is last one administrator."
|
||||||
|
redirect(s"/${userName}/_edit")
|
||||||
|
} else {
|
||||||
// // Remove repositories
|
// // Remove repositories
|
||||||
// getRepositoryNamesOfUser(userName).foreach { repositoryName =>
|
// getRepositoryNamesOfUser(userName).foreach { repositoryName =>
|
||||||
// deleteRepository(userName, repositoryName)
|
// deleteRepository(userName, repositoryName)
|
||||||
@@ -187,14 +191,12 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
|
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
|
||||||
// }
|
// }
|
||||||
// // Remove from GROUP_MEMBER, COLLABORATOR and REPOSITORY
|
// // Remove from GROUP_MEMBER, COLLABORATOR and REPOSITORY
|
||||||
// removeUserRelatedData(userName)
|
|
||||||
|
|
||||||
removeUserRelatedData(userName)
|
removeUserRelatedData(userName)
|
||||||
updateAccount(account.copy(isRemoved = true))
|
updateAccount(account.copy(isRemoved = true))
|
||||||
}
|
|
||||||
|
|
||||||
session.invalidate
|
session.invalidate
|
||||||
redirect("/")
|
redirect("/")
|
||||||
|
}
|
||||||
|
} getOrElse NotFound
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:userName/_ssh")(oneselfOnly {
|
get("/:userName/_ssh")(oneselfOnly {
|
||||||
@@ -447,8 +449,8 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
|
|
||||||
private def validPublicKey: Constraint = new Constraint(){
|
private def validPublicKey: Constraint = new Constraint(){
|
||||||
override def validate(name: String, value: String, messages: Messages): Option[String] = SshUtil.str2PublicKey(value) match {
|
override def validate(name: String, value: String, messages: Messages): Option[String] = SshUtil.str2PublicKey(value) match {
|
||||||
case Some(_) => None
|
case Some(_) if !getAllKeys().exists(_.publicKey == value) => None
|
||||||
case None => Some("Key is invalid.")
|
case _ => Some("Key is invalid.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import gitbucket.core.service.PullRequestService._
|
|||||||
import gitbucket.core.service._
|
import gitbucket.core.service._
|
||||||
import gitbucket.core.util.ControlUtil._
|
import gitbucket.core.util.ControlUtil._
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
import gitbucket.core.util.JGitUtil.{CommitInfo, getFileList, getBranches, getDefaultBranch}
|
||||||
import gitbucket.core.util._
|
import gitbucket.core.util._
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
@@ -54,14 +54,91 @@ trait ApiControllerBase extends ControllerBase {
|
|||||||
with CollaboratorsAuthenticator =>
|
with CollaboratorsAuthenticator =>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://developer.github.com/v3/users/#get-a-single-user
|
* https://developer.github.com/v3/#root-endpoint
|
||||||
*/
|
*/
|
||||||
get("/api/v3/users/:userName") {
|
get("/api/v3/") {
|
||||||
getAccountByUserName(params("userName")).map { account =>
|
JsonFormat(ApiEndPoint())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/orgs/#get-an-organization
|
||||||
|
*/
|
||||||
|
get("/api/v3/orgs/:groupName") {
|
||||||
|
getAccountByUserName(params("groupName")).filter(account => account.isGroupAccount).map { account =>
|
||||||
JsonFormat(ApiUser(account))
|
JsonFormat(ApiUser(account))
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/users/#get-a-single-user
|
||||||
|
*/
|
||||||
|
get("/api/v3/users/:userName") {
|
||||||
|
getAccountByUserName(params("userName")).filterNot(account => account.isGroupAccount).map { account =>
|
||||||
|
JsonFormat(ApiUser(account))
|
||||||
|
} getOrElse NotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/#list-organization-repositories
|
||||||
|
*/
|
||||||
|
get("/api/v3/orgs/:orgName/repos") {
|
||||||
|
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("orgName"))).map{ r => ApiRepository(r, getAccountByUserName(r.owner).get)})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/#list-user-repositories
|
||||||
|
*/
|
||||||
|
get("/api/v3/users/:userName/repos") {
|
||||||
|
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("userName"))).map{ r => ApiRepository(r, getAccountByUserName(r.owner).get)})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* https://developer.github.com/v3/repos/branches/#list-branches
|
||||||
|
*/
|
||||||
|
get ("/api/v3/repos/:owner/:repo/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))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* https://developer.github.com/v3/repos/contents/#get-contents
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repo/contents/*")(referrersOnly { repository =>
|
||||||
|
val path = multiParams("splat").head match {
|
||||||
|
case s if s.isEmpty => "."
|
||||||
|
case s => s
|
||||||
|
}
|
||||||
|
val refStr = params.getOrElse("ref", repository.repository.defaultBranch)
|
||||||
|
using(Git.open(getRepositoryDir(params("owner"), params("repo")))){ git =>
|
||||||
|
JsonFormat(getFileList(git, refStr, path).map{f => ApiContents(f)})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* https://developer.github.com/v3/git/refs/#get-a-reference
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repo/git/*") (referrersOnly { repository =>
|
||||||
|
val revstr = multiParams("splat").head
|
||||||
|
using(Git.open(getRepositoryDir(params("owner"), params("repo")))) { git =>
|
||||||
|
//JsonFormat( (revstr, git.getRepository().resolve(revstr)) )
|
||||||
|
// getRef is deprecated by jgit-4.2. use exactRef() or findRef()
|
||||||
|
val sha = git.getRepository().getRef(revstr).getObjectId().name()
|
||||||
|
JsonFormat(ApiRef(revstr, ApiObject(sha)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/collaborators/#list-collaborators
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repo/collaborators") (referrersOnly { repository =>
|
||||||
|
JsonFormat(getCollaborators(params("owner"), params("repo")).map(u => ApiUser(getAccountByUserName(u).get)))
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://developer.github.com/v3/users/#get-the-authenticated-user
|
* https://developer.github.com/v3/users/#get-the-authenticated-user
|
||||||
*/
|
*/
|
||||||
@@ -71,6 +148,16 @@ trait ApiControllerBase extends ControllerBase {
|
|||||||
} getOrElse Unauthorized
|
} getOrElse Unauthorized
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List user's own repository
|
||||||
|
* https://developer.github.com/v3/repos/#list-your-repositories
|
||||||
|
*/
|
||||||
|
get("/api/v3/user/repos")(usersOnly{
|
||||||
|
JsonFormat(getVisibleRepositories(context.loginAccount, Option(context.loginAccount.get.userName)).map{
|
||||||
|
r => ApiRepository(r, getAccountByUserName(r.owner).get)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create user repository
|
* Create user repository
|
||||||
* https://developer.github.com/v3/repos/#create
|
* https://developer.github.com/v3/repos/#create
|
||||||
@@ -119,7 +206,9 @@ trait ApiControllerBase extends ControllerBase {
|
|||||||
}) getOrElse NotFound
|
}) getOrElse NotFound
|
||||||
})
|
})
|
||||||
|
|
||||||
/** https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection
|
||||||
|
*/
|
||||||
patch("/api/v3/repos/:owner/:repo/branches/:branch")(ownerOnly { repository =>
|
patch("/api/v3/repos/:owner/:repo/branches/:branch")(ownerOnly { repository =>
|
||||||
import gitbucket.core.api._
|
import gitbucket.core.api._
|
||||||
(for{
|
(for{
|
||||||
@@ -265,14 +354,24 @@ trait ApiControllerBase extends ControllerBase {
|
|||||||
// TODO: more api spec condition
|
// TODO: more api spec condition
|
||||||
val condition = IssueSearchCondition(request)
|
val condition = IssueSearchCondition(request)
|
||||||
val baseOwner = getAccountByUserName(repository.owner).get
|
val baseOwner = getAccountByUserName(repository.owner).get
|
||||||
val issues:List[(Issue, Account, Int, PullRequest, Repository, Account)] = searchPullRequestByApi(condition, (page - 1) * PullRequestLimit, PullRequestLimit, repository.owner -> repository.name)
|
|
||||||
JsonFormat(issues.map{case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner) =>
|
val issues: List[(Issue, Account, Int, PullRequest, Repository, Account)] =
|
||||||
|
searchPullRequestByApi(
|
||||||
|
condition = condition,
|
||||||
|
offset = (page - 1) * PullRequestLimit,
|
||||||
|
limit = PullRequestLimit,
|
||||||
|
repos = repository.owner -> repository.name
|
||||||
|
)
|
||||||
|
|
||||||
|
JsonFormat(issues.map { case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner) =>
|
||||||
ApiPullRequest(
|
ApiPullRequest(
|
||||||
issue,
|
issue,
|
||||||
pullRequest,
|
pullRequest,
|
||||||
ApiRepository(headRepo, ApiUser(headOwner)),
|
ApiRepository(headRepo, ApiUser(headOwner)),
|
||||||
ApiRepository(repository, ApiUser(baseOwner)),
|
ApiRepository(repository, ApiUser(baseOwner)),
|
||||||
ApiUser(issueUser)) })
|
ApiUser(issueUser)
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -244,4 +244,13 @@ trait AccountManagementControllerBase extends ControllerBase {
|
|||||||
.map { _ => "Mail address is already registered." }
|
.map { _ => "Mail address is already registered." }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val allReservedNames = Set("git", "admin", "upload", "api")
|
||||||
|
protected def reservedNames(): Constraint = new Constraint(){
|
||||||
|
override def validate(name: String, value: String, messages: Messages): Option[String] = if(allReservedNames.contains(value)){
|
||||||
|
Some(s"${value} is reserved")
|
||||||
|
}else{
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,20 +15,7 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
with UsersAuthenticator =>
|
with UsersAuthenticator =>
|
||||||
|
|
||||||
get("/dashboard/issues")(usersOnly {
|
get("/dashboard/issues")(usersOnly {
|
||||||
val q = request.getParameter("q")
|
|
||||||
val account = context.loginAccount.get
|
|
||||||
Option(q).map { q =>
|
|
||||||
val condition = IssueSearchCondition(q, Map[String, Int]())
|
|
||||||
q match {
|
|
||||||
case q if(q.contains("is:pr")) => redirect(s"/dashboard/pulls?q=${StringUtil.urlEncode(q)}")
|
|
||||||
case q if(q.contains(s"author:${account.userName}")) => redirect(s"/dashboard/issues/created_by${condition.toURL}")
|
|
||||||
case q if(q.contains(s"assignee:${account.userName}")) => redirect(s"/dashboard/issues/assigned${condition.toURL}")
|
|
||||||
case q if(q.contains(s"mentions:${account.userName}")) => redirect(s"/dashboard/issues/mentioned${condition.toURL}")
|
|
||||||
case _ => searchIssues("created_by")
|
|
||||||
}
|
|
||||||
} getOrElse {
|
|
||||||
searchIssues("created_by")
|
searchIssues("created_by")
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/dashboard/issues/assigned")(usersOnly {
|
get("/dashboard/issues/assigned")(usersOnly {
|
||||||
@@ -44,20 +31,7 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/dashboard/pulls")(usersOnly {
|
get("/dashboard/pulls")(usersOnly {
|
||||||
val q = request.getParameter("q")
|
|
||||||
val account = context.loginAccount.get
|
|
||||||
Option(q).map { q =>
|
|
||||||
val condition = IssueSearchCondition(q, Map[String, Int]())
|
|
||||||
q match {
|
|
||||||
case q if(q.contains("is:issue")) => redirect(s"/dashboard/issues?q=${StringUtil.urlEncode(q)}")
|
|
||||||
case q if(q.contains(s"author:${account.userName}")) => redirect(s"/dashboard/pulls/created_by${condition.toURL}")
|
|
||||||
case q if(q.contains(s"assignee:${account.userName}")) => redirect(s"/dashboard/pulls/assigned${condition.toURL}")
|
|
||||||
case q if(q.contains(s"mentions:${account.userName}")) => redirect(s"/dashboard/pulls/mentioned${condition.toURL}")
|
|
||||||
case _ => searchPullRequests("created_by")
|
|
||||||
}
|
|
||||||
} getOrElse {
|
|
||||||
searchPullRequests("created_by")
|
searchPullRequests("created_by")
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/dashboard/pulls/created_by")(usersOnly {
|
get("/dashboard/pulls/created_by")(usersOnly {
|
||||||
@@ -73,19 +47,12 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
private def getOrCreateCondition(key: String, filter: String, userName: String) = {
|
private def getOrCreateCondition(key: String, filter: String, userName: String) = {
|
||||||
val condition = session.putAndGet(key, if(request.hasQueryString){
|
val condition = IssueSearchCondition(request)
|
||||||
val q = request.getParameter("q")
|
|
||||||
if(q == null){
|
|
||||||
IssueSearchCondition(request)
|
|
||||||
} else {
|
|
||||||
IssueSearchCondition(q, Map[String, Int]())
|
|
||||||
}
|
|
||||||
} else session.getAs[IssueSearchCondition](key).getOrElse(IssueSearchCondition()))
|
|
||||||
|
|
||||||
filter match {
|
filter match {
|
||||||
case "assigned" => condition.copy(assigned = Some(userName), author = None , mentioned = None)
|
case "assigned" => condition.copy(assigned = Some(Some(userName)), author = None, mentioned = None)
|
||||||
case "mentioned" => condition.copy(assigned = None , author = None , mentioned = Some(userName))
|
case "mentioned" => condition.copy(assigned = None, author = None, mentioned = Some(userName))
|
||||||
case _ => condition.copy(assigned = None , author = Some(userName), mentioned = None)
|
case _ => condition.copy(assigned = None, author = Some(userName), mentioned = None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,12 +70,14 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
countIssue(condition.copy(state = "open" ), false, userRepos: _*),
|
countIssue(condition.copy(state = "open" ), false, userRepos: _*),
|
||||||
countIssue(condition.copy(state = "closed"), false, userRepos: _*),
|
countIssue(condition.copy(state = "closed"), false, userRepos: _*),
|
||||||
filter match {
|
filter match {
|
||||||
case "assigned" => condition.copy(assigned = Some(userName))
|
case "assigned" => condition.copy(assigned = Some(Some(userName)))
|
||||||
case "mentioned" => condition.copy(mentioned = Some(userName))
|
case "mentioned" => condition.copy(mentioned = Some(userName))
|
||||||
case _ => condition.copy(author = Some(userName))
|
case _ => condition.copy(author = Some(userName))
|
||||||
},
|
},
|
||||||
filter,
|
filter,
|
||||||
getGroupNames(userName))
|
getGroupNames(userName),
|
||||||
|
getVisibleRepositories(context.loginAccount, withoutPhysicalInfo = true),
|
||||||
|
getUserRepositories(userName, withoutPhysicalInfo = true))
|
||||||
}
|
}
|
||||||
|
|
||||||
private def searchPullRequests(filter: String) = {
|
private def searchPullRequests(filter: String) = {
|
||||||
@@ -126,12 +95,14 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
countIssue(condition.copy(state = "open" ), true, allRepos: _*),
|
countIssue(condition.copy(state = "open" ), true, allRepos: _*),
|
||||||
countIssue(condition.copy(state = "closed"), true, allRepos: _*),
|
countIssue(condition.copy(state = "closed"), true, allRepos: _*),
|
||||||
filter match {
|
filter match {
|
||||||
case "assigned" => condition.copy(assigned = Some(userName))
|
case "assigned" => condition.copy(assigned = Some(Some(userName)))
|
||||||
case "mentioned" => condition.copy(mentioned = Some(userName))
|
case "mentioned" => condition.copy(mentioned = Some(userName))
|
||||||
case _ => condition.copy(author = Some(userName))
|
case _ => condition.copy(author = Some(userName))
|
||||||
},
|
},
|
||||||
filter,
|
filter,
|
||||||
getGroupNames(userName))
|
getGroupNames(userName),
|
||||||
|
getVisibleRepositories(context.loginAccount, withoutPhysicalInfo = true),
|
||||||
|
getUserRepositories(userName, withoutPhysicalInfo = true))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,26 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
import gitbucket.core.util.{Keys, FileUtil}
|
import gitbucket.core.model.Account
|
||||||
|
import gitbucket.core.service.{AccountService, RepositoryService}
|
||||||
|
import gitbucket.core.servlet.Database
|
||||||
|
import gitbucket.core.util._
|
||||||
import gitbucket.core.util.ControlUtil._
|
import gitbucket.core.util.ControlUtil._
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.eclipse.jgit.dircache.DirCache
|
||||||
|
import org.eclipse.jgit.lib.{FileMode, Constants}
|
||||||
|
import org.scalatra
|
||||||
import org.scalatra._
|
import org.scalatra._
|
||||||
import org.scalatra.servlet.{MultipartConfig, FileUploadSupport, FileItem}
|
import org.scalatra.servlet.{MultipartConfig, FileUploadSupport, FileItem}
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.{IOUtils, FileUtils}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides Ajax based file upload functionality.
|
* Provides Ajax based file upload functionality.
|
||||||
*
|
*
|
||||||
* This servlet saves uploaded file.
|
* This servlet saves uploaded file.
|
||||||
*/
|
*/
|
||||||
class FileUploadController extends ScalatraServlet with FileUploadSupport {
|
class FileUploadController extends ScalatraServlet with FileUploadSupport with RepositoryService with AccountService {
|
||||||
|
|
||||||
configureMultipartHandling(MultipartConfig(maxFileSize = Some(3 * 1024 * 1024)))
|
configureMultipartHandling(MultipartConfig(maxFileSize = Some(3 * 1024 * 1024)))
|
||||||
|
|
||||||
@@ -31,6 +39,64 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport {
|
|||||||
}, FileUtil.isUploadableType)
|
}, FileUtil.isUploadableType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
post("/wiki/:owner/:repository"){
|
||||||
|
// Don't accept not logged-in users
|
||||||
|
session.get(Keys.Session.LoginAccount).collect { case loginAccount: Account =>
|
||||||
|
val owner = params("owner")
|
||||||
|
val repository = params("repository")
|
||||||
|
|
||||||
|
// Check whether logged-in user is collaborator
|
||||||
|
collaboratorsOnly(owner, repository, loginAccount){
|
||||||
|
execute({ (file, fileId) =>
|
||||||
|
val fileName = file.getName
|
||||||
|
LockUtil.lock(s"${owner}/${repository}/wiki") {
|
||||||
|
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||||
|
val builder = DirCache.newInCore.builder()
|
||||||
|
val inserter = git.getRepository.newObjectInserter()
|
||||||
|
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
|
||||||
|
|
||||||
|
if(headId != null){
|
||||||
|
JGitUtil.processTree(git, headId){ (path, tree) =>
|
||||||
|
if(path != fileName){
|
||||||
|
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val bytes = IOUtils.toByteArray(file.getInputStream)
|
||||||
|
builder.add(JGitUtil.createDirCacheEntry(fileName, FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, bytes)))
|
||||||
|
builder.finish()
|
||||||
|
|
||||||
|
val newHeadId = JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
|
||||||
|
Constants.HEAD, loginAccount.userName, loginAccount.mailAddress, s"Uploaded ${fileName}")
|
||||||
|
|
||||||
|
fileName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, FileUtil.isUploadableType)
|
||||||
|
}
|
||||||
|
} getOrElse BadRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/import") {
|
||||||
|
import JDBCUtil._
|
||||||
|
session.get(Keys.Session.LoginAccount).collect { case loginAccount: Account if loginAccount.isAdmin =>
|
||||||
|
execute({ (file, fileId) =>
|
||||||
|
request2Session(request).conn.importAsSQL(file.getInputStream)
|
||||||
|
}, _ => true)
|
||||||
|
}
|
||||||
|
redirect("/admin/data")
|
||||||
|
}
|
||||||
|
|
||||||
|
private def collaboratorsOnly(owner: String, repository: String, loginAccount: Account)(action: => Any): Any = {
|
||||||
|
implicit val session = Database.getSession(request)
|
||||||
|
loginAccount match {
|
||||||
|
case x if(x.isAdmin) => action
|
||||||
|
case x if(getCollaborators(owner, repository).contains(x.userName)) => action
|
||||||
|
case _ => BadRequest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private def execute(f: (FileItem, String) => Unit, mimeTypeChcker: (String) => Boolean) = fileParams.get("file") match {
|
private def execute(f: (FileItem, String) => Unit, mimeTypeChcker: (String) => Boolean) = fileParams.get("file") match {
|
||||||
case Some(file) if(mimeTypeChcker(file.name)) =>
|
case Some(file) if(mimeTypeChcker(file.name)) =>
|
||||||
defining(FileUtil.generateFileId){ fileId =>
|
defining(FileUtil.generateFileId){ fileId =>
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
import gitbucket.core.api._
|
|
||||||
import gitbucket.core.helper.xml
|
import gitbucket.core.helper.xml
|
||||||
import gitbucket.core.model.Account
|
import gitbucket.core.model.Account
|
||||||
import gitbucket.core.service.{RepositoryService, ActivityService, AccountService, RepositorySearchService, IssuesService}
|
import gitbucket.core.service._
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util.ControlUtil._
|
import gitbucket.core.util.ControlUtil._
|
||||||
import gitbucket.core.util.{LDAPUtil, Keys, UsersAuthenticator, ReferrerAuthenticator, StringUtil}
|
import gitbucket.core.util.{LDAPUtil, Keys, UsersAuthenticator, ReferrerAuthenticator, StringUtil}
|
||||||
@@ -118,7 +117,9 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
* JSON API for checking user existence.
|
* JSON API for checking user existence.
|
||||||
*/
|
*/
|
||||||
post("/_user/existence")(usersOnly {
|
post("/_user/existence")(usersOnly {
|
||||||
getAccountByUserName(params("userName")).isDefined
|
getAccountByUserName(params("userName")).map { account =>
|
||||||
|
if(params.get("userOnly").isDefined) !account.isGroupAccount else true
|
||||||
|
} getOrElse false
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO Move to RepositoryViwerController?
|
// TODO Move to RepositoryViwerController?
|
||||||
@@ -138,13 +139,21 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
target.toLowerCase match {
|
target.toLowerCase match {
|
||||||
case "issue" => gitbucket.core.search.html.issues(
|
case "issue" => gitbucket.core.search.html.issues(
|
||||||
searchIssues(repository.owner, repository.name, query),
|
|
||||||
countFiles(repository.owner, repository.name, query),
|
countFiles(repository.owner, repository.name, query),
|
||||||
|
searchIssues(repository.owner, repository.name, query),
|
||||||
|
countWikiPages(repository.owner, repository.name, query),
|
||||||
|
query, page, repository)
|
||||||
|
|
||||||
|
case "wiki" => gitbucket.core.search.html.wiki(
|
||||||
|
countFiles(repository.owner, repository.name, query),
|
||||||
|
countIssues(repository.owner, repository.name, query),
|
||||||
|
searchWikiPages(repository.owner, repository.name, query),
|
||||||
query, page, repository)
|
query, page, repository)
|
||||||
|
|
||||||
case _ => gitbucket.core.search.html.code(
|
case _ => gitbucket.core.search.html.code(
|
||||||
searchFiles(repository.owner, repository.name, query),
|
searchFiles(repository.owner, repository.name, query),
|
||||||
countIssues(repository.owner, repository.name, query),
|
countIssues(repository.owner, repository.name, query),
|
||||||
|
countWikiPages(repository.owner, repository.name, query),
|
||||||
query, page, repository)
|
query, page, repository)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -204,8 +204,7 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
getIssue(repository.owner, repository.name, params("id")) map { x =>
|
getIssue(repository.owner, repository.name, params("id")) map { x =>
|
||||||
if(isEditable(x.userName, x.repositoryName, x.openedUserName)){
|
if(isEditable(x.userName, x.repositoryName, x.openedUserName)){
|
||||||
params.get("dataType") collect {
|
params.get("dataType") collect {
|
||||||
case t if t == "html" => html.editissue(
|
case t if t == "html" => html.editissue(x.content, x.issueId, repository)
|
||||||
x.content, x.issueId, x.userName, x.repositoryName)
|
|
||||||
} getOrElse {
|
} getOrElse {
|
||||||
contentType = formats("json")
|
contentType = formats("json")
|
||||||
org.json4s.jackson.Serialization.write(
|
org.json4s.jackson.Serialization.write(
|
||||||
@@ -232,8 +231,7 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
getComment(repository.owner, repository.name, params("id")) map { x =>
|
getComment(repository.owner, repository.name, params("id")) map { x =>
|
||||||
if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
|
if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
|
||||||
params.get("dataType") collect {
|
params.get("dataType") collect {
|
||||||
case t if t == "html" => html.editcomment(
|
case t if t == "html" => html.editcomment(x.content, x.commentId, repository)
|
||||||
x.content, x.commentId, x.userName, x.repositoryName)
|
|
||||||
} getOrElse {
|
} getOrElse {
|
||||||
contentType = formats("json")
|
contentType = formats("json")
|
||||||
org.json4s.jackson.Serialization.write(
|
org.json4s.jackson.Serialization.write(
|
||||||
@@ -365,16 +363,7 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
val sessionKey = Keys.Session.Issues(owner, repoName)
|
val sessionKey = Keys.Session.Issues(owner, repoName)
|
||||||
|
|
||||||
// retrieve search condition
|
// retrieve search condition
|
||||||
val condition = session.putAndGet(sessionKey,
|
val condition = IssueSearchCondition(request)
|
||||||
if(request.hasQueryString){
|
|
||||||
val q = request.getParameter("q")
|
|
||||||
if(q == null || q.trim.isEmpty){
|
|
||||||
IssueSearchCondition(request)
|
|
||||||
} else {
|
|
||||||
IssueSearchCondition(q, getMilestones(owner, repoName).map(x => (x.title, x.milestoneId)).toMap)
|
|
||||||
}
|
|
||||||
} else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
|
|
||||||
)
|
|
||||||
|
|
||||||
html.list(
|
html.list(
|
||||||
"issues",
|
"issues",
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ trait LabelsControllerBase extends ControllerBase {
|
|||||||
"labelColor" -> trim(label("Color", text(required, color)))
|
"labelColor" -> trim(label("Color", text(required, color)))
|
||||||
)(LabelForm.apply)
|
)(LabelForm.apply)
|
||||||
|
|
||||||
|
|
||||||
get("/:owner/:repository/issues/labels")(referrersOnly { repository =>
|
get("/:owner/:repository/issues/labels")(referrersOnly { repository =>
|
||||||
html.list(
|
html.list(
|
||||||
getLabels(repository.owner, repository.name),
|
getLabels(repository.owner, repository.name),
|
||||||
@@ -84,8 +85,12 @@ trait LabelsControllerBase extends ControllerBase {
|
|||||||
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] = {
|
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] = {
|
||||||
val owner = params("owner")
|
val owner = params("owner")
|
||||||
val repository = params("repository")
|
val repository = params("repository")
|
||||||
|
params.get("labelId").map { labelId =>
|
||||||
|
getLabel(owner, repository, value).filter(_.labelId != labelId.toInt).map(_ => "Name has already been taken.")
|
||||||
|
}.getOrElse {
|
||||||
getLabel(owner, repository, value).map(_ => "Name has already been taken.")
|
getLabel(owner, repository, value).map(_ => "Name has already been taken.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
val existIds = using(Git.open(Directory.getRepositoryDir(owner, name))) { git => JGitUtil.getAllCommitIds(git) }.toSet
|
val existIds = using(Git.open(Directory.getRepositoryDir(owner, name))) { git => JGitUtil.getAllCommitIds(git) }.toSet
|
||||||
pullRemote(owner, name, pullreq.requestBranch, pullreq.userName, pullreq.repositoryName, pullreq.branch, loginAccount,
|
pullRemote(owner, name, pullreq.requestBranch, pullreq.userName, pullreq.repositoryName, pullreq.branch, loginAccount,
|
||||||
"Merge branch '${alias}' into ${pullreq.requestBranch}") match {
|
s"Merge branch '${alias}' into ${pullreq.requestBranch}") match {
|
||||||
case None => // conflict
|
case None => // conflict
|
||||||
flash += "error" -> s"Can't automatic merging branch '${alias}' into ${pullreq.requestBranch}."
|
flash += "error" -> s"Can't automatic merging branch '${alias}' into ${pullreq.requestBranch}."
|
||||||
case Some(oldId) =>
|
case Some(oldId) =>
|
||||||
@@ -202,7 +202,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
// close issue by commit message
|
// close issue by commit message
|
||||||
if(pullreq.requestBranch == repository.repository.defaultBranch){
|
if(pullreq.requestBranch == repository.repository.defaultBranch){
|
||||||
commits.map{ commit =>
|
commits.map { commit =>
|
||||||
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name)
|
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,10 +255,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
commits.flatten.foreach { commit =>
|
commits.flatten.foreach { commit =>
|
||||||
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name)
|
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name)
|
||||||
}
|
}
|
||||||
issue.content match {
|
closeIssuesFromMessage(issue.title + " " + issue.content.getOrElse(""), loginAccount.userName, owner, name)
|
||||||
case Some(content) => closeIssuesFromMessage(content, loginAccount.userName, owner, name)
|
|
||||||
case _ =>
|
|
||||||
}
|
|
||||||
closeIssuesFromMessage(form.message, loginAccount.userName, owner, name)
|
closeIssuesFromMessage(form.message, loginAccount.userName, owner, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,7 +351,15 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
originRepository.owner, originRepository.name, oldId.getName,
|
originRepository.owner, originRepository.name, oldId.getName,
|
||||||
forkedRepository.owner, forkedRepository.name, newId.getName)
|
forkedRepository.owner, forkedRepository.name, newId.getName)
|
||||||
|
|
||||||
|
val title = if(commits.flatten.length == 1){
|
||||||
|
commits.flatten.head.shortMessage
|
||||||
|
} else {
|
||||||
|
val text = forkedId.replaceAll("[\\-_]", " ")
|
||||||
|
text.substring(0, 1).toUpperCase + text.substring(1)
|
||||||
|
}
|
||||||
|
|
||||||
html.compare(
|
html.compare(
|
||||||
|
title,
|
||||||
commits,
|
commits,
|
||||||
diffs,
|
diffs,
|
||||||
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
|
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
|
||||||
@@ -515,10 +520,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
val sessionKey = Keys.Session.Pulls(owner, repoName)
|
val sessionKey = Keys.Session.Pulls(owner, repoName)
|
||||||
|
|
||||||
// retrieve search condition
|
// retrieve search condition
|
||||||
val condition = session.putAndGet(sessionKey,
|
val condition = IssueSearchCondition(request)
|
||||||
if(request.hasQueryString) IssueSearchCondition(request)
|
|
||||||
else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
|
|
||||||
)
|
|
||||||
|
|
||||||
gitbucket.core.issues.html.list(
|
gitbucket.core.issues.html.list(
|
||||||
"pulls",
|
"pulls",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import org.scalatra.i18n.Messages
|
|||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.lib.Constants
|
import org.eclipse.jgit.lib.Constants
|
||||||
import org.eclipse.jgit.lib.ObjectId
|
import org.eclipse.jgit.lib.ObjectId
|
||||||
|
import gitbucket.core.model.WebHookContentType
|
||||||
|
|
||||||
|
|
||||||
class RepositorySettingsController extends RepositorySettingsControllerBase
|
class RepositorySettingsController extends RepositorySettingsControllerBase
|
||||||
@@ -26,12 +27,26 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
with OwnerAuthenticator with UsersAuthenticator =>
|
with OwnerAuthenticator with UsersAuthenticator =>
|
||||||
|
|
||||||
// for repository options
|
// for repository options
|
||||||
case class OptionsForm(repositoryName: String, description: Option[String], isPrivate: Boolean)
|
case class OptionsForm(
|
||||||
|
repositoryName: String,
|
||||||
|
description: Option[String],
|
||||||
|
isPrivate: Boolean,
|
||||||
|
enableIssues: Boolean,
|
||||||
|
externalIssuesUrl: Option[String],
|
||||||
|
enableWiki: Boolean,
|
||||||
|
allowWikiEditing: Boolean,
|
||||||
|
externalWikiUrl: Option[String]
|
||||||
|
)
|
||||||
|
|
||||||
val optionsForm = mapping(
|
val optionsForm = mapping(
|
||||||
"repositoryName" -> trim(label("Repository Name", text(required, maxlength(40), identifier, renameRepositoryName))),
|
"repositoryName" -> trim(label("Repository Name" , text(required, maxlength(40), identifier, renameRepositoryName))),
|
||||||
"description" -> trim(label("Description" , optional(text()))),
|
"description" -> trim(label("Description" , optional(text()))),
|
||||||
"isPrivate" -> trim(label("Repository Type", boolean()))
|
"isPrivate" -> trim(label("Repository Type" , boolean())),
|
||||||
|
"enableIssues" -> trim(label("Enable Issues" , boolean())),
|
||||||
|
"externalIssuesUrl" -> trim(label("External Issues URL", optional(text(maxlength(200))))),
|
||||||
|
"enableWiki" -> trim(label("Enable Wiki" , boolean())),
|
||||||
|
"allowWikiEditing" -> trim(label("Allow Wiki Editing" , boolean())),
|
||||||
|
"externalWikiUrl" -> trim(label("External Wiki URL" , optional(text(maxlength(200)))))
|
||||||
)(OptionsForm.apply)
|
)(OptionsForm.apply)
|
||||||
|
|
||||||
// for default branch
|
// for default branch
|
||||||
@@ -49,13 +64,16 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
)(CollaboratorForm.apply)
|
)(CollaboratorForm.apply)
|
||||||
|
|
||||||
// for web hook url addition
|
// for web hook url addition
|
||||||
case class WebHookForm(url: String, events: Set[WebHook.Event], token: Option[String])
|
case class WebHookForm(url: String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])
|
||||||
|
|
||||||
def webHookForm(update:Boolean) = mapping(
|
def webHookForm(update:Boolean) = mapping(
|
||||||
"url" -> trim(label("url", text(required, webHook(update)))),
|
"url" -> trim(label("url", text(required, webHook(update)))),
|
||||||
"events" -> webhookEvents,
|
"events" -> webhookEvents,
|
||||||
|
"ctype" -> label("ctype", text()),
|
||||||
"token" -> optional(trim(label("token", text(maxlength(100)))))
|
"token" -> optional(trim(label("token", text(maxlength(100)))))
|
||||||
)(WebHookForm.apply)
|
)(
|
||||||
|
(url, events, ctype, token) => WebHookForm(url, events, WebHookContentType.valueOf(ctype), token)
|
||||||
|
)
|
||||||
|
|
||||||
// for transfer ownership
|
// for transfer ownership
|
||||||
case class TransferOwnerShipForm(newOwner: String)
|
case class TransferOwnerShipForm(newOwner: String)
|
||||||
@@ -88,7 +106,12 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
form.description,
|
form.description,
|
||||||
repository.repository.parentUserName.map { _ =>
|
repository.repository.parentUserName.map { _ =>
|
||||||
repository.repository.isPrivate
|
repository.repository.isPrivate
|
||||||
} getOrElse form.isPrivate
|
} getOrElse form.isPrivate,
|
||||||
|
form.enableIssues,
|
||||||
|
form.externalIssuesUrl,
|
||||||
|
form.enableWiki,
|
||||||
|
form.allowWikiEditing,
|
||||||
|
form.externalWikiUrl
|
||||||
)
|
)
|
||||||
// Change repository name
|
// Change repository name
|
||||||
if(repository.name != form.repositoryName){
|
if(repository.name != form.repositoryName){
|
||||||
@@ -183,7 +206,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
* Display the web hook edit page.
|
* Display the web hook edit page.
|
||||||
*/
|
*/
|
||||||
get("/:owner/:repository/settings/hooks/new")(ownerOnly { repository =>
|
get("/:owner/:repository/settings/hooks/new")(ownerOnly { repository =>
|
||||||
val webhook = WebHook(repository.owner, repository.name, "", None)
|
val webhook = WebHook(repository.owner, repository.name, "", WebHookContentType.FORM, None)
|
||||||
html.edithooks(webhook, Set(WebHook.Push), repository, flash.get("info"), true)
|
html.edithooks(webhook, Set(WebHook.Push), repository, flash.get("info"), true)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -191,7 +214,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
* Add the web hook URL.
|
* Add the web hook URL.
|
||||||
*/
|
*/
|
||||||
post("/:owner/:repository/settings/hooks/new", webHookForm(false))(ownerOnly { (form, repository) =>
|
post("/:owner/:repository/settings/hooks/new", webHookForm(false))(ownerOnly { (form, repository) =>
|
||||||
addWebHook(repository.owner, repository.name, form.url, form.events, form.token)
|
addWebHook(repository.owner, repository.name, form.url, form.events, form.ctype, form.token)
|
||||||
flash += "info" -> s"Webhook ${form.url} created"
|
flash += "info" -> s"Webhook ${form.url} created"
|
||||||
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
||||||
})
|
})
|
||||||
@@ -221,7 +244,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
val url = params("url")
|
val url = params("url")
|
||||||
val token = Some(params("token"))
|
val token = Some(params("token"))
|
||||||
val dummyWebHookInfo = WebHook(repository.owner, repository.name, url, token)
|
val ctype = WebHookContentType.valueOf(params("ctype"))
|
||||||
|
val dummyWebHookInfo = WebHook(repository.owner, repository.name, url, ctype, token)
|
||||||
val dummyPayload = {
|
val dummyPayload = {
|
||||||
val ownerAccount = getAccountByUserName(repository.owner).get
|
val ownerAccount = getAccountByUserName(repository.owner).get
|
||||||
val commits = if(repository.commitCount == 0) List.empty else git.log
|
val commits = if(repository.commitCount == 0) List.empty else git.log
|
||||||
@@ -280,7 +304,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
* Update web hook settings.
|
* Update web hook settings.
|
||||||
*/
|
*/
|
||||||
post("/:owner/:repository/settings/hooks/edit", webHookForm(true))(ownerOnly { (form, repository) =>
|
post("/:owner/:repository/settings/hooks/edit", webHookForm(true))(ownerOnly { (form, repository) =>
|
||||||
updateWebHook(repository.owner, repository.name, form.url, form.events, form.token)
|
updateWebHook(repository.owner, repository.name, form.url, form.events, form.ctype, form.token)
|
||||||
flash += "info" -> s"webhook ${form.url} updated"
|
flash += "info" -> s"webhook ${form.url} updated"
|
||||||
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
||||||
})
|
})
|
||||||
@@ -289,7 +313,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
* Display the danger zone.
|
* Display the danger zone.
|
||||||
*/
|
*/
|
||||||
get("/:owner/:repository/settings/danger")(ownerOnly {
|
get("/:owner/:repository/settings/danger")(ownerOnly {
|
||||||
html.danger(_)
|
html.danger(_, flash.get("info"))
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -328,6 +352,19 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
redirect(s"/${repository.owner}")
|
redirect(s"/${repository.owner}")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run GC
|
||||||
|
*/
|
||||||
|
post("/:owner/:repository/settings/gc")(ownerOnly { repository =>
|
||||||
|
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
||||||
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||||
|
git.gc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flash += "info" -> "Garbage collection has been executed."
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/settings/danger")
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides duplication check for web hook url.
|
* Provides duplication check for web hook url.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -117,15 +117,20 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
/**
|
/**
|
||||||
* Displays the file list of the repository root and the default branch.
|
* Displays the file list of the repository root and the default branch.
|
||||||
*/
|
*/
|
||||||
get("/:owner/:repository")(referrersOnly {
|
get("/:owner/:repository") {
|
||||||
fileList(_)
|
params.get("go-get") match {
|
||||||
})
|
case Some("1") => defining(request.paths){ paths =>
|
||||||
|
getRepository(paths(0), paths(1)).map(gitbucket.core.html.goget(_))getOrElse NotFound()
|
||||||
|
}
|
||||||
|
case _ => referrersOnly(fileList(_))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the file list of the specified path and branch.
|
* Displays the file list of the specified path and branch.
|
||||||
*/
|
*/
|
||||||
get("/:owner/:repository/tree/*")(referrersOnly { repository =>
|
get("/:owner/:repository/tree/*")(referrersOnly { repository =>
|
||||||
val (id, path) = splitPath(repository, multiParams("splat").head)
|
val (id, path) = repository.splitPath(multiParams("splat").head)
|
||||||
if(path.isEmpty){
|
if(path.isEmpty){
|
||||||
fileList(repository, id)
|
fileList(repository, id)
|
||||||
} else {
|
} else {
|
||||||
@@ -137,7 +142,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
* Displays the commit list of the specified resource.
|
* Displays the commit list of the specified resource.
|
||||||
*/
|
*/
|
||||||
get("/:owner/:repository/commits/*")(referrersOnly { repository =>
|
get("/:owner/:repository/commits/*")(referrersOnly { repository =>
|
||||||
val (branchName, path) = splitPath(repository, multiParams("splat").head)
|
val (branchName, path) = repository.splitPath(multiParams("splat").head)
|
||||||
val page = params.get("page").flatMap(_.toIntOpt).getOrElse(1)
|
val page = params.get("page").flatMap(_.toIntOpt).getOrElse(1)
|
||||||
|
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
@@ -153,7 +158,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/new/*")(collaboratorsOnly { repository =>
|
get("/:owner/:repository/new/*")(collaboratorsOnly { repository =>
|
||||||
val (branch, path) = splitPath(repository, multiParams("splat").head)
|
val (branch, path) = repository.splitPath(multiParams("splat").head)
|
||||||
val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch).needStatusCheck(context.loginAccount.get.userName)
|
val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch).needStatusCheck(context.loginAccount.get.userName)
|
||||||
html.editor(branch, repository, if(path.length == 0) Nil else path.split("/").toList,
|
html.editor(branch, repository, if(path.length == 0) Nil else path.split("/").toList,
|
||||||
None, JGitUtil.ContentInfo("text", None, Some("UTF-8")),
|
None, JGitUtil.ContentInfo("text", None, Some("UTF-8")),
|
||||||
@@ -161,7 +166,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/edit/*")(collaboratorsOnly { repository =>
|
get("/:owner/:repository/edit/*")(collaboratorsOnly { repository =>
|
||||||
val (branch, path) = splitPath(repository, multiParams("splat").head)
|
val (branch, path) = repository.splitPath(multiParams("splat").head)
|
||||||
val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch).needStatusCheck(context.loginAccount.get.userName)
|
val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch).needStatusCheck(context.loginAccount.get.userName)
|
||||||
|
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
@@ -177,7 +182,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/remove/*")(collaboratorsOnly { repository =>
|
get("/:owner/:repository/remove/*")(collaboratorsOnly { repository =>
|
||||||
val (branch, path) = splitPath(repository, multiParams("splat").head)
|
val (branch, path) = repository.splitPath(multiParams("splat").head)
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
|
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
|
||||||
|
|
||||||
@@ -235,7 +240,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/raw/*")(referrersOnly { repository =>
|
get("/:owner/:repository/raw/*")(referrersOnly { repository =>
|
||||||
val (id, path) = splitPath(repository, multiParams("splat").head)
|
val (id, path) = repository.splitPath(multiParams("splat").head)
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
||||||
getPathObjectId(git, path, revCommit).flatMap { objectId =>
|
getPathObjectId(git, path, revCommit).flatMap { objectId =>
|
||||||
@@ -253,7 +258,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
* Displays the file content of the specified branch or commit.
|
* Displays the file content of the specified branch or commit.
|
||||||
*/
|
*/
|
||||||
val blobRoute = get("/:owner/:repository/blob/*")(referrersOnly { repository =>
|
val blobRoute = get("/:owner/:repository/blob/*")(referrersOnly { repository =>
|
||||||
val (id, path) = splitPath(repository, multiParams("splat").head)
|
val (id, path) = repository.splitPath(multiParams("splat").head)
|
||||||
val raw = params.get("raw").getOrElse("false").toBoolean
|
val raw = params.get("raw").getOrElse("false").toBoolean
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
||||||
@@ -285,7 +290,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
* Blame data.
|
* Blame data.
|
||||||
*/
|
*/
|
||||||
ajaxGet("/:owner/:repository/get-blame/*")(referrersOnly { repository =>
|
ajaxGet("/:owner/:repository/get-blame/*")(referrersOnly { repository =>
|
||||||
val (id, path) = splitPath(repository, multiParams("splat").head)
|
val (id, path) = repository.splitPath(multiParams("splat").head)
|
||||||
contentType = formats("json")
|
contentType = formats("json")
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
val last = git.log.add(git.getRepository.resolve(id)).addPath(path).setMaxCount(1).call.iterator.next.name
|
val last = git.log.add(git.getRepository.resolve(id)).addPath(path).setMaxCount(1).call.iterator.next.name
|
||||||
@@ -376,8 +381,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
getCommitComment(repository.owner, repository.name, params("id")) map { x =>
|
getCommitComment(repository.owner, repository.name, params("id")) map { x =>
|
||||||
if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
|
if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
|
||||||
params.get("dataType") collect {
|
params.get("dataType") collect {
|
||||||
case t if t == "html" => html.editcomment(
|
case t if t == "html" => html.editcomment(x.content, x.commentId, repository)
|
||||||
x.content, x.commentId, x.userName, x.repositoryName)
|
|
||||||
} getOrElse {
|
} getOrElse {
|
||||||
contentType = formats("json")
|
contentType = formats("json")
|
||||||
org.json4s.jackson.Serialization.write(
|
org.json4s.jackson.Serialization.write(
|
||||||
@@ -497,6 +501,10 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
getForkedRepositories(
|
getForkedRepositories(
|
||||||
repository.repository.originUserName.getOrElse(repository.owner),
|
repository.repository.originUserName.getOrElse(repository.owner),
|
||||||
repository.repository.originRepositoryName.getOrElse(repository.name)),
|
repository.repository.originRepositoryName.getOrElse(repository.name)),
|
||||||
|
context.loginAccount match {
|
||||||
|
case None => List()
|
||||||
|
case account: Option[Account] => getGroupsByUserName(account.get.userName)
|
||||||
|
}, // groups of current user
|
||||||
repository)
|
repository)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -507,13 +515,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
val ref = multiParams("splat").head
|
val ref = multiParams("splat").head
|
||||||
JGitUtil.getTreeId(git, ref).map{ treeId =>
|
JGitUtil.getTreeId(git, ref).map{ treeId =>
|
||||||
html.find(ref,
|
html.find(ref, treeId, repository)
|
||||||
treeId,
|
|
||||||
repository,
|
|
||||||
context.loginAccount match {
|
|
||||||
case None => List()
|
|
||||||
case account: Option[Account] => getGroupsByUserName(account.get.userName)
|
|
||||||
})
|
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -529,17 +531,6 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
private def splitPath(repository: RepositoryService.RepositoryInfo, path: String): (String, String) = {
|
|
||||||
val id = repository.branchList.collectFirst {
|
|
||||||
case branch if(path == branch || path.startsWith(branch + "/")) => branch
|
|
||||||
} orElse repository.tags.collectFirst {
|
|
||||||
case tag if(path == tag.name || path.startsWith(tag.name + "/")) => tag.name
|
|
||||||
} getOrElse path.split("/")(0)
|
|
||||||
|
|
||||||
(id, path.substring(id.length).stripPrefix("/"))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private val readmeFiles = PluginRegistry().renderableExtensions.map { extension =>
|
private val readmeFiles = PluginRegistry().renderableExtensions.map { extension =>
|
||||||
s"readme.${extension}"
|
s"readme.${extension}"
|
||||||
} ++ Seq("readme.txt", "readme")
|
} ++ Seq("readme.txt", "readme")
|
||||||
@@ -575,10 +566,6 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
html.files(revision, repository,
|
html.files(revision, repository,
|
||||||
if(path == ".") Nil else path.split("/").toList, // current path
|
if(path == ".") Nil else path.split("/").toList, // current path
|
||||||
context.loginAccount match {
|
|
||||||
case None => List()
|
|
||||||
case account: Option[Account] => getGroupsByUserName(account.get.userName)
|
|
||||||
}, // groups of current user
|
|
||||||
new JGitUtil.CommitInfo(lastModifiedCommit), // last modified commit
|
new JGitUtil.CommitInfo(lastModifiedCommit), // last modified commit
|
||||||
files, readme, hasWritePermission(repository.owner, repository.name, context.loginAccount),
|
files, readme, hasWritePermission(repository.owner, repository.name, context.loginAccount),
|
||||||
getPullRequestFromBranch(repository.owner, repository.name, revstr, repository.repository.defaultBranch),
|
getPullRequestFromBranch(repository.owner, repository.name, revstr, repository.repository.defaultBranch),
|
||||||
@@ -697,8 +684,6 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
|
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
|
||||||
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
override protected def renderUncaughtException(e: Throwable)(implicit request: HttpServletRequest, response: HttpServletResponse): Unit = {
|
override protected def renderUncaughtException(e: Throwable)(implicit request: HttpServletRequest, response: HttpServletResponse): Unit = {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
|
import java.io.FileInputStream
|
||||||
|
|
||||||
import gitbucket.core.admin.html
|
import gitbucket.core.admin.html
|
||||||
import gitbucket.core.service.{AccountService, SystemSettingsService, RepositoryService}
|
import gitbucket.core.service.{AccountService, RepositoryService, SystemSettingsService}
|
||||||
import gitbucket.core.util.AdminAuthenticator
|
import gitbucket.core.util.{AdminAuthenticator, Mailer}
|
||||||
import gitbucket.core.ssh.SshServer
|
import gitbucket.core.ssh.SshServer
|
||||||
import gitbucket.core.plugin.PluginRegistry
|
import gitbucket.core.plugin.PluginRegistry
|
||||||
import SystemSettingsService._
|
import SystemSettingsService._
|
||||||
@@ -11,7 +13,7 @@ import gitbucket.core.util.ControlUtil._
|
|||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
import gitbucket.core.util.StringUtil._
|
import gitbucket.core.util.StringUtil._
|
||||||
import io.github.gitbucket.scalatra.forms._
|
import io.github.gitbucket.scalatra.forms._
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.{FileUtils, IOUtils}
|
||||||
import org.scalatra.i18n.Messages
|
import org.scalatra.i18n.Messages
|
||||||
|
|
||||||
class SystemSettingsController extends SystemSettingsControllerBase
|
class SystemSettingsController extends SystemSettingsControllerBase
|
||||||
@@ -68,12 +70,22 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
).flatten
|
).flatten
|
||||||
}
|
}
|
||||||
|
|
||||||
private val pluginForm = mapping(
|
private val sendMailForm = mapping(
|
||||||
"pluginId" -> list(trim(label("", text())))
|
"smtp" -> mapping(
|
||||||
)(PluginForm.apply)
|
"host" -> trim(label("SMTP Host", text(required))),
|
||||||
|
"port" -> trim(label("SMTP Port", optional(number()))),
|
||||||
|
"user" -> trim(label("SMTP User", optional(text()))),
|
||||||
|
"password" -> trim(label("SMTP Password", optional(text()))),
|
||||||
|
"ssl" -> trim(label("Enable SSL", optional(boolean()))),
|
||||||
|
"fromAddress" -> trim(label("FROM Address", optional(text()))),
|
||||||
|
"fromName" -> trim(label("FROM Name", optional(text())))
|
||||||
|
)(Smtp.apply),
|
||||||
|
"testAddress" -> trim(label("", text(required)))
|
||||||
|
)(SendMailForm.apply)
|
||||||
|
|
||||||
case class PluginForm(pluginIds: List[String])
|
case class SendMailForm(smtp: Smtp, testAddress: String)
|
||||||
|
|
||||||
|
case class DataExportForm(tableNames: List[String])
|
||||||
|
|
||||||
case class NewUserForm(userName: String, password: String, fullName: String,
|
case class NewUserForm(userName: String, password: String, fullName: String,
|
||||||
mailAddress: String, isAdmin: Boolean,
|
mailAddress: String, isAdmin: Boolean,
|
||||||
@@ -91,7 +103,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
|
|
||||||
|
|
||||||
val newUserForm = mapping(
|
val newUserForm = mapping(
|
||||||
"userName" -> trim(label("Username" ,text(required, maxlength(100), identifier, uniqueUserName))),
|
"userName" -> trim(label("Username" ,text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||||
"password" -> trim(label("Password" ,text(required, maxlength(20)))),
|
"password" -> trim(label("Password" ,text(required, maxlength(20)))),
|
||||||
"fullName" -> trim(label("Full Name" ,text(required, maxlength(100)))),
|
"fullName" -> trim(label("Full Name" ,text(required, maxlength(100)))),
|
||||||
"mailAddress" -> trim(label("Mail Address" ,text(required, maxlength(100), uniqueMailAddress()))),
|
"mailAddress" -> trim(label("Mail Address" ,text(required, maxlength(100), uniqueMailAddress()))),
|
||||||
@@ -113,7 +125,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
)(EditUserForm.apply)
|
)(EditUserForm.apply)
|
||||||
|
|
||||||
val newGroupForm = mapping(
|
val newGroupForm = mapping(
|
||||||
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier, uniqueUserName))),
|
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||||
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
||||||
"fileId" -> trim(label("File ID" ,optional(text()))),
|
"fileId" -> trim(label("File ID" ,optional(text()))),
|
||||||
"members" -> trim(label("Members" ,text(required, members)))
|
"members" -> trim(label("Members" ,text(required, members)))
|
||||||
@@ -149,6 +161,18 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
redirect("/admin/system")
|
redirect("/admin/system")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
post("/admin/system/sendmail", sendMailForm)(adminOnly { form =>
|
||||||
|
try {
|
||||||
|
new Mailer(form.smtp).send(form.testAddress,
|
||||||
|
"Test message from GitBucket", "This is a test message from GitBucket.")
|
||||||
|
|
||||||
|
"Test mail has been sent to: " + form.testAddress
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
case e: Exception => "[Error] " + e.toString
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
get("/admin/plugins")(adminOnly {
|
get("/admin/plugins")(adminOnly {
|
||||||
html.plugins(PluginRegistry().getPlugins())
|
html.plugins(PluginRegistry().getPlugins())
|
||||||
})
|
})
|
||||||
@@ -176,13 +200,16 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
|
|
||||||
get("/admin/users/:userName/_edituser")(adminOnly {
|
get("/admin/users/:userName/_edituser")(adminOnly {
|
||||||
val userName = params("userName")
|
val userName = params("userName")
|
||||||
html.user(getAccountByUserName(userName, true))
|
html.user(getAccountByUserName(userName, true), flash.get("error"))
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/admin/users/:name/_edituser", editUserForm)(adminOnly { form =>
|
post("/admin/users/:name/_edituser", editUserForm)(adminOnly { form =>
|
||||||
val userName = params("userName")
|
val userName = params("userName")
|
||||||
getAccountByUserName(userName, true).map { account =>
|
getAccountByUserName(userName, true).map { account =>
|
||||||
|
if(account.isAdmin && (form.isRemoved || !form.isAdmin) && isLastAdministrator(account)){
|
||||||
|
flash += "error" -> "Account can't be turned off because this is last one administrator."
|
||||||
|
redirect(s"/admin/users/${userName}/_edituser")
|
||||||
|
} else {
|
||||||
if(form.isRemoved){
|
if(form.isRemoved){
|
||||||
// Remove repositories
|
// Remove repositories
|
||||||
// getRepositoryNamesOfUser(userName).foreach { repositoryName =>
|
// getRepositoryNamesOfUser(userName).foreach { repositoryName =>
|
||||||
@@ -205,7 +232,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
|
|
||||||
updateImage(userName, form.fileId, form.clearImage)
|
updateImage(userName, form.fileId, form.clearImage)
|
||||||
redirect("/admin/users")
|
redirect("/admin/users")
|
||||||
|
}
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -268,6 +295,27 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
get("/admin/data")(adminOnly {
|
||||||
|
import gitbucket.core.util.JDBCUtil._
|
||||||
|
val session = request2Session(request)
|
||||||
|
html.data(session.conn.allTableNames())
|
||||||
|
})
|
||||||
|
|
||||||
|
post("/admin/export")(adminOnly {
|
||||||
|
import gitbucket.core.util.JDBCUtil._
|
||||||
|
val file = request2Session(request).conn.exportAsSQL(request.getParameterValues("tableNames").toSeq)
|
||||||
|
|
||||||
|
contentType = "application/octet-stream"
|
||||||
|
response.setHeader("Content-Disposition", "attachment; filename=" + file.getName)
|
||||||
|
response.setContentLength(file.length.toInt)
|
||||||
|
|
||||||
|
using(new FileInputStream(file)){ in =>
|
||||||
|
IOUtils.copy(in, response.outputStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
()
|
||||||
|
})
|
||||||
|
|
||||||
private def members: Constraint = new Constraint(){
|
private def members: Constraint = new Constraint(){
|
||||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||||
if(value.split(",").exists {
|
if(value.split(",").exists {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
import gitbucket.core.wiki.html
|
import gitbucket.core.wiki.html
|
||||||
import gitbucket.core.service.{RepositoryService, WikiService, ActivityService, AccountService}
|
import gitbucket.core.service.{AccountService, ActivityService, RepositoryService, WikiService}
|
||||||
import gitbucket.core.util._
|
import gitbucket.core.util._
|
||||||
import gitbucket.core.util.StringUtil._
|
import gitbucket.core.util.StringUtil._
|
||||||
import gitbucket.core.util.ControlUtil._
|
import gitbucket.core.util.ControlUtil._
|
||||||
@@ -12,7 +13,8 @@ import org.eclipse.jgit.api.Git
|
|||||||
import org.scalatra.i18n.Messages
|
import org.scalatra.i18n.Messages
|
||||||
|
|
||||||
class WikiController extends WikiControllerBase
|
class WikiController extends WikiControllerBase
|
||||||
with WikiService with RepositoryService with AccountService with ActivityService with CollaboratorsAuthenticator with ReferrerAuthenticator
|
with WikiService with RepositoryService with AccountService with ActivityService
|
||||||
|
with CollaboratorsAuthenticator with ReferrerAuthenticator
|
||||||
|
|
||||||
trait WikiControllerBase extends ControllerBase {
|
trait WikiControllerBase extends ControllerBase {
|
||||||
self: WikiService with RepositoryService with ActivityService with CollaboratorsAuthenticator with ReferrerAuthenticator =>
|
self: WikiService with RepositoryService with ActivityService with CollaboratorsAuthenticator with ReferrerAuthenticator =>
|
||||||
@@ -38,7 +40,7 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
get("/:owner/:repository/wiki")(referrersOnly { repository =>
|
get("/:owner/:repository/wiki")(referrersOnly { repository =>
|
||||||
getWikiPage(repository.owner, repository.name, "Home").map { page =>
|
getWikiPage(repository.owner, repository.name, "Home").map { page =>
|
||||||
html.page("Home", page, getWikiPageList(repository.owner, repository.name),
|
html.page("Home", page, getWikiPageList(repository.owner, repository.name),
|
||||||
repository, hasWritePermission(repository.owner, repository.name, context.loginAccount),
|
repository, isEditable(repository),
|
||||||
getWikiPage(repository.owner, repository.name, "_Sidebar"),
|
getWikiPage(repository.owner, repository.name, "_Sidebar"),
|
||||||
getWikiPage(repository.owner, repository.name, "_Footer"))
|
getWikiPage(repository.owner, repository.name, "_Footer"))
|
||||||
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/Home/_edit")
|
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/Home/_edit")
|
||||||
@@ -49,7 +51,7 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
getWikiPage(repository.owner, repository.name, pageName).map { page =>
|
getWikiPage(repository.owner, repository.name, pageName).map { page =>
|
||||||
html.page(pageName, page, getWikiPageList(repository.owner, repository.name),
|
html.page(pageName, page, getWikiPageList(repository.owner, repository.name),
|
||||||
repository, hasWritePermission(repository.owner, repository.name, context.loginAccount),
|
repository, isEditable(repository),
|
||||||
getWikiPage(repository.owner, repository.name, "_Sidebar"),
|
getWikiPage(repository.owner, repository.name, "_Sidebar"),
|
||||||
getWikiPage(repository.owner, repository.name, "_Footer"))
|
getWikiPage(repository.owner, repository.name, "_Footer"))
|
||||||
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_edit")
|
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_edit")
|
||||||
@@ -61,7 +63,7 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
JGitUtil.getCommitLog(git, "master", path = pageName + ".md") match {
|
JGitUtil.getCommitLog(git, "master", path = pageName + ".md") match {
|
||||||
case Right((logs, hasNext)) => html.history(Some(pageName), logs, repository)
|
case Right((logs, hasNext)) => html.history(Some(pageName), logs, repository)
|
||||||
case Left(_) => NotFound
|
case Left(_) => NotFound()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -72,7 +74,7 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
html.compare(Some(pageName), from, to, JGitUtil.getDiffs(git, from, to, true).filter(_.newPath == pageName + ".md"), repository,
|
html.compare(Some(pageName), from, to, JGitUtil.getDiffs(git, from, to, true).filter(_.newPath == pageName + ".md"), repository,
|
||||||
hasWritePermission(repository.owner, repository.name, context.loginAccount), flash.get("info"))
|
isEditable(repository), flash.get("info"))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -81,11 +83,12 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
html.compare(None, from, to, JGitUtil.getDiffs(git, from, to, true), repository,
|
html.compare(None, from, to, JGitUtil.getDiffs(git, from, to, true), repository,
|
||||||
hasWritePermission(repository.owner, repository.name, context.loginAccount), flash.get("info"))
|
isEditable(repository), flash.get("info"))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/:page/_revert/:commitId")(collaboratorsOnly { repository =>
|
get("/:owner/:repository/wiki/:page/_revert/:commitId")(referrersOnly { repository =>
|
||||||
|
if(isEditable(repository)){
|
||||||
val pageName = StringUtil.urlDecode(params("page"))
|
val pageName = StringUtil.urlDecode(params("page"))
|
||||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||||
|
|
||||||
@@ -95,9 +98,11 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
flash += "info" -> "This patch was not able to be reversed."
|
flash += "info" -> "This patch was not able to be reversed."
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/${from}...${to}")
|
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/${from}...${to}")
|
||||||
}
|
}
|
||||||
|
} else Unauthorized()
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_revert/:commitId")(collaboratorsOnly { repository =>
|
get("/:owner/:repository/wiki/_revert/:commitId")(referrersOnly { repository =>
|
||||||
|
if(isEditable(repository)){
|
||||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||||
|
|
||||||
if(revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, None)){
|
if(revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, None)){
|
||||||
@@ -106,14 +111,18 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
flash += "info" -> "This patch was not able to be reversed."
|
flash += "info" -> "This patch was not able to be reversed."
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki/_compare/${from}...${to}")
|
redirect(s"/${repository.owner}/${repository.name}/wiki/_compare/${from}...${to}")
|
||||||
}
|
}
|
||||||
|
} else Unauthorized()
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/:page/_edit")(collaboratorsOnly { repository =>
|
get("/:owner/:repository/wiki/:page/_edit")(referrersOnly { repository =>
|
||||||
|
if(isEditable(repository)){
|
||||||
val pageName = StringUtil.urlDecode(params("page"))
|
val pageName = StringUtil.urlDecode(params("page"))
|
||||||
html.edit(pageName, getWikiPage(repository.owner, repository.name, pageName), repository)
|
html.edit(pageName, getWikiPage(repository.owner, repository.name, pageName), repository)
|
||||||
|
} else Unauthorized()
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/wiki/_edit", editForm)(collaboratorsOnly { (form, repository) =>
|
post("/:owner/:repository/wiki/_edit", editForm)(referrersOnly { (form, repository) =>
|
||||||
|
if(isEditable(repository)){
|
||||||
defining(context.loginAccount.get){ loginAccount =>
|
defining(context.loginAccount.get){ loginAccount =>
|
||||||
saveWikiPage(
|
saveWikiPage(
|
||||||
repository.owner,
|
repository.owner,
|
||||||
@@ -134,13 +143,17 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else Unauthorized()
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_new")(collaboratorsOnly {
|
get("/:owner/:repository/wiki/_new")(referrersOnly { repository =>
|
||||||
html.edit("", None, _)
|
if(isEditable(repository)){
|
||||||
|
html.edit("", None, repository)
|
||||||
|
} else Unauthorized()
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/wiki/_new", newForm)(collaboratorsOnly { (form, repository) =>
|
post("/:owner/:repository/wiki/_new", newForm)(referrersOnly { (form, repository) =>
|
||||||
|
if(isEditable(repository)){
|
||||||
defining(context.loginAccount.get){ loginAccount =>
|
defining(context.loginAccount.get){ loginAccount =>
|
||||||
saveWikiPage(repository.owner, repository.name, form.currentPageName, form.pageName,
|
saveWikiPage(repository.owner, repository.name, form.currentPageName, form.pageName,
|
||||||
form.content, loginAccount, form.message.getOrElse(""), None)
|
form.content, loginAccount, form.message.getOrElse(""), None)
|
||||||
@@ -154,9 +167,11 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else Unauthorized()
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/:page/_delete")(collaboratorsOnly { repository =>
|
get("/:owner/:repository/wiki/:page/_delete")(referrersOnly { repository =>
|
||||||
|
if(isEditable(repository)){
|
||||||
val pageName = StringUtil.urlDecode(params("page"))
|
val pageName = StringUtil.urlDecode(params("page"))
|
||||||
|
|
||||||
defining(context.loginAccount.get){ loginAccount =>
|
defining(context.loginAccount.get){ loginAccount =>
|
||||||
@@ -165,18 +180,18 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||||
}
|
}
|
||||||
|
} else Unauthorized()
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_pages")(referrersOnly { repository =>
|
get("/:owner/:repository/wiki/_pages")(referrersOnly { repository =>
|
||||||
html.pages(getWikiPageList(repository.owner, repository.name), repository,
|
html.pages(getWikiPageList(repository.owner, repository.name), repository, isEditable(repository))
|
||||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
|
get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
|
||||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
JGitUtil.getCommitLog(git, "master") match {
|
JGitUtil.getCommitLog(git, "master") match {
|
||||||
case Right((logs, hasNext)) => html.history(None, logs, repository)
|
case Right((logs, hasNext)) => html.history(None, logs, repository)
|
||||||
case Left(_) => NotFound
|
case Left(_) => NotFound()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -225,4 +240,9 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
private def targetWikiPage = getWikiPage(params("owner"), params("repository"), params("pageName"))
|
private def targetWikiPage = getWikiPage(params("owner"), params("repository"), params("pageName"))
|
||||||
|
|
||||||
|
private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean =
|
||||||
|
repository.repository.allowWikiEditing || (
|
||||||
|
hasWritePermission(repository.owner, repository.name, context.loginAccount)
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
package gitbucket.core.model
|
|
||||||
|
|
||||||
trait PluginComponent extends TemplateComponent { self: Profile =>
|
|
||||||
import profile.simple._
|
|
||||||
import self._
|
|
||||||
|
|
||||||
lazy val Plugins = TableQuery[Plugins]
|
|
||||||
|
|
||||||
class Plugins(tag: Tag) extends Table[Plugin](tag, "PLUGIN"){
|
|
||||||
val pluginId = column[String]("PLUGIN_ID", O PrimaryKey)
|
|
||||||
val version = column[String]("VERSION")
|
|
||||||
def * = (pluginId, version) <> (Plugin.tupled, Plugin.unapply)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case class Plugin(
|
|
||||||
pluginId: String,
|
|
||||||
version: String
|
|
||||||
)
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package gitbucket.core.model
|
package gitbucket.core.model
|
||||||
|
|
||||||
|
import gitbucket.core.util.DatabaseConfig
|
||||||
|
|
||||||
trait Profile {
|
trait Profile {
|
||||||
val profile: slick.driver.JdbcProfile
|
val profile: slick.driver.JdbcProfile
|
||||||
@@ -28,7 +29,9 @@ trait Profile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
trait ProfileProvider { self: Profile =>
|
trait ProfileProvider { self: Profile =>
|
||||||
val profile = slick.driver.H2Driver
|
|
||||||
|
lazy val profile = DatabaseConfig.slickDriver
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trait CoreProfile extends ProfileProvider with Profile
|
trait CoreProfile extends ProfileProvider with Profile
|
||||||
@@ -49,7 +52,6 @@ trait CoreProfile extends ProfileProvider with Profile
|
|||||||
with SshKeyComponent
|
with SshKeyComponent
|
||||||
with WebHookComponent
|
with WebHookComponent
|
||||||
with WebHookEventComponent
|
with WebHookEventComponent
|
||||||
with PluginComponent
|
|
||||||
with ProtectedBranchComponent
|
with ProtectedBranchComponent
|
||||||
|
|
||||||
object Profile extends CoreProfile
|
object Profile extends CoreProfile
|
||||||
|
|||||||
@@ -17,7 +17,14 @@ trait RepositoryComponent extends TemplateComponent { self: Profile =>
|
|||||||
val originRepositoryName = column[String]("ORIGIN_REPOSITORY_NAME")
|
val originRepositoryName = column[String]("ORIGIN_REPOSITORY_NAME")
|
||||||
val parentUserName = column[String]("PARENT_USER_NAME")
|
val parentUserName = column[String]("PARENT_USER_NAME")
|
||||||
val parentRepositoryName = column[String]("PARENT_REPOSITORY_NAME")
|
val parentRepositoryName = column[String]("PARENT_REPOSITORY_NAME")
|
||||||
def * = (userName, repositoryName, isPrivate, description.?, defaultBranch, registeredDate, updatedDate, lastActivityDate, originUserName.?, originRepositoryName.?, parentUserName.?, parentRepositoryName.?) <> (Repository.tupled, Repository.unapply)
|
val enableIssues = column[Boolean]("ENABLE_ISSUES")
|
||||||
|
val externalIssuesUrl = column[String]("EXTERNAL_ISSUES_URL")
|
||||||
|
val enableWiki = column[Boolean]("ENABLE_WIKI")
|
||||||
|
val allowWikiEditing = column[Boolean]("ALLOW_WIKI_EDITING")
|
||||||
|
val externalWikiUrl = column[String]("EXTERNAL_WIKI_URL")
|
||||||
|
def * = (userName, repositoryName, isPrivate, description.?, defaultBranch,
|
||||||
|
registeredDate, updatedDate, lastActivityDate, originUserName.?, originRepositoryName.?, parentUserName.?, parentRepositoryName.?,
|
||||||
|
enableIssues, externalIssuesUrl.?, enableWiki, allowWikiEditing, externalWikiUrl.?) <> (Repository.tupled, Repository.unapply)
|
||||||
|
|
||||||
def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository)
|
def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository)
|
||||||
}
|
}
|
||||||
@@ -35,5 +42,10 @@ case class Repository(
|
|||||||
originUserName: Option[String],
|
originUserName: Option[String],
|
||||||
originRepositoryName: Option[String],
|
originRepositoryName: Option[String],
|
||||||
parentUserName: Option[String],
|
parentUserName: Option[String],
|
||||||
parentRepositoryName: Option[String]
|
parentRepositoryName: Option[String],
|
||||||
|
enableIssues: Boolean,
|
||||||
|
externalIssuesUrl: Option[String],
|
||||||
|
enableWiki: Boolean,
|
||||||
|
allowWikiEditing: Boolean,
|
||||||
|
externalWikiUrl: Option[String]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,21 +3,42 @@ package gitbucket.core.model
|
|||||||
trait WebHookComponent extends TemplateComponent { self: Profile =>
|
trait WebHookComponent extends TemplateComponent { self: Profile =>
|
||||||
import profile.simple._
|
import profile.simple._
|
||||||
|
|
||||||
|
implicit val whContentTypeColumnType = MappedColumnType.base[WebHookContentType, String](whct => whct.code , code => WebHookContentType.valueOf(code))
|
||||||
|
|
||||||
lazy val WebHooks = TableQuery[WebHooks]
|
lazy val WebHooks = TableQuery[WebHooks]
|
||||||
|
|
||||||
class WebHooks(tag: Tag) extends Table[WebHook](tag, "WEB_HOOK") with BasicTemplate {
|
class WebHooks(tag: Tag) extends Table[WebHook](tag, "WEB_HOOK") with BasicTemplate {
|
||||||
val url = column[String]("URL")
|
val url = column[String]("URL")
|
||||||
val token = column[Option[String]]("TOKEN", O.Nullable)
|
val token = column[Option[String]]("TOKEN", O.Nullable)
|
||||||
def * = (userName, repositoryName, url, token) <> ((WebHook.apply _).tupled, WebHook.unapply)
|
val ctype = column[WebHookContentType]("CTYPE", O.NotNull)
|
||||||
|
def * = (userName, repositoryName, url, ctype, token) <> ((WebHook.apply _).tupled, WebHook.unapply)
|
||||||
|
|
||||||
def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind)
|
def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case class WebHookContentType(val code: String, val ctype: String)
|
||||||
|
|
||||||
|
object WebHookContentType {
|
||||||
|
object JSON extends WebHookContentType("json", "application/json")
|
||||||
|
|
||||||
|
object FORM extends WebHookContentType("form", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
val values: Vector[WebHookContentType] = Vector(JSON, FORM)
|
||||||
|
|
||||||
|
private val map: Map[String, WebHookContentType] = values.map(enum => enum.code -> enum).toMap
|
||||||
|
|
||||||
|
def apply(code: String): WebHookContentType = map(code)
|
||||||
|
|
||||||
|
def valueOf(code: String): WebHookContentType = map(code)
|
||||||
|
def valueOpt(code: String): Option[WebHookContentType] = map.get(code)
|
||||||
|
}
|
||||||
|
|
||||||
case class WebHook(
|
case class WebHook(
|
||||||
userName: String,
|
userName: String,
|
||||||
repositoryName: String,
|
repositoryName: String,
|
||||||
url: String,
|
url: String,
|
||||||
|
ctype: WebHookContentType,
|
||||||
token: Option[String]
|
token: Option[String]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
package gitbucket.core.plugin
|
package gitbucket.core.plugin
|
||||||
|
|
||||||
import javax.servlet.ServletContext
|
import javax.servlet.ServletContext
|
||||||
import gitbucket.core.controller.ControllerBase
|
import gitbucket.core.controller.{Context, ControllerBase}
|
||||||
|
import gitbucket.core.model.Account
|
||||||
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||||
import gitbucket.core.util.ControlUtil._
|
import gitbucket.core.util.ControlUtil._
|
||||||
import gitbucket.core.util.Version
|
import io.github.gitbucket.solidbase.model.Version
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trait for define plugin interface.
|
* Trait for define plugin interface.
|
||||||
* To provide plugin, put Plugin class which mixed in this trait into the package root.
|
* To provide a plugin, put a Plugin class which extends this class into the package root.
|
||||||
*/
|
*/
|
||||||
trait Plugin {
|
abstract class Plugin {
|
||||||
|
|
||||||
val pluginId: String
|
val pluginId: String
|
||||||
val pluginName: String
|
val pluginName: String
|
||||||
@@ -77,6 +79,106 @@ trait Plugin {
|
|||||||
*/
|
*/
|
||||||
def receiveHooks(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[ReceiveHook] = Nil
|
def receiveHooks(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[ReceiveHook] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add global menus.
|
||||||
|
*/
|
||||||
|
val globalMenus: Seq[(Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add global menus.
|
||||||
|
*/
|
||||||
|
def globalMenus(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add repository menus.
|
||||||
|
*/
|
||||||
|
val repositoryMenus: Seq[(RepositoryInfo, Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add repository menus.
|
||||||
|
*/
|
||||||
|
def repositoryMenus(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(RepositoryInfo, Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add repository setting tabs.
|
||||||
|
*/
|
||||||
|
val repositorySettingTabs: Seq[(RepositoryInfo, Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add repository setting tabs.
|
||||||
|
*/
|
||||||
|
def repositorySettingTabs(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(RepositoryInfo, Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add profile tabs.
|
||||||
|
*/
|
||||||
|
val profileTabs: Seq[(Account, Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add profile tabs.
|
||||||
|
*/
|
||||||
|
def profileTabs(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Account, Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add system setting menus.
|
||||||
|
*/
|
||||||
|
val systemSettingMenus: Seq[(Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add system setting menus.
|
||||||
|
*/
|
||||||
|
def systemSettingMenus(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add account setting menus.
|
||||||
|
*/
|
||||||
|
val accountSettingMenus: Seq[(Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add account setting menus.
|
||||||
|
*/
|
||||||
|
def accountSettingMenus(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add dashboard tabs.
|
||||||
|
*/
|
||||||
|
val dashboardTabs: Seq[(Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add dashboard tabs.
|
||||||
|
*/
|
||||||
|
def dashboardTabs(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add assets mappings.
|
||||||
|
*/
|
||||||
|
val assetsMappings: Seq[(String, String)] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add assets mappings.
|
||||||
|
*/
|
||||||
|
def assetsMappings(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(String, String)] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add text decorators.
|
||||||
|
*/
|
||||||
|
val textDecorators: Seq[TextDecorator] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add text decorators.
|
||||||
|
*/
|
||||||
|
def textDecorators(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[TextDecorator] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add suggestion provider.
|
||||||
|
*/
|
||||||
|
val suggestionProviders: Seq[SuggestionProvider] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add suggestion provider.
|
||||||
|
*/
|
||||||
|
def suggestionProviders(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[SuggestionProvider] = Nil
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is invoked in initialization of plugin system.
|
* This method is invoked in initialization of plugin system.
|
||||||
* Register plugin functionality to PluginRegistry.
|
* Register plugin functionality to PluginRegistry.
|
||||||
@@ -100,6 +202,36 @@ trait Plugin {
|
|||||||
(receiveHooks ++ receiveHooks(registry, context, settings)).foreach { receiveHook =>
|
(receiveHooks ++ receiveHooks(registry, context, settings)).foreach { receiveHook =>
|
||||||
registry.addReceiveHook(receiveHook)
|
registry.addReceiveHook(receiveHook)
|
||||||
}
|
}
|
||||||
|
(globalMenus ++ globalMenus(registry, context, settings)).foreach { globalMenu =>
|
||||||
|
registry.addGlobalMenu(globalMenu)
|
||||||
|
}
|
||||||
|
(repositoryMenus ++ repositoryMenus(registry, context, settings)).foreach { repositoryMenu =>
|
||||||
|
registry.addRepositoryMenu(repositoryMenu)
|
||||||
|
}
|
||||||
|
(repositorySettingTabs ++ repositorySettingTabs(registry, context, settings)).foreach { repositorySettingTab =>
|
||||||
|
registry.addRepositorySettingTab(repositorySettingTab)
|
||||||
|
}
|
||||||
|
(profileTabs ++ profileTabs(registry, context, settings)).foreach { profileTab =>
|
||||||
|
registry.addProfileTab(profileTab)
|
||||||
|
}
|
||||||
|
(systemSettingMenus ++ systemSettingMenus(registry, context, settings)).foreach { systemSettingMenu =>
|
||||||
|
registry.addSystemSettingMenu(systemSettingMenu)
|
||||||
|
}
|
||||||
|
(accountSettingMenus ++ accountSettingMenus(registry, context, settings)).foreach { accountSettingMenu =>
|
||||||
|
registry.addAccountSettingMenu(accountSettingMenu)
|
||||||
|
}
|
||||||
|
(dashboardTabs ++ dashboardTabs(registry, context, settings)).foreach { dashboardTab =>
|
||||||
|
registry.addDashboardTab(dashboardTab)
|
||||||
|
}
|
||||||
|
(assetsMappings ++ assetsMappings(registry, context, settings)).foreach { assetMapping =>
|
||||||
|
registry.addAssetsMapping((assetMapping._1, assetMapping._2, getClass.getClassLoader))
|
||||||
|
}
|
||||||
|
(textDecorators ++ textDecorators(registry, context, settings)).foreach { textDecorator =>
|
||||||
|
registry.addTextDecorator(textDecorator)
|
||||||
|
}
|
||||||
|
(suggestionProviders ++ suggestionProviders(registry, context, settings)).foreach { suggestionProvider =>
|
||||||
|
registry.addSuggestionProvider(suggestionProvider)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,16 +3,18 @@ package gitbucket.core.plugin
|
|||||||
import java.io.{File, FilenameFilter, InputStream}
|
import java.io.{File, FilenameFilter, InputStream}
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
import javax.servlet.ServletContext
|
import javax.servlet.ServletContext
|
||||||
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
|
||||||
|
|
||||||
import gitbucket.core.controller.{Context, ControllerBase}
|
import gitbucket.core.controller.{Context, ControllerBase}
|
||||||
|
import gitbucket.core.model.Account
|
||||||
import gitbucket.core.service.ProtectedBranchService.ProtectedBranchReceiveHook
|
import gitbucket.core.service.ProtectedBranchService.ProtectedBranchReceiveHook
|
||||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||||
import gitbucket.core.util.ControlUtil._
|
import gitbucket.core.util.ControlUtil._
|
||||||
|
import gitbucket.core.util.DatabaseConfig
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
import gitbucket.core.util.JDBCUtil._
|
import io.github.gitbucket.solidbase.Solidbase
|
||||||
import gitbucket.core.util.{Version, Versions}
|
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
|
||||||
|
import io.github.gitbucket.solidbase.model.Module
|
||||||
import org.apache.commons.codec.binary.{Base64, StringUtils}
|
import org.apache.commons.codec.binary.{Base64, StringUtils}
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
@@ -33,9 +35,20 @@ class PluginRegistry {
|
|||||||
private val receiveHooks = new ListBuffer[ReceiveHook]
|
private val receiveHooks = new ListBuffer[ReceiveHook]
|
||||||
receiveHooks += new ProtectedBranchReceiveHook()
|
receiveHooks += new ProtectedBranchReceiveHook()
|
||||||
|
|
||||||
def addPlugin(pluginInfo: PluginInfo): Unit = {
|
private val globalMenus = new ListBuffer[(Context) => Option[Link]]
|
||||||
plugins += pluginInfo
|
private val repositoryMenus = new ListBuffer[(RepositoryInfo, Context) => Option[Link]]
|
||||||
}
|
private val repositorySettingTabs = new ListBuffer[(RepositoryInfo, Context) => Option[Link]]
|
||||||
|
private val profileTabs = new ListBuffer[(Account, Context) => Option[Link]]
|
||||||
|
private val systemSettingMenus = new ListBuffer[(Context) => Option[Link]]
|
||||||
|
private val accountSettingMenus = new ListBuffer[(Context) => Option[Link]]
|
||||||
|
private val dashboardTabs = new ListBuffer[(Context) => Option[Link]]
|
||||||
|
private val assetsMappings = new ListBuffer[(String, String, ClassLoader)]
|
||||||
|
private val textDecorators = new ListBuffer[TextDecorator]
|
||||||
|
|
||||||
|
private val suggestionProviders = new ListBuffer[SuggestionProvider]
|
||||||
|
suggestionProviders += new UserNameSuggestionProvider()
|
||||||
|
|
||||||
|
def addPlugin(pluginInfo: PluginInfo): Unit = plugins += pluginInfo
|
||||||
|
|
||||||
def getPlugins(): List[PluginInfo] = plugins.toList
|
def getPlugins(): List[PluginInfo] = plugins.toList
|
||||||
|
|
||||||
@@ -56,42 +69,26 @@ class PluginRegistry {
|
|||||||
|
|
||||||
def getImage(id: String): String = images(id)
|
def getImage(id: String): String = images(id)
|
||||||
|
|
||||||
def addController(path: String, controller: ControllerBase): Unit = {
|
def addController(path: String, controller: ControllerBase): Unit = controllers += ((controller, path))
|
||||||
controllers += ((controller, path))
|
|
||||||
}
|
|
||||||
|
|
||||||
@deprecated("Use addController(path: String, controller: ControllerBase) instead", "3.4.0")
|
@deprecated("Use addController(path: String, controller: ControllerBase) instead", "3.4.0")
|
||||||
def addController(controller: ControllerBase, path: String): Unit = {
|
def addController(controller: ControllerBase, path: String): Unit = addController(path, controller)
|
||||||
addController(path, controller)
|
|
||||||
}
|
|
||||||
|
|
||||||
def getControllers(): Seq[(ControllerBase, String)] = controllers.toSeq
|
def getControllers(): Seq[(ControllerBase, String)] = controllers.toSeq
|
||||||
|
|
||||||
def addJavaScript(path: String, script: String): Unit = {
|
def addJavaScript(path: String, script: String): Unit = javaScripts += ((path, script))
|
||||||
javaScripts += ((path, script))
|
|
||||||
}
|
|
||||||
|
|
||||||
def getJavaScript(currentPath: String): List[String] = {
|
def getJavaScript(currentPath: String): List[String] = javaScripts.filter(x => currentPath.matches(x._1)).toList.map(_._2)
|
||||||
javaScripts.filter(x => currentPath.matches(x._1)).toList.map(_._2)
|
|
||||||
}
|
|
||||||
|
|
||||||
def addRenderer(extension: String, renderer: Renderer): Unit = {
|
def addRenderer(extension: String, renderer: Renderer): Unit = renderers += ((extension, renderer))
|
||||||
renderers += ((extension, renderer))
|
|
||||||
}
|
|
||||||
|
|
||||||
def getRenderer(extension: String): Renderer = {
|
def getRenderer(extension: String): Renderer = renderers.get(extension).getOrElse(DefaultRenderer)
|
||||||
renderers.get(extension).getOrElse(DefaultRenderer)
|
|
||||||
}
|
|
||||||
|
|
||||||
def renderableExtensions: Seq[String] = renderers.keys.toSeq
|
def renderableExtensions: Seq[String] = renderers.keys.toSeq
|
||||||
|
|
||||||
def addRepositoryRouting(routing: GitRepositoryRouting): Unit = {
|
def addRepositoryRouting(routing: GitRepositoryRouting): Unit = repositoryRoutings += routing
|
||||||
repositoryRoutings += routing
|
|
||||||
}
|
|
||||||
|
|
||||||
def getRepositoryRoutings(): Seq[GitRepositoryRouting] = {
|
def getRepositoryRoutings(): Seq[GitRepositoryRouting] = repositoryRoutings.toSeq
|
||||||
repositoryRoutings.toSeq
|
|
||||||
}
|
|
||||||
|
|
||||||
def getRepositoryRouting(repositoryPath: String): Option[GitRepositoryRouting] = {
|
def getRepositoryRouting(repositoryPath: String): Option[GitRepositoryRouting] = {
|
||||||
PluginRegistry().getRepositoryRoutings().find {
|
PluginRegistry().getRepositoryRoutings().find {
|
||||||
@@ -101,24 +98,49 @@ class PluginRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def addReceiveHook(commitHook: ReceiveHook): Unit = {
|
def addReceiveHook(commitHook: ReceiveHook): Unit = receiveHooks += commitHook
|
||||||
receiveHooks += commitHook
|
|
||||||
}
|
|
||||||
|
|
||||||
def getReceiveHooks: Seq[ReceiveHook] = receiveHooks.toSeq
|
def getReceiveHooks: Seq[ReceiveHook] = receiveHooks.toSeq
|
||||||
|
|
||||||
private case class GlobalAction(
|
def addGlobalMenu(globalMenu: (Context) => Option[Link]): Unit = globalMenus += globalMenu
|
||||||
method: String,
|
|
||||||
path: String,
|
|
||||||
function: (HttpServletRequest, HttpServletResponse, Context) => Any
|
|
||||||
)
|
|
||||||
|
|
||||||
private case class RepositoryAction(
|
def getGlobalMenus: Seq[(Context) => Option[Link]] = globalMenus.toSeq
|
||||||
method: String,
|
|
||||||
path: String,
|
|
||||||
function: (HttpServletRequest, HttpServletResponse, Context, RepositoryInfo) => Any
|
|
||||||
)
|
|
||||||
|
|
||||||
|
def addRepositoryMenu(repositoryMenu: (RepositoryInfo, Context) => Option[Link]): Unit = repositoryMenus += repositoryMenu
|
||||||
|
|
||||||
|
def getRepositoryMenus: Seq[(RepositoryInfo, Context) => Option[Link]] = repositoryMenus.toSeq
|
||||||
|
|
||||||
|
def addRepositorySettingTab(repositorySettingTab: (RepositoryInfo, Context) => Option[Link]): Unit = repositorySettingTabs += repositorySettingTab
|
||||||
|
|
||||||
|
def getRepositorySettingTabs: Seq[(RepositoryInfo, Context) => Option[Link]] = repositorySettingTabs.toSeq
|
||||||
|
|
||||||
|
def addProfileTab(profileTab: (Account, Context) => Option[Link]): Unit = profileTabs += profileTab
|
||||||
|
|
||||||
|
def getProfileTabs: Seq[(Account, Context) => Option[Link]] = profileTabs.toSeq
|
||||||
|
|
||||||
|
def addSystemSettingMenu(systemSettingMenu: (Context) => Option[Link]): Unit = systemSettingMenus += systemSettingMenu
|
||||||
|
|
||||||
|
def getSystemSettingMenus: Seq[(Context) => Option[Link]] = systemSettingMenus.toSeq
|
||||||
|
|
||||||
|
def addAccountSettingMenu(accountSettingMenu: (Context) => Option[Link]): Unit = accountSettingMenus += accountSettingMenu
|
||||||
|
|
||||||
|
def getAccountSettingMenus: Seq[(Context) => Option[Link]] = accountSettingMenus.toSeq
|
||||||
|
|
||||||
|
def addDashboardTab(dashboardTab: (Context) => Option[Link]): Unit = dashboardTabs += dashboardTab
|
||||||
|
|
||||||
|
def getDashboardTabs: Seq[(Context) => Option[Link]] = dashboardTabs.toSeq
|
||||||
|
|
||||||
|
def addAssetsMapping(assetsMapping: (String, String, ClassLoader)): Unit = assetsMappings += assetsMapping
|
||||||
|
|
||||||
|
def getAssetsMappings: Seq[(String, String, ClassLoader)] = assetsMappings.toSeq
|
||||||
|
|
||||||
|
def addTextDecorator(textDecorator: TextDecorator): Unit = textDecorators += textDecorator
|
||||||
|
|
||||||
|
def getTextDecorators: Seq[TextDecorator] = textDecorators.toSeq
|
||||||
|
|
||||||
|
def addSuggestionProvider(suggestionProvider: SuggestionProvider): Unit = suggestionProviders += suggestionProvider
|
||||||
|
|
||||||
|
def getSuggestionProviders: Seq[SuggestionProvider] = suggestionProviders.toSeq
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -140,6 +162,8 @@ object PluginRegistry {
|
|||||||
*/
|
*/
|
||||||
def initialize(context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = {
|
def initialize(context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = {
|
||||||
val pluginDir = new File(PluginHome)
|
val pluginDir = new File(PluginHome)
|
||||||
|
val manager = new JDBCVersionManager(conn)
|
||||||
|
|
||||||
if(pluginDir.exists && pluginDir.isDirectory){
|
if(pluginDir.exists && pluginDir.isDirectory){
|
||||||
pluginDir.listFiles(new FilenameFilter {
|
pluginDir.listFiles(new FilenameFilter {
|
||||||
override def accept(dir: File, name: String): Boolean = name.endsWith(".jar")
|
override def accept(dir: File, name: String): Boolean = name.endsWith(".jar")
|
||||||
@@ -149,22 +173,14 @@ object PluginRegistry {
|
|||||||
val plugin = classLoader.loadClass("Plugin").newInstance().asInstanceOf[Plugin]
|
val plugin = classLoader.loadClass("Plugin").newInstance().asInstanceOf[Plugin]
|
||||||
|
|
||||||
// Migration
|
// Migration
|
||||||
val headVersion = plugin.versions.head
|
val solidbase = new Solidbase()
|
||||||
val currentVersion = conn.find("SELECT * FROM PLUGIN WHERE PLUGIN_ID = ?", plugin.pluginId)(_.getString("VERSION")) match {
|
solidbase.migrate(conn, classLoader, DatabaseConfig.liquiDriver, new Module(plugin.pluginId, plugin.versions: _*))
|
||||||
case Some(x) => {
|
|
||||||
val dim = x.split("\\.")
|
|
||||||
Version(dim(0).toInt, dim(1).toInt)
|
|
||||||
}
|
|
||||||
case None => Version(0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
Versions.update(conn, headVersion, currentVersion, plugin.versions, new URLClassLoader(Array(pluginJar.toURI.toURL))){ conn =>
|
// Check version
|
||||||
currentVersion.versionString match {
|
val databaseVersion = manager.getCurrentVersion(plugin.pluginId)
|
||||||
case "0.0" =>
|
val pluginVersion = plugin.versions.last.getVersion
|
||||||
conn.update("INSERT INTO PLUGIN (PLUGIN_ID, VERSION) VALUES (?, ?)", plugin.pluginId, headVersion.versionString)
|
if(databaseVersion != pluginVersion){
|
||||||
case _ =>
|
throw new IllegalStateException(s"Plugin version is ${pluginVersion}, but database version is ${databaseVersion}")
|
||||||
conn.update("UPDATE PLUGIN SET VERSION = ? WHERE PLUGIN_ID = ?", headVersion.versionString, plugin.pluginId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
@@ -172,14 +188,14 @@ object PluginRegistry {
|
|||||||
instance.addPlugin(PluginInfo(
|
instance.addPlugin(PluginInfo(
|
||||||
pluginId = plugin.pluginId,
|
pluginId = plugin.pluginId,
|
||||||
pluginName = plugin.pluginName,
|
pluginName = plugin.pluginName,
|
||||||
version = plugin.versions.head.versionString,
|
pluginVersion = plugin.versions.last.getVersion,
|
||||||
description = plugin.description,
|
description = plugin.description,
|
||||||
pluginClass = plugin
|
pluginClass = plugin
|
||||||
))
|
))
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
case e: Throwable => {
|
case e: Throwable => {
|
||||||
logger.error(s"Error during plugin initialization", e)
|
logger.error(s"Error during plugin initialization: ${pluginJar.getAbsolutePath}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,10 +217,12 @@ object PluginRegistry {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case class Link(id: String, label: String, path: String, icon: Option[String] = None)
|
||||||
|
|
||||||
case class PluginInfo(
|
case class PluginInfo(
|
||||||
pluginId: String,
|
pluginId: String,
|
||||||
pluginName: String,
|
pluginName: String,
|
||||||
version: String,
|
pluginVersion: String,
|
||||||
description: String,
|
description: String,
|
||||||
pluginClass: Plugin
|
pluginClass: Plugin
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package gitbucket.core.plugin
|
||||||
|
|
||||||
|
import gitbucket.core.controller.Context
|
||||||
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
|
|
||||||
|
trait SuggestionProvider {
|
||||||
|
|
||||||
|
val id: String
|
||||||
|
val prefix: String
|
||||||
|
val suffix: String = " "
|
||||||
|
val context: Seq[String]
|
||||||
|
|
||||||
|
def values(repository: RepositoryInfo): Seq[String]
|
||||||
|
def template(implicit context: Context): String = "value"
|
||||||
|
def additionalScript(implicit context: Context): String = ""
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserNameSuggestionProvider extends SuggestionProvider {
|
||||||
|
override val id: String = "user"
|
||||||
|
override val prefix: String = "@"
|
||||||
|
override val context: Seq[String] = Seq("issues")
|
||||||
|
override def values(repository: RepositoryInfo): Seq[String] = Nil
|
||||||
|
override def template(implicit context: Context): String = "'@' + value"
|
||||||
|
override def additionalScript(implicit context: Context): String =
|
||||||
|
s"""$$.get('${context.path}/_user/proposals', { query: '' }, function (data) { user = data.options; });"""
|
||||||
|
}
|
||||||
10
src/main/scala/gitbucket/core/plugin/TextDecorator.scala
Normal file
10
src/main/scala/gitbucket/core/plugin/TextDecorator.scala
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package gitbucket.core.plugin
|
||||||
|
|
||||||
|
import gitbucket.core.controller.Context
|
||||||
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
|
|
||||||
|
trait TextDecorator {
|
||||||
|
|
||||||
|
def decorate(text: String, repository: RepositoryInfo)(implicit context: Context): String
|
||||||
|
|
||||||
|
}
|
||||||
@@ -97,6 +97,12 @@ trait AccountService {
|
|||||||
Accounts filter (_.removed === false.bind) sortBy(_.userName) list
|
Accounts filter (_.removed === false.bind) sortBy(_.userName) list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def isLastAdministrator(account: Account)(implicit s: Session): Boolean = {
|
||||||
|
if(account.isAdmin){
|
||||||
|
(Accounts filter (_.removed === false.bind) filter (_.isAdmin === true.bind) map (_.userName.length)).first == 1
|
||||||
|
} else false
|
||||||
|
}
|
||||||
|
|
||||||
def createAccount(userName: String, password: String, fullName: String, mailAddress: String, isAdmin: Boolean, url: Option[String])
|
def createAccount(userName: String, password: String, fullName: String, mailAddress: String, isAdmin: Boolean, url: Option[String])
|
||||||
(implicit s: Session): Unit =
|
(implicit s: Session): Unit =
|
||||||
Accounts insert Account(
|
Accounts insert Account(
|
||||||
@@ -180,7 +186,7 @@ trait AccountService {
|
|||||||
|
|
||||||
def getGroupNames(userName: String)(implicit s: Session): List[String] = {
|
def getGroupNames(userName: String)(implicit s: Session): List[String] = {
|
||||||
List(userName) ++
|
List(userName) ++
|
||||||
Collaborators.filter(_.collaboratorName === userName.bind).sortBy(_.userName).map(_.userName).list
|
Collaborators.filter(_.collaboratorName === userName.bind).sortBy(_.userName).map(_.userName).list.distinct
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ trait IssuesService {
|
|||||||
import IssuesService._
|
import IssuesService._
|
||||||
|
|
||||||
def getIssue(owner: String, repository: String, issueId: String)(implicit s: Session) =
|
def getIssue(owner: String, repository: String, issueId: String)(implicit s: Session) =
|
||||||
if (issueId forall (_.isDigit))
|
if (isInteger(issueId))
|
||||||
Issues filter (_.byPrimaryKey(owner, repository, issueId.toInt)) firstOption
|
Issues filter (_.byPrimaryKey(owner, repository, issueId.toInt)) firstOption
|
||||||
else None
|
else None
|
||||||
|
|
||||||
@@ -108,25 +108,41 @@ trait IssuesService {
|
|||||||
}
|
}
|
||||||
import gitbucket.core.model.Profile.commitStateColumnType
|
import gitbucket.core.model.Profile.commitStateColumnType
|
||||||
val query = Q.query[Seq[(String, String, Int)], (String, String, Int, Int, Int, Option[String], Option[CommitState], Option[String], Option[String])](s"""
|
val query = Q.query[Seq[(String, String, Int)], (String, String, Int, Int, Int, Option[String], Option[CommitState], Option[String], Option[String])](s"""
|
||||||
SELECT SUMM.USER_NAME, SUMM.REPOSITORY_NAME, SUMM.ISSUE_ID, CS_ALL, CS_SUCCESS
|
SELECT
|
||||||
, CSD.CONTEXT, CSD.STATE, CSD.TARGET_URL, CSD.DESCRIPTION
|
SUMM.USER_NAME,
|
||||||
FROM (SELECT
|
SUMM.REPOSITORY_NAME,
|
||||||
PR.USER_NAME
|
SUMM.ISSUE_ID,
|
||||||
, PR.REPOSITORY_NAME
|
CS_ALL,
|
||||||
, PR.ISSUE_ID
|
CS_SUCCESS,
|
||||||
, COUNT(CS.STATE) AS CS_ALL
|
CSD.CONTEXT,
|
||||||
, SUM(CS.STATE='success') AS CS_SUCCESS
|
CSD.STATE,
|
||||||
, PR.COMMIT_ID_TO AS COMMIT_ID
|
CSD.TARGET_URL,
|
||||||
|
CSD.DESCRIPTION
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
PR.USER_NAME,
|
||||||
|
PR.REPOSITORY_NAME,
|
||||||
|
PR.ISSUE_ID,
|
||||||
|
COUNT(CS.STATE) AS CS_ALL,
|
||||||
|
CSS.CS_SUCCESS AS CS_SUCCESS,
|
||||||
|
PR.COMMIT_ID_TO AS COMMIT_ID
|
||||||
FROM PULL_REQUEST PR
|
FROM PULL_REQUEST PR
|
||||||
JOIN COMMIT_STATUS CS
|
JOIN COMMIT_STATUS CS
|
||||||
ON PR.USER_NAME=CS.USER_NAME
|
ON PR.USER_NAME = CS.USER_NAME AND PR.REPOSITORY_NAME = CS.REPOSITORY_NAME AND PR.COMMIT_ID_TO = CS.COMMIT_ID
|
||||||
AND PR.REPOSITORY_NAME=CS.REPOSITORY_NAME
|
JOIN (
|
||||||
AND PR.COMMIT_ID_TO=CS.COMMIT_ID
|
SELECT
|
||||||
|
COUNT(*) AS CS_SUCCESS,
|
||||||
|
USER_NAME,
|
||||||
|
REPOSITORY_NAME,
|
||||||
|
COMMIT_ID
|
||||||
|
FROM COMMIT_STATUS WHERE STATE = 'success' GROUP BY USER_NAME, REPOSITORY_NAME, COMMIT_ID
|
||||||
|
) CSS ON PR.USER_NAME = CSS.USER_NAME AND PR.REPOSITORY_NAME = CSS.REPOSITORY_NAME AND PR.COMMIT_ID_TO = CSS.COMMIT_ID
|
||||||
WHERE $issueIdQuery
|
WHERE $issueIdQuery
|
||||||
GROUP BY PR.USER_NAME, PR.REPOSITORY_NAME, PR.ISSUE_ID) as SUMM
|
GROUP BY PR.USER_NAME, PR.REPOSITORY_NAME, PR.ISSUE_ID, CSS.CS_SUCCESS
|
||||||
|
) as SUMM
|
||||||
LEFT OUTER JOIN COMMIT_STATUS CSD
|
LEFT OUTER JOIN COMMIT_STATUS CSD
|
||||||
ON SUMM.CS_ALL = 1 AND SUMM.COMMIT_ID = CSD.COMMIT_ID""");
|
ON SUMM.CS_ALL = 1 AND SUMM.COMMIT_ID = CSD.COMMIT_ID""");
|
||||||
query(issueList).list.map{
|
query(issueList).list.map {
|
||||||
case(userName, repositoryName, issueId, count, successCount, context, state, targetUrl, description) =>
|
case(userName, repositoryName, issueId, count, successCount, context, state, targetUrl, description) =>
|
||||||
(userName, repositoryName, issueId) -> CommitStatusInfo(count, successCount, context, state, targetUrl, description)
|
(userName, repositoryName, issueId) -> CommitStatusInfo(count, successCount, context, state, targetUrl, description)
|
||||||
}.toMap
|
}.toMap
|
||||||
@@ -218,9 +234,8 @@ trait IssuesService {
|
|||||||
.map { case (owner, repository) => t1.byRepository(owner, repository) }
|
.map { case (owner, repository) => t1.byRepository(owner, repository) }
|
||||||
.foldLeft[Column[Boolean]](false) ( _ || _ ) &&
|
.foldLeft[Column[Boolean]](false) ( _ || _ ) &&
|
||||||
(t1.closed === (condition.state == "closed").bind) &&
|
(t1.closed === (condition.state == "closed").bind) &&
|
||||||
//(t1.milestoneId === condition.milestoneId.get.get.bind, condition.milestoneId.flatten.isDefined) &&
|
|
||||||
(t1.milestoneId.? isEmpty, condition.milestone == Some(None)) &&
|
(t1.milestoneId.? isEmpty, condition.milestone == Some(None)) &&
|
||||||
(t1.assignedUserName === condition.assigned.get.bind, condition.assigned.isDefined) &&
|
(t1.assignedUserName.? isEmpty, condition.assigned == Some(None)) &&
|
||||||
(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) &&
|
(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) &&
|
||||||
(t1.pullRequest === pullRequest.bind) &&
|
(t1.pullRequest === pullRequest.bind) &&
|
||||||
// Milestone filter
|
// Milestone filter
|
||||||
@@ -228,6 +243,8 @@ trait IssuesService {
|
|||||||
(t2.byPrimaryKey(t1.userName, t1.repositoryName, t1.milestoneId)) &&
|
(t2.byPrimaryKey(t1.userName, t1.repositoryName, t1.milestoneId)) &&
|
||||||
(t2.title === condition.milestone.get.get.bind)
|
(t2.title === condition.milestone.get.get.bind)
|
||||||
} exists, condition.milestone.flatten.isDefined) &&
|
} exists, condition.milestone.flatten.isDefined) &&
|
||||||
|
// Assignee filter
|
||||||
|
(t1.assignedUserName === condition.assigned.get.get.bind, condition.assigned.flatten.isDefined) &&
|
||||||
// Label filter
|
// Label filter
|
||||||
(IssueLabels filter { t2 =>
|
(IssueLabels filter { t2 =>
|
||||||
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) &&
|
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) &&
|
||||||
@@ -431,7 +448,7 @@ object IssuesService {
|
|||||||
labels: Set[String] = Set.empty,
|
labels: Set[String] = Set.empty,
|
||||||
milestone: Option[Option[String]] = None,
|
milestone: Option[Option[String]] = None,
|
||||||
author: Option[String] = None,
|
author: Option[String] = None,
|
||||||
assigned: Option[String] = None,
|
assigned: Option[Option[String]] = None,
|
||||||
mentioned: Option[String] = None,
|
mentioned: Option[String] = None,
|
||||||
state: String = "open",
|
state: String = "open",
|
||||||
sort: String = "created",
|
sort: String = "created",
|
||||||
@@ -475,12 +492,15 @@ object IssuesService {
|
|||||||
def toURL: String =
|
def toURL: String =
|
||||||
"?" + List(
|
"?" + List(
|
||||||
if(labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(","))),
|
if(labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(","))),
|
||||||
milestone.map { _ match {
|
milestone.map {
|
||||||
case Some(x) => "milestone=" + urlEncode(x)
|
case Some(x) => "milestone=" + urlEncode(x)
|
||||||
case None => "milestone=none"
|
case None => "milestone=none"
|
||||||
}},
|
},
|
||||||
author .map(x => "author=" + urlEncode(x)),
|
author .map(x => "author=" + urlEncode(x)),
|
||||||
assigned .map(x => "assigned=" + urlEncode(x)),
|
assigned.map {
|
||||||
|
case Some(x) => "assigned=" + urlEncode(x)
|
||||||
|
case None => "assigned=none"
|
||||||
|
},
|
||||||
mentioned.map(x => "mentioned=" + urlEncode(x)),
|
mentioned.map(x => "mentioned=" + urlEncode(x)),
|
||||||
Some("state=" + urlEncode(state)),
|
Some("state=" + urlEncode(state)),
|
||||||
Some("sort=" + urlEncode(sort)),
|
Some("sort=" + urlEncode(sort)),
|
||||||
@@ -498,46 +518,6 @@ object IssuesService {
|
|||||||
if(value == null || value.isEmpty || (allow.nonEmpty && !allow.contains(value))) None else Some(value)
|
if(value == null || value.isEmpty || (allow.nonEmpty && !allow.contains(value))) None else Some(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Restores IssueSearchCondition instance from filter query.
|
|
||||||
*/
|
|
||||||
def apply(filter: String, milestones: Map[String, Int]): IssueSearchCondition = {
|
|
||||||
val conditions = filter.split("[ \t]+").flatMap { x =>
|
|
||||||
x.split(":") match {
|
|
||||||
case Array(key, value) => Some((key, value))
|
|
||||||
case _ => None
|
|
||||||
}
|
|
||||||
}.groupBy(_._1).map { case (key, values) =>
|
|
||||||
key -> values.map(_._2).toSeq
|
|
||||||
}
|
|
||||||
|
|
||||||
val (sort, direction) = conditions.get("sort").flatMap(_.headOption).getOrElse("created-desc") match {
|
|
||||||
case "created-asc" => ("created" , "asc" )
|
|
||||||
case "comments-desc" => ("comments", "desc")
|
|
||||||
case "comments-asc" => ("comments", "asc" )
|
|
||||||
case "updated-desc" => ("comments", "desc")
|
|
||||||
case "updated-asc" => ("comments", "asc" )
|
|
||||||
case _ => ("created" , "desc")
|
|
||||||
}
|
|
||||||
|
|
||||||
IssueSearchCondition(
|
|
||||||
conditions.get("label").map(_.toSet).getOrElse(Set.empty),
|
|
||||||
conditions.get("milestone").flatMap(_.headOption) match {
|
|
||||||
case None => None
|
|
||||||
case Some("none") => Some(None)
|
|
||||||
case Some(x) => Some(Some(x)) //milestones.get(x).map(x => Some(x))
|
|
||||||
},
|
|
||||||
conditions.get("author").flatMap(_.headOption),
|
|
||||||
conditions.get("assignee").flatMap(_.headOption),
|
|
||||||
conditions.get("mentions").flatMap(_.headOption),
|
|
||||||
conditions.get("is").getOrElse(Seq.empty).find(x => x == "open" || x == "closed").getOrElse("open"),
|
|
||||||
sort,
|
|
||||||
direction,
|
|
||||||
conditions.get("visibility").flatMap(_.headOption),
|
|
||||||
conditions.get("group").map(_.toSet).getOrElse(Set.empty)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restores IssueSearchCondition instance from request parameters.
|
* Restores IssueSearchCondition instance from request parameters.
|
||||||
*/
|
*/
|
||||||
@@ -549,7 +529,10 @@ object IssuesService {
|
|||||||
case x => Some(x)
|
case x => Some(x)
|
||||||
},
|
},
|
||||||
param(request, "author"),
|
param(request, "author"),
|
||||||
param(request, "assigned"),
|
param(request, "assigned").map {
|
||||||
|
case "none" => None
|
||||||
|
case x => Some(x)
|
||||||
|
},
|
||||||
param(request, "mentioned"),
|
param(request, "mentioned"),
|
||||||
param(request, "state", Seq("open", "closed")).getOrElse("open"),
|
param(request, "state", Seq("open", "closed")).getOrElse("open"),
|
||||||
param(request, "sort", Seq("created", "comments", "updated")).getOrElse("created"),
|
param(request, "sort", Seq("created", "comments", "updated")).getOrElse("created"),
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ trait MilestonesService {
|
|||||||
|
|
||||||
def getMilestonesWithIssueCount(owner: String, repository: String)(implicit s: Session): List[(Milestone, Int, Int)] = {
|
def getMilestonesWithIssueCount(owner: String, repository: String)(implicit s: Session): List[(Milestone, Int, Int)] = {
|
||||||
val counts = Issues
|
val counts = Issues
|
||||||
.filter { t => (t.byRepository(owner, repository)) && (t.milestoneId.? isDefined) }
|
.filter { t => t.byRepository(owner, repository) && (t.milestoneId.? isDefined) }
|
||||||
.groupBy { t => t.milestoneId -> t.closed }
|
.groupBy { t => t.milestoneId -> t.closed }
|
||||||
.map { case (t1, t2) => t1._1 -> t1._2 -> t2.length }
|
.map { case (t1, t2) => t1._1 -> t1._2 -> t2.length }
|
||||||
.toMap
|
.toMap
|
||||||
@@ -52,6 +52,6 @@ trait MilestonesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def getMilestones(owner: String, repository: String)(implicit s: Session): List[Milestone] =
|
def getMilestones(owner: String, repository: String)(implicit s: Session): List[Milestone] =
|
||||||
Milestones.filter(_.byRepository(owner, repository)).sortBy(_.milestoneId asc).list
|
Milestones.filter(_.byRepository(owner, repository)).sortBy(t => (t.dueDate.asc, t.closedDate.desc, t.milestoneId.desc)).list
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
package gitbucket.core.service
|
|
||||||
|
|
||||||
import gitbucket.core.model.Plugin
|
|
||||||
import gitbucket.core.model.Profile._
|
|
||||||
import profile.simple._
|
|
||||||
|
|
||||||
trait PluginService {
|
|
||||||
|
|
||||||
def getPlugins()(implicit s: Session): List[Plugin] =
|
|
||||||
Plugins.sortBy(_.pluginId).list
|
|
||||||
|
|
||||||
def registerPlugin(plugin: Plugin)(implicit s: Session): Unit =
|
|
||||||
Plugins.insert(plugin)
|
|
||||||
|
|
||||||
def updatePlugin(plugin: Plugin)(implicit s: Session): Unit =
|
|
||||||
Plugins.filter(_.pluginId === plugin.pluginId.bind).map(_.version).update(plugin.version)
|
|
||||||
|
|
||||||
def deletePlugin(pluginId: String)(implicit s: Session): Unit =
|
|
||||||
Plugins.filter(_.pluginId === pluginId.bind).delete
|
|
||||||
|
|
||||||
def getPlugin(pluginId: String)(implicit s: Session): Option[Plugin] =
|
|
||||||
Plugins.filter(_.pluginId === pluginId.bind).firstOption
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -53,7 +53,30 @@ trait RepositorySearchService { self: IssuesService =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def searchRepositoryFiles(git: Git, query: String): List[(String, String)] = {
|
def countWikiPages(owner: String, repository: String, query: String): Int =
|
||||||
|
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
|
||||||
|
if(JGitUtil.isEmpty(git)) 0 else searchRepositoryFiles(git, query).length
|
||||||
|
}
|
||||||
|
|
||||||
|
def searchWikiPages(owner: String, repository: String, query: String): List[FileSearchResult] =
|
||||||
|
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
|
||||||
|
if(JGitUtil.isEmpty(git)){
|
||||||
|
Nil
|
||||||
|
} else {
|
||||||
|
val files = searchRepositoryFiles(git, query)
|
||||||
|
val commits = JGitUtil.getLatestCommitFromPaths(git, files.map(_._1), "HEAD")
|
||||||
|
files.map { case (path, text) =>
|
||||||
|
val (highlightText, lineNumber) = getHighlightText(text, query)
|
||||||
|
FileSearchResult(
|
||||||
|
path.replaceFirst("\\.md$", ""),
|
||||||
|
commits(path).getCommitterIdent.getWhen,
|
||||||
|
highlightText,
|
||||||
|
lineNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def searchRepositoryFiles(git: Git, query: String): List[(String, String)] = {
|
||||||
val revWalk = new RevWalk(git.getRepository)
|
val revWalk = new RevWalk(git.getRepository)
|
||||||
val objectId = git.getRepository.resolve("HEAD")
|
val objectId = git.getRepository.resolve("HEAD")
|
||||||
val revCommit = revWalk.parseCommit(objectId)
|
val revCommit = revWalk.parseCommit(objectId)
|
||||||
|
|||||||
@@ -36,7 +36,13 @@ trait RepositoryService { self: AccountService =>
|
|||||||
originUserName = originUserName,
|
originUserName = originUserName,
|
||||||
originRepositoryName = originRepositoryName,
|
originRepositoryName = originRepositoryName,
|
||||||
parentUserName = parentUserName,
|
parentUserName = parentUserName,
|
||||||
parentRepositoryName = parentRepositoryName)
|
parentRepositoryName = parentRepositoryName,
|
||||||
|
enableIssues = true,
|
||||||
|
externalIssuesUrl = None,
|
||||||
|
enableWiki = true,
|
||||||
|
allowWikiEditing = true,
|
||||||
|
externalWikiUrl = None
|
||||||
|
)
|
||||||
|
|
||||||
IssueId insert (userName, repositoryName, 0)
|
IssueId insert (userName, repositoryName, 0)
|
||||||
}
|
}
|
||||||
@@ -222,7 +228,7 @@ trait RepositoryService { self: AccountService =>
|
|||||||
* Include public repository, private own repository and private but collaborator repository.
|
* Include public repository, private own repository and private but collaborator repository.
|
||||||
*
|
*
|
||||||
* @param userName the user name of collaborator
|
* @param userName the user name of collaborator
|
||||||
* @return the repository infomation list
|
* @return the repository information list
|
||||||
*/
|
*/
|
||||||
def getAllRepositories(userName: String)(implicit s: Session): List[(String, String)] = {
|
def getAllRepositories(userName: String)(implicit s: Session): List[(String, String)] = {
|
||||||
Repositories.filter { t1 =>
|
Repositories.filter { t1 =>
|
||||||
@@ -313,10 +319,12 @@ trait RepositoryService { self: AccountService =>
|
|||||||
* Save repository options.
|
* Save repository options.
|
||||||
*/
|
*/
|
||||||
def saveRepositoryOptions(userName: String, repositoryName: String,
|
def saveRepositoryOptions(userName: String, repositoryName: String,
|
||||||
description: Option[String], isPrivate: Boolean)(implicit s: Session): Unit =
|
description: Option[String], isPrivate: Boolean,
|
||||||
|
enableIssues: Boolean, externalIssuesUrl: Option[String],
|
||||||
|
enableWiki: Boolean, allowWikiEditing: Boolean, externalWikiUrl: Option[String])(implicit s: Session): Unit =
|
||||||
Repositories.filter(_.byRepository(userName, repositoryName))
|
Repositories.filter(_.byRepository(userName, repositoryName))
|
||||||
.map { r => (r.description.?, r.isPrivate, r.updatedDate) }
|
.map { r => (r.description.?, r.isPrivate, r.enableIssues, r.externalIssuesUrl.?, r.enableWiki, r.allowWikiEditing, r.externalWikiUrl.?, r.updatedDate) }
|
||||||
.update (description, isPrivate, currentDate)
|
.update (description, isPrivate, enableIssues, externalIssuesUrl, enableWiki, allowWikiEditing, externalWikiUrl, currentDate)
|
||||||
|
|
||||||
def saveRepositoryDefaultBranch(userName: String, repositoryName: String,
|
def saveRepositoryDefaultBranch(userName: String, repositoryName: String,
|
||||||
defaultBranch: String)(implicit s: Session): Unit =
|
defaultBranch: String)(implicit s: Session): Unit =
|
||||||
@@ -412,14 +420,23 @@ object RepositoryService {
|
|||||||
|
|
||||||
def httpUrl(implicit context: Context): String = RepositoryService.httpUrl(owner, name)
|
def httpUrl(implicit context: Context): String = RepositoryService.httpUrl(owner, name)
|
||||||
def sshUrl(implicit context: Context): Option[String] = RepositoryService.sshUrl(owner, name)
|
def sshUrl(implicit context: Context): Option[String] = RepositoryService.sshUrl(owner, name)
|
||||||
|
|
||||||
|
def splitPath(path: String): (String, String) = {
|
||||||
|
val id = branchList.collectFirst {
|
||||||
|
case branch if(path == branch || path.startsWith(branch + "/")) => branch
|
||||||
|
} orElse tags.collectFirst {
|
||||||
|
case tag if(path == tag.name || path.startsWith(tag.name + "/")) => tag.name
|
||||||
|
} getOrElse path.split("/")(0)
|
||||||
|
|
||||||
|
(id, path.substring(id.length).stripPrefix("/"))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def httpUrl(owner: String, name: String)(implicit context: Context): String = s"${context.baseUrl}/git/${owner}/${name}.git"
|
def httpUrl(owner: String, name: String)(implicit context: Context): String = s"${context.baseUrl}/git/${owner}/${name}.git"
|
||||||
def sshUrl(owner: String, name: String)(implicit context: Context): Option[String] =
|
def sshUrl(owner: String, name: String)(implicit context: Context): Option[String] =
|
||||||
if(context.settings.ssh){
|
if(context.settings.ssh){
|
||||||
context.loginAccount.flatMap { loginAccount =>
|
context.settings.sshAddress.map { x => s"ssh://${x.genericUser}@${x.host}:${x.port}/${owner}/${name}.git" }
|
||||||
context.settings.sshAddress.map { x => s"ssh://${loginAccount.userName}@${x.host}:${x.port}/${owner}/${name}.git" }
|
|
||||||
}
|
|
||||||
} else None
|
} else None
|
||||||
def openRepoUrl(openUrl: String)(implicit context: Context): String = s"github-${context.platform}://openRepo/${openUrl}"
|
def openRepoUrl(openUrl: String)(implicit context: Context): String = s"github-${context.platform}://openRepo/${openUrl}"
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ trait SshKeyService {
|
|||||||
def getPublicKeys(userName: String)(implicit s: Session): List[SshKey] =
|
def getPublicKeys(userName: String)(implicit s: Session): List[SshKey] =
|
||||||
SshKeys.filter(_.userName === userName.bind).sortBy(_.sshKeyId).list
|
SshKeys.filter(_.userName === userName.bind).sortBy(_.sshKeyId).list
|
||||||
|
|
||||||
|
def getAllKeys()(implicit s: Session): List[SshKey] =
|
||||||
|
SshKeys.filter(_.publicKey.trim =!= "").list
|
||||||
|
|
||||||
def deletePublicKey(userName: String, sshKeyId: Int)(implicit s: Session): Unit =
|
def deletePublicKey(userName: String, sshKeyId: Int)(implicit s: Session): Unit =
|
||||||
SshKeys filter (_.byPrimaryKey(userName, sshKeyId)) delete
|
SshKeys filter (_.byPrimaryKey(userName, sshKeyId)) delete
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ trait SystemSettingsService {
|
|||||||
getValue(props, AllowAccountRegistration, false),
|
getValue(props, AllowAccountRegistration, false),
|
||||||
getValue(props, AllowAnonymousAccess, true),
|
getValue(props, AllowAnonymousAccess, true),
|
||||||
getValue(props, IsCreateRepoOptionPublic, true),
|
getValue(props, IsCreateRepoOptionPublic, true),
|
||||||
getValue(props, Gravatar, true),
|
getValue(props, Gravatar, false),
|
||||||
getValue(props, Notification, false),
|
getValue(props, Notification, false),
|
||||||
getOptionValue[Int](props, ActivityLogLimit, None),
|
getOptionValue[Int](props, ActivityLogLimit, None),
|
||||||
getValue(props, Ssh, false),
|
getValue(props, Ssh, false),
|
||||||
@@ -141,7 +141,11 @@ object SystemSettingsService {
|
|||||||
for {
|
for {
|
||||||
host <- sshHost if ssh
|
host <- sshHost if ssh
|
||||||
}
|
}
|
||||||
yield SshAddress(host, sshPort.getOrElse(DefaultSshPort))
|
yield SshAddress(
|
||||||
|
host,
|
||||||
|
sshPort.getOrElse(DefaultSshPort),
|
||||||
|
"git"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
case class Ldap(
|
case class Ldap(
|
||||||
@@ -169,7 +173,8 @@ object SystemSettingsService {
|
|||||||
|
|
||||||
case class SshAddress(
|
case class SshAddress(
|
||||||
host:String,
|
host:String,
|
||||||
port:Int)
|
port:Int,
|
||||||
|
genericUser:String)
|
||||||
|
|
||||||
val DefaultSshPort = 29418
|
val DefaultSshPort = 29418
|
||||||
val DefaultSmtpPort = 25
|
val DefaultSmtpPort = 25
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package gitbucket.core.service
|
package gitbucket.core.service
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream
|
|
||||||
|
|
||||||
import fr.brouillard.oss.security.xhub.XHub
|
import fr.brouillard.oss.security.xhub.XHub
|
||||||
import fr.brouillard.oss.security.xhub.XHub.{XHubDigest, XHubConverter}
|
import fr.brouillard.oss.security.xhub.XHub.{XHubDigest, XHubConverter}
|
||||||
import gitbucket.core.api._
|
import gitbucket.core.api._
|
||||||
@@ -12,7 +10,6 @@ import profile.simple._
|
|||||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||||
import gitbucket.core.util.RepositoryName
|
import gitbucket.core.util.RepositoryName
|
||||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
|
|
||||||
import org.apache.http.NameValuePair
|
import org.apache.http.NameValuePair
|
||||||
import org.apache.http.client.entity.UrlEncodedFormEntity
|
import org.apache.http.client.entity.UrlEncodedFormEntity
|
||||||
import org.apache.http.message.BasicNameValuePair
|
import org.apache.http.message.BasicNameValuePair
|
||||||
@@ -22,6 +19,9 @@ import org.slf4j.LoggerFactory
|
|||||||
import scala.concurrent._
|
import scala.concurrent._
|
||||||
import org.apache.http.HttpRequest
|
import org.apache.http.HttpRequest
|
||||||
import org.apache.http.HttpResponse
|
import org.apache.http.HttpResponse
|
||||||
|
import gitbucket.core.model.WebHookContentType
|
||||||
|
import org.apache.http.client.entity.EntityBuilder
|
||||||
|
import org.apache.http.entity.ContentType
|
||||||
|
|
||||||
|
|
||||||
trait WebHookService {
|
trait WebHookService {
|
||||||
@@ -52,15 +52,15 @@ trait WebHookService {
|
|||||||
.map{ case (w,t) => w -> t.event }
|
.map{ case (w,t) => w -> t.event }
|
||||||
.list.groupBy(_._1).mapValues(_.map(_._2).toSet).headOption
|
.list.groupBy(_._1).mapValues(_.map(_._2).toSet).headOption
|
||||||
|
|
||||||
def addWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], token: Option[String])(implicit s: Session): Unit = {
|
def addWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])(implicit s: Session): Unit = {
|
||||||
WebHooks insert WebHook(owner, repository, url, token)
|
WebHooks insert WebHook(owner, repository, url, ctype, token)
|
||||||
events.toSet.map{ event: WebHook.Event =>
|
events.toSet.map{ event: WebHook.Event =>
|
||||||
WebHookEvents insert WebHookEvent(owner, repository, url, event)
|
WebHookEvents insert WebHookEvent(owner, repository, url, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def updateWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], token: Option[String])(implicit s: Session): Unit = {
|
def updateWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])(implicit s: Session): Unit = {
|
||||||
WebHooks.filter(_.byPrimaryKey(owner, repository, url)).map(w => w.token).update(token)
|
WebHooks.filter(_.byPrimaryKey(owner, repository, url)).map(w => (w.ctype, w.token)).update((ctype, token))
|
||||||
WebHookEvents.filter(_.byWebHook(owner, repository, url)).delete
|
WebHookEvents.filter(_.byWebHook(owner, repository, url)).delete
|
||||||
events.toSet.map{ event: WebHook.Event =>
|
events.toSet.map{ event: WebHook.Event =>
|
||||||
WebHookEvents insert WebHookEvent(owner, repository, url, event)
|
WebHookEvents insert WebHookEvent(owner, repository, url, event)
|
||||||
@@ -97,22 +97,32 @@ trait WebHookService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
try{
|
try{
|
||||||
val httpClient = HttpClientBuilder.create.addInterceptorLast(itcp).build
|
val httpClient = HttpClientBuilder.create.useSystemProperties.addInterceptorLast(itcp).build
|
||||||
logger.debug(s"start web hook invocation for ${webHook.url}")
|
logger.debug(s"start web hook invocation for ${webHook.url}")
|
||||||
val httpPost = new HttpPost(webHook.url)
|
val httpPost = new HttpPost(webHook.url)
|
||||||
httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded")
|
logger.info(s"Content-Type: ${webHook.ctype.ctype}")
|
||||||
|
httpPost.addHeader("Content-Type", webHook.ctype.ctype)
|
||||||
httpPost.addHeader("X-Github-Event", event.name)
|
httpPost.addHeader("X-Github-Event", event.name)
|
||||||
httpPost.addHeader("X-Github-Delivery", java.util.UUID.randomUUID().toString)
|
httpPost.addHeader("X-Github-Delivery", java.util.UUID.randomUUID().toString)
|
||||||
|
|
||||||
|
webHook.ctype match {
|
||||||
|
case WebHookContentType.FORM => {
|
||||||
val params: java.util.List[NameValuePair] = new java.util.ArrayList()
|
val params: java.util.List[NameValuePair] = new java.util.ArrayList()
|
||||||
params.add(new BasicNameValuePair("payload", json))
|
params.add(new BasicNameValuePair("payload", json))
|
||||||
def postContent = new UrlEncodedFormEntity(params, "UTF-8")
|
def postContent = new UrlEncodedFormEntity(params, "UTF-8")
|
||||||
httpPost.setEntity(postContent)
|
httpPost.setEntity(postContent)
|
||||||
|
if (webHook.token.exists(_.trim.nonEmpty)) {
|
||||||
if (!webHook.token.isEmpty) {
|
|
||||||
// TODO find a better way and see how to extract content from postContent
|
// TODO find a better way and see how to extract content from postContent
|
||||||
val contentAsBytes = URLEncodedUtils.format(params, "UTF-8").getBytes("UTF-8")
|
val contentAsBytes = URLEncodedUtils.format(params, "UTF-8").getBytes("UTF-8")
|
||||||
httpPost.addHeader("X-Hub-Signature", XHub.generateHeaderXHubToken(XHubConverter.HEXA_LOWERCASE, XHubDigest.SHA1, webHook.token.orNull, contentAsBytes))
|
httpPost.addHeader("X-Hub-Signature", XHub.generateHeaderXHubToken(XHubConverter.HEXA_LOWERCASE, XHubDigest.SHA1, webHook.token.get, contentAsBytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case WebHookContentType.JSON => {
|
||||||
|
httpPost.setEntity(EntityBuilder.create().setContentType(ContentType.APPLICATION_JSON).setText(json).build())
|
||||||
|
if (webHook.token.exists(_.trim.nonEmpty)) {
|
||||||
|
httpPost.addHeader("X-Hub-Signature", XHub.generateHeaderXHubToken(XHubConverter.HEXA_LOWERCASE, XHubDigest.SHA1, webHook.token.orNull, json.getBytes("UTF-8")))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val res = httpClient.execute(httpPost)
|
val res = httpClient.execute(httpPost)
|
||||||
|
|||||||
@@ -4,15 +4,14 @@ import javax.servlet._
|
|||||||
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
||||||
|
|
||||||
import gitbucket.core.model.Account
|
import gitbucket.core.model.Account
|
||||||
import gitbucket.core.service.AccessTokenService
|
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||||
import gitbucket.core.util.Keys
|
import gitbucket.core.service.{AccessTokenService, AccountService, SystemSettingsService}
|
||||||
|
import gitbucket.core.util.{AuthUtil, Keys}
|
||||||
import org.scalatra.servlet.ServletApiImplicits._
|
import org.scalatra.servlet.ServletApiImplicits._
|
||||||
import org.scalatra._
|
import org.scalatra._
|
||||||
|
|
||||||
|
|
||||||
class AccessTokenAuthenticationFilter extends Filter with AccessTokenService {
|
class ApiAuthenticationFilter extends Filter with AccessTokenService with AccountService with SystemSettingsService {
|
||||||
private val tokenHeaderPrefix = "token "
|
|
||||||
|
|
||||||
override def init(filterConfig: FilterConfig): Unit = {}
|
override def init(filterConfig: FilterConfig): Unit = {}
|
||||||
|
|
||||||
@@ -23,9 +22,9 @@ class AccessTokenAuthenticationFilter extends Filter with AccessTokenService {
|
|||||||
implicit val session = req.getAttribute(Keys.Request.DBSession).asInstanceOf[slick.jdbc.JdbcBackend#Session]
|
implicit val session = req.getAttribute(Keys.Request.DBSession).asInstanceOf[slick.jdbc.JdbcBackend#Session]
|
||||||
val response = res.asInstanceOf[HttpServletResponse]
|
val response = res.asInstanceOf[HttpServletResponse]
|
||||||
Option(request.getHeader("Authorization")).map{
|
Option(request.getHeader("Authorization")).map{
|
||||||
case auth if auth.startsWith("token ") => AccessTokenService.getAccountByAccessToken(auth.substring(6).trim).toRight(Unit)
|
case auth if auth.startsWith("token ") => AccessTokenService.getAccountByAccessToken(auth.substring(6).trim).toRight(())
|
||||||
// TODO Basic Authentication Support
|
case auth if auth.startsWith("Basic ") => doBasicAuth(auth, loadSystemSettings(), request).toRight(())
|
||||||
case _ => Left(Unit)
|
case _ => Left(())
|
||||||
}.orElse{
|
}.orElse{
|
||||||
Option(request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]).map(Right(_))
|
Option(request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]).map(Right(_))
|
||||||
} match {
|
} match {
|
||||||
@@ -40,4 +39,10 @@ class AccessTokenAuthenticationFilter extends Filter with AccessTokenService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def doBasicAuth(auth: String, settings: SystemSettings, request: HttpServletRequest): Option[Account] = {
|
||||||
|
implicit val session = request.getAttribute(Keys.Request.DBSession).asInstanceOf[slick.jdbc.JdbcBackend#Session]
|
||||||
|
val Array(username, password) = AuthUtil.decodeAuthHeader(auth).split(":", 2)
|
||||||
|
authenticate(settings, username, password)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
package gitbucket.core.servlet
|
|
||||||
|
|
||||||
import java.io.File
|
|
||||||
import java.sql.{DriverManager, Connection}
|
|
||||||
import gitbucket.core.plugin.PluginRegistry
|
|
||||||
import gitbucket.core.service.SystemSettingsService
|
|
||||||
import gitbucket.core.util._
|
|
||||||
import org.apache.commons.io.FileUtils
|
|
||||||
import javax.servlet.{ServletContextListener, ServletContextEvent}
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import Directory._
|
|
||||||
import ControlUtil._
|
|
||||||
import JDBCUtil._
|
|
||||||
import org.eclipse.jgit.api.Git
|
|
||||||
import gitbucket.core.util.Versions
|
|
||||||
import gitbucket.core.util.Directory
|
|
||||||
|
|
||||||
object AutoUpdate {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The history of versions. A head of this sequence is the current GitBucket version.
|
|
||||||
*/
|
|
||||||
val versions = Seq(
|
|
||||||
new Version(3, 13),
|
|
||||||
new Version(3, 12),
|
|
||||||
new Version(3, 11),
|
|
||||||
new Version(3, 10),
|
|
||||||
new Version(3, 9),
|
|
||||||
new Version(3, 8),
|
|
||||||
new Version(3, 7) with SystemSettingsService {
|
|
||||||
override def update(conn: Connection, cl: ClassLoader): Unit = {
|
|
||||||
super.update(conn, cl)
|
|
||||||
val settings = loadSystemSettings()
|
|
||||||
if(settings.notification){
|
|
||||||
saveSystemSettings(settings.copy(useSMTP = true))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new Version(3, 6),
|
|
||||||
new Version(3, 5),
|
|
||||||
new Version(3, 4),
|
|
||||||
new Version(3, 3),
|
|
||||||
new Version(3, 2),
|
|
||||||
new Version(3, 1),
|
|
||||||
new Version(3, 0),
|
|
||||||
new Version(2, 8),
|
|
||||||
new Version(2, 7) {
|
|
||||||
override def update(conn: Connection, cl: ClassLoader): Unit = {
|
|
||||||
super.update(conn, cl)
|
|
||||||
conn.select("SELECT * FROM REPOSITORY"){ rs =>
|
|
||||||
// Rename attached files directory from /issues to /comments
|
|
||||||
val userName = rs.getString("USER_NAME")
|
|
||||||
val repoName = rs.getString("REPOSITORY_NAME")
|
|
||||||
defining(Directory.getAttachedDir(userName, repoName)){ newDir =>
|
|
||||||
val oldDir = new File(newDir.getParentFile, "issues")
|
|
||||||
if(oldDir.exists && oldDir.isDirectory){
|
|
||||||
oldDir.renameTo(newDir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Update ORIGIN_USER_NAME and ORIGIN_REPOSITORY_NAME if it does not exist
|
|
||||||
val originalUserName = rs.getString("ORIGIN_USER_NAME")
|
|
||||||
val originalRepoName = rs.getString("ORIGIN_REPOSITORY_NAME")
|
|
||||||
if(originalUserName != null && originalRepoName != null){
|
|
||||||
if(conn.selectInt("SELECT COUNT(*) FROM REPOSITORY WHERE USER_NAME = ? AND REPOSITORY_NAME = ?",
|
|
||||||
originalUserName, originalRepoName) == 0){
|
|
||||||
conn.update("UPDATE REPOSITORY SET ORIGIN_USER_NAME = NULL, ORIGIN_REPOSITORY_NAME = NULL " +
|
|
||||||
"WHERE USER_NAME = ? AND REPOSITORY_NAME = ?", userName, repoName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Update PARENT_USER_NAME and PARENT_REPOSITORY_NAME if it does not exist
|
|
||||||
val parentUserName = rs.getString("PARENT_USER_NAME")
|
|
||||||
val parentRepoName = rs.getString("PARENT_REPOSITORY_NAME")
|
|
||||||
if(parentUserName != null && parentRepoName != null){
|
|
||||||
if(conn.selectInt("SELECT COUNT(*) FROM REPOSITORY WHERE USER_NAME = ? AND REPOSITORY_NAME = ?",
|
|
||||||
parentUserName, parentRepoName) == 0){
|
|
||||||
conn.update("UPDATE REPOSITORY SET PARENT_USER_NAME = NULL, PARENT_REPOSITORY_NAME = NULL " +
|
|
||||||
"WHERE USER_NAME = ? AND REPOSITORY_NAME = ?", userName, repoName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new Version(2, 6),
|
|
||||||
new Version(2, 5),
|
|
||||||
new Version(2, 4),
|
|
||||||
new Version(2, 3) {
|
|
||||||
override def update(conn: Connection, cl: ClassLoader): Unit = {
|
|
||||||
super.update(conn, cl)
|
|
||||||
conn.select("SELECT ACTIVITY_ID, ADDITIONAL_INFO FROM ACTIVITY WHERE ACTIVITY_TYPE='push'"){ rs =>
|
|
||||||
val curInfo = rs.getString("ADDITIONAL_INFO")
|
|
||||||
val newInfo = curInfo.split("\n").filter(_ matches "^[0-9a-z]{40}:.*").mkString("\n")
|
|
||||||
if (curInfo != newInfo) {
|
|
||||||
conn.update("UPDATE ACTIVITY SET ADDITIONAL_INFO = ? WHERE ACTIVITY_ID = ?", newInfo, rs.getInt("ACTIVITY_ID"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ignore {
|
|
||||||
FileUtils.deleteDirectory(Directory.getPluginCacheDir())
|
|
||||||
//FileUtils.deleteDirectory(new File(Directory.PluginHome))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new Version(2, 2),
|
|
||||||
new Version(2, 1),
|
|
||||||
new Version(2, 0){
|
|
||||||
override def update(conn: Connection, cl: ClassLoader): Unit = {
|
|
||||||
import eu.medsea.mimeutil.{MimeUtil2, MimeType}
|
|
||||||
|
|
||||||
val mimeUtil = new MimeUtil2()
|
|
||||||
mimeUtil.registerMimeDetector("eu.medsea.mimeutil.detector.MagicMimeMimeDetector")
|
|
||||||
|
|
||||||
super.update(conn, cl)
|
|
||||||
conn.select("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY"){ rs =>
|
|
||||||
defining(Directory.getAttachedDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME"))){ dir =>
|
|
||||||
if(dir.exists && dir.isDirectory){
|
|
||||||
dir.listFiles.foreach { file =>
|
|
||||||
if(file.getName.indexOf('.') < 0){
|
|
||||||
val mimeType = MimeUtil2.getMostSpecificMimeType(mimeUtil.getMimeTypes(file, new MimeType("application/octet-stream"))).toString
|
|
||||||
if(mimeType.startsWith("image/")){
|
|
||||||
file.renameTo(new File(file.getParent, file.getName + "." + mimeType.split("/")(1)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Version(1, 13),
|
|
||||||
Version(1, 12),
|
|
||||||
Version(1, 11),
|
|
||||||
Version(1, 10),
|
|
||||||
Version(1, 9),
|
|
||||||
Version(1, 8),
|
|
||||||
Version(1, 7),
|
|
||||||
Version(1, 6),
|
|
||||||
Version(1, 5),
|
|
||||||
Version(1, 4),
|
|
||||||
new Version(1, 3){
|
|
||||||
override def update(conn: Connection, cl: ClassLoader): Unit = {
|
|
||||||
super.update(conn, cl)
|
|
||||||
// Fix wiki repository configuration
|
|
||||||
conn.select("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY"){ rs =>
|
|
||||||
using(Git.open(getWikiRepositoryDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME")))){ git =>
|
|
||||||
defining(git.getRepository.getConfig){ config =>
|
|
||||||
if(!config.getBoolean("http", "receivepack", false)){
|
|
||||||
config.setBoolean("http", null, "receivepack", true)
|
|
||||||
config.save
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Version(1, 2),
|
|
||||||
Version(1, 1),
|
|
||||||
Version(1, 0),
|
|
||||||
Version(0, 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The head version of GitBucket.
|
|
||||||
*/
|
|
||||||
val headVersion = versions.head
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The version file (GITBUCKET_HOME/version).
|
|
||||||
*/
|
|
||||||
lazy val versionFile = new File(GitBucketHome, "version")
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current version from the version file.
|
|
||||||
*/
|
|
||||||
def getCurrentVersion(): Version = {
|
|
||||||
if(versionFile.exists){
|
|
||||||
FileUtils.readFileToString(versionFile, "UTF-8").trim.split("\\.") match {
|
|
||||||
case Array(majorVersion, minorVersion) => {
|
|
||||||
versions.find { v =>
|
|
||||||
v.majorVersion == majorVersion.toInt && v.minorVersion == minorVersion.toInt
|
|
||||||
}.getOrElse(Version(0, 0))
|
|
||||||
}
|
|
||||||
case _ => Version(0, 0)
|
|
||||||
}
|
|
||||||
} else Version(0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -5,16 +5,16 @@ import javax.servlet.http._
|
|||||||
import gitbucket.core.plugin.{GitRepositoryFilter, GitRepositoryRouting, PluginRegistry}
|
import gitbucket.core.plugin.{GitRepositoryFilter, GitRepositoryRouting, PluginRegistry}
|
||||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||||
import gitbucket.core.service.{RepositoryService, AccountService, SystemSettingsService}
|
import gitbucket.core.service.{RepositoryService, AccountService, SystemSettingsService}
|
||||||
import gitbucket.core.util.{Keys, Implicits}
|
import gitbucket.core.util.{Keys, Implicits, AuthUtil}
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import Implicits._
|
import Implicits._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides BASIC Authentication for [[GitRepositoryServlet]].
|
* Provides BASIC Authentication for [[GitRepositoryServlet]].
|
||||||
*/
|
*/
|
||||||
class BasicAuthenticationFilter extends Filter with RepositoryService with AccountService with SystemSettingsService {
|
class GitAuthenticationFilter extends Filter with RepositoryService with AccountService with SystemSettingsService {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(classOf[BasicAuthenticationFilter])
|
private val logger = LoggerFactory.getLogger(classOf[GitAuthenticationFilter])
|
||||||
|
|
||||||
def init(config: FilterConfig) = {}
|
def init(config: FilterConfig) = {}
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
|
|||||||
} catch {
|
} catch {
|
||||||
case ex: Exception => {
|
case ex: Exception => {
|
||||||
logger.error("error", ex)
|
logger.error("error", ex)
|
||||||
requireAuth(response)
|
AuthUtil.requireAuth(response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,7 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
|
|||||||
|
|
||||||
val account = for {
|
val account = for {
|
||||||
auth <- Option(request.getHeader("Authorization"))
|
auth <- Option(request.getHeader("Authorization"))
|
||||||
Array(username, password) = decodeAuthHeader(auth).split(":", 2)
|
Array(username, password) = AuthUtil.decodeAuthHeader(auth).split(":", 2)
|
||||||
account <- authenticate(settings, username, password)
|
account <- authenticate(settings, username, password)
|
||||||
} yield {
|
} yield {
|
||||||
request.setAttribute(Keys.Request.UserName, account.userName)
|
request.setAttribute(Keys.Request.UserName, account.userName)
|
||||||
@@ -64,7 +64,7 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
|
|||||||
if(filter.filter(request.gitRepositoryPath, account.map(_.userName), settings, isUpdating)){
|
if(filter.filter(request.gitRepositoryPath, account.map(_.userName), settings, isUpdating)){
|
||||||
chain.doFilter(request, response)
|
chain.doFilter(request, response)
|
||||||
} else {
|
} else {
|
||||||
requireAuth(response)
|
AuthUtil.requireAuth(response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
|
|||||||
} else {
|
} else {
|
||||||
val passed = for {
|
val passed = for {
|
||||||
auth <- Option(request.getHeader("Authorization"))
|
auth <- Option(request.getHeader("Authorization"))
|
||||||
Array(username, password) = decodeAuthHeader(auth).split(":", 2)
|
Array(username, password) = AuthUtil.decodeAuthHeader(auth).split(":", 2)
|
||||||
account <- authenticate(settings, username, password)
|
account <- authenticate(settings, username, password)
|
||||||
} yield if(isUpdating || repository.repository.isPrivate){
|
} yield if(isUpdating || repository.repository.isPrivate){
|
||||||
if(hasWritePermission(repository.owner, repository.name, Some(account))){
|
if(hasWritePermission(repository.owner, repository.name, Some(account))){
|
||||||
@@ -93,7 +93,7 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
|
|||||||
if(passed.getOrElse(false)){
|
if(passed.getOrElse(false)){
|
||||||
chain.doFilter(request, response)
|
chain.doFilter(request, response)
|
||||||
} else {
|
} else {
|
||||||
requireAuth(response)
|
AuthUtil.requireAuth(response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,17 +108,4 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def requireAuth(response: HttpServletResponse): Unit = {
|
|
||||||
response.setHeader("WWW-Authenticate", "BASIC realm=\"GitBucket\"")
|
|
||||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def decodeAuthHeader(header: String): String = {
|
|
||||||
try {
|
|
||||||
new String(new sun.misc.BASE64Decoder().decodeBuffer(header.substring(6)))
|
|
||||||
} catch {
|
|
||||||
case _: Throwable => ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,7 @@ import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
|
|||||||
* Provides Git repository via HTTP.
|
* Provides Git repository via HTTP.
|
||||||
*
|
*
|
||||||
* This servlet provides only Git repository functionality.
|
* This servlet provides only Git repository functionality.
|
||||||
* Authentication is provided by [[BasicAuthenticationFilter]].
|
* Authentication is provided by [[GitAuthenticationFilter]].
|
||||||
*/
|
*/
|
||||||
class GitRepositoryServlet extends GitServlet with SystemSettingsService {
|
class GitRepositoryServlet extends GitServlet with SystemSettingsService {
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
package gitbucket.core.servlet
|
package gitbucket.core.servlet
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
import akka.event.Logging
|
import akka.event.Logging
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
|
import gitbucket.core.GitBucketCoreModule
|
||||||
import gitbucket.core.plugin.PluginRegistry
|
import gitbucket.core.plugin.PluginRegistry
|
||||||
import gitbucket.core.service.{ActivityService, SystemSettingsService}
|
import gitbucket.core.service.{ActivityService, SystemSettingsService}
|
||||||
import org.apache.commons.io.FileUtils
|
import gitbucket.core.util.DatabaseConfig
|
||||||
|
import gitbucket.core.util.Directory._
|
||||||
|
import gitbucket.core.util.JDBCUtil._
|
||||||
|
import io.github.gitbucket.solidbase.Solidbase
|
||||||
|
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
|
||||||
import javax.servlet.{ServletContextListener, ServletContextEvent}
|
import javax.servlet.{ServletContextListener, ServletContextEvent}
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import gitbucket.core.util.Versions
|
|
||||||
import akka.actor.{Actor, Props, ActorSystem}
|
import akka.actor.{Actor, Props, ActorSystem}
|
||||||
import com.typesafe.akka.extension.quartz.QuartzSchedulerExtension
|
import com.typesafe.akka.extension.quartz.QuartzSchedulerExtension
|
||||||
import AutoUpdate._
|
import scala.collection.JavaConverters._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize GitBucket system.
|
* Initialize GitBucket system.
|
||||||
@@ -29,15 +36,63 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
|
|||||||
|
|
||||||
Database() withTransaction { session =>
|
Database() withTransaction { session =>
|
||||||
val conn = session.conn
|
val conn = session.conn
|
||||||
|
val manager = new JDBCVersionManager(conn)
|
||||||
|
|
||||||
// Migration
|
// Check version
|
||||||
logger.debug("Start schema update")
|
val versionFile = new File(GitBucketHome, "version")
|
||||||
Versions.update(conn, headVersion, getCurrentVersion(), versions, Thread.currentThread.getContextClassLoader){ conn =>
|
|
||||||
FileUtils.writeStringToFile(versionFile, headVersion.versionString, "UTF-8")
|
if(versionFile.exists()){
|
||||||
|
val version = FileUtils.readFileToString(versionFile, "UTF-8")
|
||||||
|
if(version == "3.14"){
|
||||||
|
// Initialization for GitBucket 3.14
|
||||||
|
logger.info("Migration to GitBucket 4.x start")
|
||||||
|
|
||||||
|
// Backup current data
|
||||||
|
val dataMvFile = new File(GitBucketHome, "data.mv.db")
|
||||||
|
if(dataMvFile.exists) {
|
||||||
|
FileUtils.copyFile(dataMvFile, new File(GitBucketHome, "data.mv.db_3.14"))
|
||||||
|
}
|
||||||
|
val dataTraceFile = new File(GitBucketHome, "data.trace.db")
|
||||||
|
if(dataTraceFile.exists) {
|
||||||
|
FileUtils.copyFile(dataTraceFile, new File(GitBucketHome, "data.trace.db_3.14"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change form
|
||||||
|
manager.initialize()
|
||||||
|
manager.updateVersion(GitBucketCoreModule.getModuleId, "4.0.0")
|
||||||
|
conn.select("SELECT PLUGIN_ID, VERSION FROM PLUGIN"){ rs =>
|
||||||
|
manager.updateVersion(rs.getString("PLUGIN_ID"), rs.getString("VERSION"))
|
||||||
|
}
|
||||||
|
conn.update("DROP TABLE PLUGIN")
|
||||||
|
versionFile.delete()
|
||||||
|
|
||||||
|
logger.info("Migration to GitBucket 4.x completed")
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new Exception("GitBucket can't migrate from this version. Please update to 3.14 at first.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run normal migration
|
||||||
|
logger.info("Start schema update")
|
||||||
|
val solidbase = new Solidbase()
|
||||||
|
solidbase.migrate(conn, Thread.currentThread.getContextClassLoader, DatabaseConfig.liquiDriver, GitBucketCoreModule)
|
||||||
|
|
||||||
|
// Rescue code for users who updated from 3.14 to 4.0.0
|
||||||
|
// https://github.com/gitbucket/gitbucket/issues/1227
|
||||||
|
val currentVersion = manager.getCurrentVersion(GitBucketCoreModule.getModuleId)
|
||||||
|
val databaseVersion = if(currentVersion == "4.0"){
|
||||||
|
manager.updateVersion(GitBucketCoreModule.getModuleId, "4.0.0")
|
||||||
|
"4.0.0"
|
||||||
|
} else currentVersion
|
||||||
|
|
||||||
|
val gitbucketVersion = GitBucketCoreModule.getVersions.asScala.last.getVersion
|
||||||
|
if(databaseVersion != gitbucketVersion){
|
||||||
|
throw new IllegalStateException(s"Initialization failed. GitBucket version is ${gitbucketVersion}, but database version is ${databaseVersion}.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load plugins
|
// Load plugins
|
||||||
logger.debug("Initialize plugins")
|
logger.info("Initialize plugins")
|
||||||
PluginRegistry.initialize(event.getServletContext, loadSystemSettings(), conn)
|
PluginRegistry.initialize(event.getServletContext, loadSystemSettings(), conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supply assets which are provided by plugins.
|
||||||
|
*/
|
||||||
|
class PluginAssetsServlet extends HttpServlet {
|
||||||
|
|
||||||
|
override def doGet(req: HttpServletRequest, resp: HttpServletResponse): Unit = {
|
||||||
|
val assetsMappings = PluginRegistry().getAssetsMappings
|
||||||
|
val path = req.getRequestURI.substring(req.getContextPath.length)
|
||||||
|
|
||||||
|
assetsMappings
|
||||||
|
.find { case (prefix, _, _) => path.startsWith("/plugin-assets" + prefix) }
|
||||||
|
.flatMap { case (prefix, resourcePath, classLoader) =>
|
||||||
|
val resourceName = path.substring(("/plugin-assets" + prefix).length)
|
||||||
|
Option(classLoader.getResourceAsStream(resourcePath.replaceFirst("^/", "") + resourceName))
|
||||||
|
}
|
||||||
|
.map { in =>
|
||||||
|
try {
|
||||||
|
val bytes = IOUtils.toByteArray(in)
|
||||||
|
resp.setContentLength(bytes.length)
|
||||||
|
resp.setContentType(FileUtil.getContentType(path, bytes))
|
||||||
|
resp.getOutputStream.write(bytes)
|
||||||
|
} finally {
|
||||||
|
in.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.getOrElse {
|
||||||
|
resp.setStatus(404)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ package gitbucket.core.servlet
|
|||||||
|
|
||||||
import javax.servlet._
|
import javax.servlet._
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
import com.mchange.v2.c3p0.ComboPooledDataSource
|
import com.zaxxer.hikari._
|
||||||
import gitbucket.core.util.DatabaseConfig
|
import gitbucket.core.util.DatabaseConfig
|
||||||
import org.scalatra.ScalatraBase
|
import org.scalatra.ScalatraBase
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
@@ -46,14 +46,21 @@ object Database {
|
|||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(Database.getClass)
|
private val logger = LoggerFactory.getLogger(Database.getClass)
|
||||||
|
|
||||||
private val dataSource: ComboPooledDataSource = {
|
private val dataSource: HikariDataSource = {
|
||||||
val ds = new ComboPooledDataSource
|
val config = new HikariConfig()
|
||||||
ds.setDriverClass(DatabaseConfig.driver)
|
config.setDriverClassName(DatabaseConfig.jdbcDriver)
|
||||||
ds.setJdbcUrl(DatabaseConfig.url)
|
config.setJdbcUrl(DatabaseConfig.url)
|
||||||
ds.setUser(DatabaseConfig.user)
|
config.setUsername(DatabaseConfig.user)
|
||||||
ds.setPassword(DatabaseConfig.password)
|
config.setPassword(DatabaseConfig.password)
|
||||||
|
config.setAutoCommit(false)
|
||||||
|
DatabaseConfig.connectionTimeout.foreach(config.setConnectionTimeout)
|
||||||
|
DatabaseConfig.idleTimeout.foreach(config.setIdleTimeout)
|
||||||
|
DatabaseConfig.maxLifetime.foreach(config.setMaxLifetime)
|
||||||
|
DatabaseConfig.minimumIdle.foreach(config.setMinimumIdle)
|
||||||
|
DatabaseConfig.maximumPoolSize.foreach(config.setMaximumPoolSize)
|
||||||
|
|
||||||
logger.debug("load database connection pool")
|
logger.debug("load database connection pool")
|
||||||
ds
|
new HikariDataSource(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val db: SlickDatabase = {
|
private val db: SlickDatabase = {
|
||||||
|
|||||||
@@ -5,14 +5,15 @@ import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry}
|
|||||||
import gitbucket.core.service.{RepositoryService, AccountService, SystemSettingsService}
|
import gitbucket.core.service.{RepositoryService, AccountService, SystemSettingsService}
|
||||||
import gitbucket.core.servlet.{Database, CommitLogHook}
|
import gitbucket.core.servlet.{Database, CommitLogHook}
|
||||||
import gitbucket.core.util.{Directory, ControlUtil}
|
import gitbucket.core.util.{Directory, ControlUtil}
|
||||||
import org.apache.sshd.server.{CommandFactory, Environment, ExitCallback, Command}
|
import org.apache.sshd.server.{CommandFactory, Environment, ExitCallback, Command, SessionAware}
|
||||||
|
import org.apache.sshd.server.session.ServerSession
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.io.{File, InputStream, OutputStream}
|
import java.io.{File, InputStream, OutputStream}
|
||||||
import ControlUtil._
|
import ControlUtil._
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import Directory._
|
import Directory._
|
||||||
import org.eclipse.jgit.transport.{ReceivePack, UploadPack}
|
import org.eclipse.jgit.transport.{ReceivePack, UploadPack}
|
||||||
import org.apache.sshd.server.command.UnknownCommand
|
import org.apache.sshd.server.scp.UnknownCommand
|
||||||
import org.eclipse.jgit.errors.RepositoryNotFoundException
|
import org.eclipse.jgit.errors.RepositoryNotFoundException
|
||||||
|
|
||||||
object GitCommand {
|
object GitCommand {
|
||||||
@@ -20,21 +21,24 @@ object GitCommand {
|
|||||||
val SimpleCommandRegex = """\Agit-(upload|receive)-pack '/(.+\.git)'\Z""".r
|
val SimpleCommandRegex = """\Agit-(upload|receive)-pack '/(.+\.git)'\Z""".r
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class GitCommand() extends Command {
|
abstract class GitCommand extends Command with SessionAware {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(classOf[GitCommand])
|
private val logger = LoggerFactory.getLogger(classOf[GitCommand])
|
||||||
@volatile protected var err: OutputStream = null
|
@volatile protected var err: OutputStream = null
|
||||||
@volatile protected var in: InputStream = null
|
@volatile protected var in: InputStream = null
|
||||||
@volatile protected var out: OutputStream = null
|
@volatile protected var out: OutputStream = null
|
||||||
@volatile protected var callback: ExitCallback = null
|
@volatile protected var callback: ExitCallback = null
|
||||||
|
@volatile private var authUser:Option[String] = None
|
||||||
|
|
||||||
protected def runTask(user: String)(implicit session: Session): Unit
|
protected def runTask(authUser: String)(implicit session: Session): Unit
|
||||||
|
|
||||||
private def newTask(user: String): Runnable = new Runnable {
|
private def newTask(): Runnable = new Runnable {
|
||||||
override def run(): Unit = {
|
override def run(): Unit = {
|
||||||
|
authUser match {
|
||||||
|
case Some(authUser) =>
|
||||||
Database() withSession { implicit session =>
|
Database() withSession { implicit session =>
|
||||||
try {
|
try {
|
||||||
runTask(user)
|
runTask(authUser)
|
||||||
callback.onExit(0)
|
callback.onExit(0)
|
||||||
} catch {
|
} catch {
|
||||||
case e: RepositoryNotFoundException =>
|
case e: RepositoryNotFoundException =>
|
||||||
@@ -45,12 +49,16 @@ abstract class GitCommand() extends Command {
|
|||||||
callback.onExit(1)
|
callback.onExit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case None =>
|
||||||
|
val message = "User not authenticated"
|
||||||
|
logger.error(message)
|
||||||
|
callback.onExit(1, message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override def start(env: Environment): Unit = {
|
final override def start(env: Environment): Unit = {
|
||||||
val user = env.getEnv.get("USER")
|
val thread = new Thread(newTask())
|
||||||
val thread = new Thread(newTask(user))
|
|
||||||
thread.start()
|
thread.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,6 +80,10 @@ abstract class GitCommand() extends Command {
|
|||||||
this.in = in
|
this.in = in
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override def setSession(serverSession:ServerSession) {
|
||||||
|
this.authUser = PublicKeyAuthenticator.getUserName(serverSession)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class DefaultGitCommand(val owner: String, val repoName: String) extends GitCommand {
|
abstract class DefaultGitCommand(val owner: String, val repoName: String) extends GitCommand {
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ class NoShell(sshAddress:SshAddress) extends Factory[Command] {
|
|||||||
private var callback: ExitCallback = null
|
private var callback: ExitCallback = null
|
||||||
|
|
||||||
override def start(env: Environment): Unit = {
|
override def start(env: Environment): Unit = {
|
||||||
val user = env.getEnv.get("USER")
|
|
||||||
val message =
|
val message =
|
||||||
"""
|
"""
|
||||||
| Welcome to
|
| Welcome to
|
||||||
@@ -32,7 +31,7 @@ class NoShell(sshAddress:SshAddress) extends Factory[Command] {
|
|||||||
| Please use:
|
| Please use:
|
||||||
|
|
|
|
||||||
| git clone ssh://%s@%s:%d/OWNER/REPOSITORY_NAME.git
|
| git clone ssh://%s@%s:%d/OWNER/REPOSITORY_NAME.git
|
||||||
""".stripMargin.format(user, sshAddress.host, sshAddress.port).replace("\n", "\r\n") + "\r\n"
|
""".stripMargin.format(sshAddress.genericUser, sshAddress.host, sshAddress.port).replace("\n", "\r\n") + "\r\n"
|
||||||
err.write(Constants.encode(message))
|
err.write(Constants.encode(message))
|
||||||
err.flush()
|
err.flush()
|
||||||
in.close()
|
in.close()
|
||||||
|
|||||||
@@ -2,22 +2,73 @@ package gitbucket.core.ssh
|
|||||||
|
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
|
||||||
|
import gitbucket.core.model.SshKey
|
||||||
import gitbucket.core.service.SshKeyService
|
import gitbucket.core.service.SshKeyService
|
||||||
import gitbucket.core.servlet.Database
|
import gitbucket.core.servlet.Database
|
||||||
import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator
|
import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator
|
||||||
import org.apache.sshd.server.session.ServerSession
|
import org.apache.sshd.server.session.ServerSession
|
||||||
|
import org.apache.sshd.common.AttributeStore
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
class PublicKeyAuthenticator extends PublickeyAuthenticator with SshKeyService {
|
object PublicKeyAuthenticator {
|
||||||
|
// put in the ServerSession here to be read by GitCommand later
|
||||||
|
private val userNameSessionKey = new AttributeStore.AttributeKey[String]
|
||||||
|
|
||||||
override def authenticate(username: String, key: PublicKey, session: ServerSession): Boolean = {
|
def putUserName(serverSession:ServerSession, userName:String):Unit =
|
||||||
Database() withSession { implicit session =>
|
serverSession.setAttribute(userNameSessionKey, userName)
|
||||||
getPublicKeys(username).exists { sshKey =>
|
|
||||||
SshUtil.str2PublicKey(sshKey.publicKey) match {
|
|
||||||
case Some(publicKey) => key.equals(publicKey)
|
|
||||||
case _ => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
def getUserName(serverSession:ServerSession):Option[String] =
|
||||||
|
Option(serverSession.getAttribute(userNameSessionKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
class PublicKeyAuthenticator(genericUser:String) extends PublickeyAuthenticator with SshKeyService {
|
||||||
|
private val logger = LoggerFactory.getLogger(classOf[PublicKeyAuthenticator])
|
||||||
|
|
||||||
|
override def authenticate(username: String, key: PublicKey, session: ServerSession): Boolean =
|
||||||
|
if (username == genericUser) authenticateGenericUser(username, key, session, genericUser)
|
||||||
|
else authenticateLoginUser(username, key, session)
|
||||||
|
|
||||||
|
private def authenticateLoginUser(username: String, key: PublicKey, session: ServerSession): Boolean = {
|
||||||
|
val authenticated =
|
||||||
|
Database()
|
||||||
|
.withSession { implicit dbSession => getPublicKeys(username) }
|
||||||
|
.map(_.publicKey)
|
||||||
|
.flatMap(SshUtil.str2PublicKey)
|
||||||
|
.contains(key)
|
||||||
|
if (authenticated) {
|
||||||
|
logger.info(s"authentication as ssh user ${username} succeeded")
|
||||||
|
PublicKeyAuthenticator.putUserName(session, username)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logger.info(s"authentication as ssh user ${username} failed")
|
||||||
|
}
|
||||||
|
authenticated
|
||||||
|
}
|
||||||
|
|
||||||
|
private def authenticateGenericUser(username: String, key: PublicKey, session: ServerSession, genericUser:String): Boolean = {
|
||||||
|
// find all users having the key we got from ssh
|
||||||
|
val possibleUserNames =
|
||||||
|
Database()
|
||||||
|
.withSession { implicit dbSession => getAllKeys() }
|
||||||
|
.filter { sshKey =>
|
||||||
|
SshUtil.str2PublicKey(sshKey.publicKey).exists(_ == key)
|
||||||
|
}
|
||||||
|
.map(_.userName)
|
||||||
|
.distinct
|
||||||
|
// determine the user - if different accounts share the same key, tough luck
|
||||||
|
val uniqueUserName =
|
||||||
|
possibleUserNames match {
|
||||||
|
case List() =>
|
||||||
|
logger.info(s"authentication as generic user ${genericUser} failed, public key not found")
|
||||||
|
None
|
||||||
|
case List(name) =>
|
||||||
|
logger.info(s"authentication as generic user ${genericUser} succeeded, identified ${name}")
|
||||||
|
Some(name)
|
||||||
|
case _ =>
|
||||||
|
logger.info(s"authentication as generic user ${genericUser} failed, public key is ambiguous")
|
||||||
|
None
|
||||||
|
}
|
||||||
|
uniqueUserName.foreach(PublicKeyAuthenticator.putUserName(session, _))
|
||||||
|
uniqueUserName.isDefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ object SshServer {
|
|||||||
provider.setAlgorithm("RSA")
|
provider.setAlgorithm("RSA")
|
||||||
provider.setOverwriteAllowed(false)
|
provider.setOverwriteAllowed(false)
|
||||||
server.setKeyPairProvider(provider)
|
server.setKeyPairProvider(provider)
|
||||||
server.setPublickeyAuthenticator(new PublicKeyAuthenticator)
|
server.setPublickeyAuthenticator(new PublicKeyAuthenticator(sshAddress.genericUser))
|
||||||
server.setCommandFactory(new GitCommandFactory(baseUrl))
|
server.setCommandFactory(new GitCommandFactory(baseUrl))
|
||||||
server.setShellFactory(new NoShell(sshAddress))
|
server.setShellFactory(new NoShell(sshAddress))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,7 @@ object SshUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def fingerPrint(key: String): Option[String] = str2PublicKey(key) match {
|
def fingerPrint(key: String): Option[String] =
|
||||||
case Some(publicKey) => Some(KeyUtils.getFingerPrint(publicKey))
|
str2PublicKey(key) map KeyUtils.getFingerPrint
|
||||||
case None => None
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
21
src/main/scala/gitbucket/core/util/AuthUtil.scala
Normal file
21
src/main/scala/gitbucket/core/util/AuthUtil.scala
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package gitbucket.core.util
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides HTTP (Basic) Authentication related functions.
|
||||||
|
*/
|
||||||
|
object AuthUtil {
|
||||||
|
def requireAuth(response: HttpServletResponse): Unit = {
|
||||||
|
response.setHeader("WWW-Authenticate", "BASIC realm=\"GitBucket\"")
|
||||||
|
response.sendError(HttpServletResponse.SC_UNAUTHORIZED)
|
||||||
|
}
|
||||||
|
|
||||||
|
def decodeAuthHeader(header: String): String = {
|
||||||
|
try {
|
||||||
|
new String(new sun.misc.BASE64Decoder().decodeBuffer(header.substring(6)))
|
||||||
|
} catch {
|
||||||
|
case _: Throwable => ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,97 @@
|
|||||||
package gitbucket.core.util
|
package gitbucket.core.util
|
||||||
|
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
import Directory.DatabaseHome
|
import java.io.File
|
||||||
|
import Directory._
|
||||||
|
import liquibase.database.AbstractJdbcDatabase
|
||||||
|
import liquibase.database.core.{PostgresDatabase, MySQLDatabase, H2Database}
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
|
|
||||||
object DatabaseConfig {
|
object DatabaseConfig {
|
||||||
|
|
||||||
private val config = ConfigFactory.load("database")
|
private lazy val config = {
|
||||||
private val dbUrl = config.getString("db.url")
|
val file = new File(GitBucketHome, "database.conf")
|
||||||
|
if(!file.exists){
|
||||||
|
FileUtils.write(file,
|
||||||
|
"""db {
|
||||||
|
| url = "jdbc:h2:${DatabaseHome};MVCC=true"
|
||||||
|
| user = "sa"
|
||||||
|
| password = "sa"
|
||||||
|
|# connectionTimeout = 30000
|
||||||
|
|# idleTimeout = 600000
|
||||||
|
|# maxLifetime = 1800000
|
||||||
|
|# minimumIdle = 10
|
||||||
|
|# maximumPoolSize = 10
|
||||||
|
|}
|
||||||
|
|""".stripMargin, "UTF-8")
|
||||||
|
}
|
||||||
|
ConfigFactory.parseFile(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
private lazy val dbUrl = config.getString("db.url")
|
||||||
|
|
||||||
def url(directory: Option[String]): String =
|
def url(directory: Option[String]): String =
|
||||||
dbUrl.replace("${DatabaseHome}", directory.getOrElse(DatabaseHome))
|
dbUrl.replace("${DatabaseHome}", directory.getOrElse(DatabaseHome))
|
||||||
|
|
||||||
val url: String = url(None)
|
lazy val url : String = url(None)
|
||||||
val user: String = config.getString("db.user")
|
lazy val user : String = config.getString("db.user")
|
||||||
val password: String = config.getString("db.password")
|
lazy val password : String = config.getString("db.password")
|
||||||
val driver: String = config.getString("db.driver")
|
lazy val jdbcDriver : String = DatabaseType(url).jdbcDriver
|
||||||
|
lazy val slickDriver : slick.driver.JdbcProfile = DatabaseType(url).slickDriver
|
||||||
|
lazy val liquiDriver : AbstractJdbcDatabase = DatabaseType(url).liquiDriver
|
||||||
|
lazy val connectionTimeout : Option[Long] = getOptionValue("db.connectionTimeout", config.getLong)
|
||||||
|
lazy val idleTimeout : Option[Long] = getOptionValue("db.idleTimeout" , config.getLong)
|
||||||
|
lazy val maxLifetime : Option[Long] = getOptionValue("db.maxLifetime" , config.getLong)
|
||||||
|
lazy val minimumIdle : Option[Int] = getOptionValue("db.minimumIdle" , config.getInt)
|
||||||
|
lazy val maximumPoolSize : Option[Int] = getOptionValue("db.maximumPoolSize" , config.getInt)
|
||||||
|
|
||||||
|
private def getOptionValue[T](path: String, f: String => T): Option[T] = {
|
||||||
|
if(config.hasPath(path)) Some(f(path)) else None
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed trait DatabaseType {
|
||||||
|
val jdbcDriver: String
|
||||||
|
val slickDriver: slick.driver.JdbcProfile
|
||||||
|
val liquiDriver: AbstractJdbcDatabase
|
||||||
|
}
|
||||||
|
|
||||||
|
object DatabaseType {
|
||||||
|
|
||||||
|
def apply(url: String): DatabaseType = {
|
||||||
|
if(url.startsWith("jdbc:h2:")){
|
||||||
|
H2
|
||||||
|
} else if(url.startsWith("jdbc:mysql:")){
|
||||||
|
MySQL
|
||||||
|
} else if(url.startsWith("jdbc:postgresql:")){
|
||||||
|
PostgreSQL
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(s"${url} is not supported.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object H2 extends DatabaseType {
|
||||||
|
val jdbcDriver = "org.h2.Driver"
|
||||||
|
val slickDriver = slick.driver.H2Driver
|
||||||
|
val liquiDriver = new H2Database()
|
||||||
|
}
|
||||||
|
|
||||||
|
object MySQL extends DatabaseType {
|
||||||
|
val jdbcDriver = "com.mysql.jdbc.Driver"
|
||||||
|
val slickDriver = slick.driver.MySQLDriver
|
||||||
|
val liquiDriver = new MySQLDatabase()
|
||||||
|
}
|
||||||
|
|
||||||
|
object PostgreSQL extends DatabaseType {
|
||||||
|
val jdbcDriver = "org.postgresql.Driver2"
|
||||||
|
val slickDriver = new slick.driver.PostgresDriver {
|
||||||
|
override def quoteIdentifier(id: String): String = {
|
||||||
|
val s = new StringBuilder(id.length + 4) append '"'
|
||||||
|
for(c <- id) if(c == '"') s append "\"\"" else s append c.toLower
|
||||||
|
(s append '"').toString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val liquiDriver = new PostgresDatabase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import gitbucket.core.api.JsonFormat
|
|||||||
import gitbucket.core.controller.Context
|
import gitbucket.core.controller.Context
|
||||||
import gitbucket.core.servlet.Database
|
import gitbucket.core.servlet.Database
|
||||||
|
|
||||||
|
import java.util.regex.Pattern.quote
|
||||||
|
|
||||||
import javax.servlet.http.{HttpSession, HttpServletRequest}
|
import javax.servlet.http.{HttpSession, HttpServletRequest}
|
||||||
|
|
||||||
import scala.util.matching.Regex
|
import scala.util.matching.Regex
|
||||||
@@ -73,7 +75,7 @@ object Implicits {
|
|||||||
|
|
||||||
def hasAttribute(name: String): Boolean = request.getAttribute(name) != null
|
def hasAttribute(name: String): Boolean = request.getAttribute(name) != null
|
||||||
|
|
||||||
def gitRepositoryPath: String = request.getRequestURI.replaceFirst("^/git/", "/")
|
def gitRepositoryPath: String = request.getRequestURI.replaceFirst("^" + quote(request.getContextPath) + "/git/", "/")
|
||||||
|
|
||||||
def baseUrl:String = {
|
def baseUrl:String = {
|
||||||
val url = request.getRequestURL.toString
|
val url = request.getRequestURL.toString
|
||||||
@@ -83,12 +85,6 @@ object Implicits {
|
|||||||
}
|
}
|
||||||
|
|
||||||
implicit class RichSession(session: HttpSession){
|
implicit class RichSession(session: HttpSession){
|
||||||
|
|
||||||
def putAndGet[T](key: String, value: T): T = {
|
|
||||||
session.setAttribute(key, value)
|
|
||||||
value
|
|
||||||
}
|
|
||||||
|
|
||||||
def getAndRemove[T](key: String): Option[T] = {
|
def getAndRemove[T](key: String): Option[T] = {
|
||||||
val value = session.getAttribute(key).asInstanceOf[T]
|
val value = session.getAttribute(key).asInstanceOf[T]
|
||||||
if(value == null){
|
if(value == null){
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package gitbucket.core.util
|
package gitbucket.core.util
|
||||||
|
|
||||||
|
import java.io._
|
||||||
import java.sql._
|
import java.sql._
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
import ControlUtil._
|
import ControlUtil._
|
||||||
|
import scala.annotation.tailrec
|
||||||
import scala.collection.mutable.ListBuffer
|
import scala.collection.mutable.ListBuffer
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,6 +61,172 @@ object JDBCUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def importAsSQL(in: InputStream): Unit = {
|
||||||
|
conn.setAutoCommit(false)
|
||||||
|
try {
|
||||||
|
using(in){ in =>
|
||||||
|
var out = new ByteArrayOutputStream()
|
||||||
|
|
||||||
|
var length = 0
|
||||||
|
val bytes = new scala.Array[Byte](1024 * 8)
|
||||||
|
var stringLiteral = false
|
||||||
|
|
||||||
|
var count = 0
|
||||||
|
|
||||||
|
while({ length = in.read(bytes); length != -1 }){
|
||||||
|
for(i <- 0 to length - 1){
|
||||||
|
val c = bytes(i)
|
||||||
|
if(c == '\''){
|
||||||
|
stringLiteral = !stringLiteral
|
||||||
|
}
|
||||||
|
if(c == ';' && !stringLiteral){
|
||||||
|
val sql = new String(out.toByteArray, "UTF-8")
|
||||||
|
conn.update(sql)
|
||||||
|
out = new ByteArrayOutputStream()
|
||||||
|
} else {
|
||||||
|
out.write(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
case e: Exception => {
|
||||||
|
conn.rollback()
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def exportAsSQL(targetTables: Seq[String]): File = {
|
||||||
|
val dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss")
|
||||||
|
val file = File.createTempFile("gitbucket-export-", ".sql")
|
||||||
|
|
||||||
|
using(new FileOutputStream(file)) { out =>
|
||||||
|
val dbMeta = conn.getMetaData
|
||||||
|
val allTablesInDatabase = allTablesOrderByDependencies(dbMeta)
|
||||||
|
|
||||||
|
allTablesInDatabase.reverse.foreach { tableName =>
|
||||||
|
if (targetTables.contains(tableName)) {
|
||||||
|
out.write(s"DELETE FROM ${tableName};\n".getBytes("UTF-8"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allTablesInDatabase.foreach { tableName =>
|
||||||
|
if (targetTables.contains(tableName)) {
|
||||||
|
val sb = new StringBuilder()
|
||||||
|
select(s"SELECT * FROM ${tableName}") { rs =>
|
||||||
|
sb.append(s"INSERT INTO ${tableName} (")
|
||||||
|
|
||||||
|
val rsMeta = rs.getMetaData
|
||||||
|
val columns = (1 to rsMeta.getColumnCount).map { i =>
|
||||||
|
(rsMeta.getColumnName(i), rsMeta.getColumnType(i))
|
||||||
|
}
|
||||||
|
sb.append(columns.map(_._1).mkString(", "))
|
||||||
|
sb.append(") VALUES (")
|
||||||
|
|
||||||
|
val values = columns.map { case (columnName, columnType) =>
|
||||||
|
if(rs.getObject(columnName) == null){
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
columnType match {
|
||||||
|
case Types.BOOLEAN | Types.BIT => rs.getBoolean(columnName)
|
||||||
|
case Types.VARCHAR | Types.CLOB | Types.CHAR | Types.LONGVARCHAR => rs.getString(columnName)
|
||||||
|
case Types.INTEGER => rs.getInt(columnName)
|
||||||
|
case Types.TIMESTAMP => rs.getTimestamp(columnName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val columnValues = values.map { value =>
|
||||||
|
value match {
|
||||||
|
case x: String => "'" + x.replace("'", "''") + "'"
|
||||||
|
case x: Timestamp => "'" + dateFormat.format(x) + "'"
|
||||||
|
case null => "NULL"
|
||||||
|
case x => x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.append(columnValues.mkString(", "))
|
||||||
|
sb.append(");\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
out.write(sb.toString.getBytes("UTF-8"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file
|
||||||
|
}
|
||||||
|
|
||||||
|
def allTableNames(): Seq[String] = {
|
||||||
|
using(conn.getMetaData.getTables(null, null, "%", Seq("TABLE").toArray)) { rs =>
|
||||||
|
val tableNames = new ListBuffer[String]
|
||||||
|
while (rs.next) {
|
||||||
|
val name = rs.getString("TABLE_NAME").toUpperCase
|
||||||
|
if (name != "VERSIONS" && name != "PLUGIN") {
|
||||||
|
tableNames += name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tableNames.toSeq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def childTables(meta: DatabaseMetaData, tableName: String): Seq[String] = {
|
||||||
|
val normalizedTableName =
|
||||||
|
if(meta.getDatabaseProductName == "PostgreSQL"){
|
||||||
|
tableName.toLowerCase
|
||||||
|
} else {
|
||||||
|
tableName
|
||||||
|
}
|
||||||
|
|
||||||
|
using(meta.getExportedKeys(null, null, normalizedTableName)) { rs =>
|
||||||
|
val children = new ListBuffer[String]
|
||||||
|
while (rs.next) {
|
||||||
|
val childTableName = rs.getString("FKTABLE_NAME").toUpperCase
|
||||||
|
if(!children.contains(childTableName)){
|
||||||
|
children += childTableName
|
||||||
|
children ++= childTables(meta, childTableName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
children.distinct.toSeq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private def allTablesOrderByDependencies(meta: DatabaseMetaData): Seq[String] = {
|
||||||
|
val tables = allTableNames.map { tableName =>
|
||||||
|
val result = TableDependency(tableName, childTables(meta, tableName))
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
val edges = tables.flatMap { table =>
|
||||||
|
table.children.map { child => (table.tableName, child) }
|
||||||
|
}
|
||||||
|
|
||||||
|
tsort(edges).toSeq
|
||||||
|
}
|
||||||
|
|
||||||
|
case class TableDependency(tableName: String, children: Seq[String])
|
||||||
|
|
||||||
|
|
||||||
|
def tsort[A](edges: Traversable[(A, A)]): Iterable[A] = {
|
||||||
|
@tailrec
|
||||||
|
def tsort(toPreds: Map[A, Set[A]], done: Iterable[A]): Iterable[A] = {
|
||||||
|
val (noPreds, hasPreds) = toPreds.partition { _._2.isEmpty }
|
||||||
|
if (noPreds.isEmpty) {
|
||||||
|
if (hasPreds.isEmpty) done else sys.error(hasPreds.toString)
|
||||||
|
} else {
|
||||||
|
val found = noPreds.map { _._1 }
|
||||||
|
tsort(hasPreds.mapValues { _ -- found }, done ++ found)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val toPred = edges.foldLeft(Map[A, Set[A]]()) { (acc, e) =>
|
||||||
|
acc + (e._1 -> acc.getOrElse(e._1, Set())) + (e._2 -> (acc.getOrElse(e._2, Set()) + e._1))
|
||||||
|
}
|
||||||
|
tsort(toPred, Seq())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -898,11 +898,7 @@ object JGitUtil {
|
|||||||
try {
|
try {
|
||||||
val defaultCommit = walk.parseCommit(defaultObject)
|
val defaultCommit = walk.parseCommit(defaultObject)
|
||||||
val branchName = ref.getName.stripPrefix("refs/heads/")
|
val branchName = ref.getName.stripPrefix("refs/heads/")
|
||||||
val branchCommit = if(branchName == defaultBranch){
|
val branchCommit = walk.parseCommit(ref.getObjectId)
|
||||||
defaultCommit
|
|
||||||
} else {
|
|
||||||
walk.parseCommit(ref.getObjectId)
|
|
||||||
}
|
|
||||||
val when = branchCommit.getCommitterIdent.getWhen
|
val when = branchCommit.getCommitterIdent.getWhen
|
||||||
val committer = branchCommit.getCommitterIdent.getName
|
val committer = branchCommit.getCommitterIdent.getName
|
||||||
val committerEmail = branchCommit.getCommitterIdent.getEmailAddress
|
val committerEmail = branchCommit.getCommitterIdent.getEmailAddress
|
||||||
|
|||||||
@@ -84,6 +84,21 @@ class Mailer(private val smtp: Smtp) extends Notifier {
|
|||||||
enableLineBreaks = false
|
enableLineBreaks = false
|
||||||
))) { case (subject, msg) =>
|
))) { case (subject, msg) =>
|
||||||
recipients(issue) { to =>
|
recipients(issue) { to =>
|
||||||
|
send(to, subject, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"Notifications Successful."
|
||||||
|
}
|
||||||
|
f onSuccess {
|
||||||
|
case s => logger.debug(s)
|
||||||
|
}
|
||||||
|
f onFailure {
|
||||||
|
case t => logger.error("Notifications Failed.", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def send(to: String, subject: String, msg: String)(implicit context: Context): Unit = {
|
||||||
val email = new HtmlEmail
|
val email = new HtmlEmail
|
||||||
email.setHostName(smtp.host)
|
email.setHostName(smtp.host)
|
||||||
email.setSmtpPort(smtp.port.get)
|
email.setSmtpPort(smtp.port.get)
|
||||||
@@ -105,17 +120,7 @@ class Mailer(private val smtp: Smtp) extends Notifier {
|
|||||||
|
|
||||||
email.addTo(to).send
|
email.addTo(to).send
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
"Notifications Successful."
|
|
||||||
}
|
|
||||||
f onSuccess {
|
|
||||||
case s => logger.debug(s)
|
|
||||||
}
|
|
||||||
f onFailure {
|
|
||||||
case t => logger.error("Notifications Failed.", t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
class MockMailer extends Notifier {
|
class MockMailer extends Notifier {
|
||||||
def toNotify(r: RepositoryService.RepositoryInfo, issue: Issue, content: String)
|
def toNotify(r: RepositoryService.RepositoryInfo, issue: Issue, content: String)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package gitbucket.core.util
|
package gitbucket.core.util
|
||||||
|
|
||||||
|
// TODO Move to gitbucket.core.api package?
|
||||||
case class RepositoryName(owner:String, name:String){
|
case class RepositoryName(owner:String, name:String){
|
||||||
val fullName = s"${owner}/${name}"
|
val fullName = s"${owner}/${name}"
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user