mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-02 19:45:57 +01:00
Compare commits
363 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
ea7c8e62de | ||
|
|
b84421723b | ||
|
|
9a42b93d1f | ||
|
|
e162cd956a | ||
|
|
6431d25409 | ||
|
|
c8686f4b34 | ||
|
|
43097b4c1c | ||
|
|
539751a1d9 | ||
|
|
70e2079c7f | ||
|
|
e8737d263a | ||
|
|
4c4b08f1b8 | ||
|
|
c7e1edf262 | ||
|
|
876bb396fd | ||
|
|
a6788f858f | ||
|
|
b263764730 | ||
|
|
1b1bd371a4 | ||
|
|
f194a08cfe | ||
|
|
1211bfc7be | ||
|
|
eab7011e0f | ||
|
|
6f30ffa865 | ||
|
|
de3026248c | ||
|
|
413e75be5a | ||
|
|
6a8ec18f9a | ||
|
|
5b1b2ef3d7 | ||
|
|
9a705c62bf | ||
|
|
b103180bf6 | ||
|
|
536a0d3fe2 | ||
|
|
356202e28a | ||
|
|
6db36e12b5 | ||
|
|
bfcd5a2855 | ||
|
|
e218b52b78 | ||
|
|
46998dc1fa | ||
|
|
977f856854 | ||
|
|
da2a7bf77d | ||
|
|
3da3a048f0 | ||
|
|
7b5b453e56 | ||
|
|
c18f95edf8 | ||
|
|
71cf043f56 | ||
|
|
a31e4b5897 | ||
|
|
1679da4abe | ||
|
|
505bc71f9a | ||
|
|
4bc057c653 | ||
|
|
8eee13d7aa | ||
|
|
8981e339b4 | ||
|
|
e1dd5dd057 | ||
|
|
cb64f8eab8 | ||
|
|
c47d50d0df | ||
|
|
1f46da2273 | ||
|
|
06fc26cd06 | ||
|
|
3a4f9b9027 | ||
|
|
f98c849c7c | ||
|
|
aa0bd5b34a | ||
|
|
b52e904ed1 | ||
|
|
70e0dcf99d | ||
|
|
56bb20dfd2 | ||
|
|
7d7d2f488d | ||
|
|
72affd67b9 | ||
|
|
0cf1f43deb | ||
|
|
8494c682a7 | ||
|
|
1af5611159 | ||
|
|
4d39f63ef7 | ||
|
|
120d1c2fff | ||
|
|
62e9c0358a | ||
|
|
5a90848c75 | ||
|
|
760d443f74 | ||
|
|
5ee0e75dfe | ||
|
|
3b4d2d6f91 | ||
|
|
dfaabeb41d | ||
|
|
ff3205b6c7 | ||
|
|
0fae2dac35 | ||
|
|
4db4fe28b4 | ||
|
|
5b87efa032 | ||
|
|
3ad609bad7 | ||
|
|
8145cba111 | ||
|
|
24b9a9a12c | ||
|
|
ee7220ebd2 | ||
|
|
8fb72fd55e | ||
|
|
a1eded2d9a | ||
|
|
7f184e1126 | ||
|
|
09aafbcce1 | ||
|
|
7f5024a746 | ||
|
|
8fec0870a8 | ||
|
|
a8d2afaff7 | ||
|
|
8fd92f1c2f | ||
|
|
419ea16ead | ||
|
|
e72d808a3c | ||
|
|
44e8c0a9be | ||
|
|
e2c39d7815 | ||
|
|
687cd54f9a | ||
|
|
911754e1dc | ||
|
|
0067cbce6f | ||
|
|
f40f8427aa | ||
|
|
98ceff2391 | ||
|
|
642a51a208 | ||
|
|
9ec7c321d8 | ||
|
|
a3c419b6f5 | ||
|
|
15c28cffa4 | ||
|
|
f4d0f16481 | ||
|
|
45535e4fdf | ||
|
|
64635c5dc6 | ||
|
|
2fd95c7f1a | ||
|
|
eb6da85183 | ||
|
|
bcc05f021c | ||
|
|
d58ed55c3a | ||
|
|
057f029c80 | ||
|
|
c9a12ff913 | ||
|
|
66bf00b5d3 | ||
|
|
7ba3ca6f15 | ||
|
|
a1bacccc09 | ||
|
|
333eeb4bad | ||
|
|
3f2935612d | ||
|
|
4c87bdd959 | ||
|
|
3543073150 | ||
|
|
e50fe604c2 | ||
|
|
63369258bd | ||
|
|
e7c3376303 | ||
|
|
86163f66ce | ||
|
|
518f0bfc28 | ||
|
|
0a759f6127 | ||
|
|
afa79d01b1 | ||
|
|
860fc8ef4c | ||
|
|
5a2224623b | ||
|
|
dda3458e80 | ||
|
|
40e36e3f8b | ||
|
|
df06f509f5 | ||
|
|
0b4d0be1b4 | ||
|
|
0bef8d29d5 | ||
|
|
6265faa14f | ||
|
|
7593c97ad3 | ||
|
|
7c8de834bc | ||
|
|
637e9f906b | ||
|
|
97035be2e5 | ||
|
|
80fedbd335 | ||
|
|
3f6ebc1164 | ||
|
|
27b99319d3 | ||
|
|
a46d1ecf69 | ||
|
|
fb531526bd | ||
|
|
21d6143e40 | ||
|
|
129b424a3a | ||
|
|
689334a599 | ||
|
|
b3ee2222f3 | ||
|
|
da5e4fe5b1 | ||
|
|
f3b7318453 | ||
|
|
3d1c9bc9de | ||
|
|
9874eb7243 | ||
|
|
cc6f4d70da | ||
|
|
9b06bfaaf5 | ||
|
|
cdf0d06bcc | ||
|
|
501f542982 | ||
|
|
1a3504e885 | ||
|
|
fa617badc1 | ||
|
|
1a1267dc60 | ||
|
|
361babd327 | ||
|
|
64cacb18a4 | ||
|
|
833cfc3465 | ||
|
|
5a5bf34fe0 | ||
|
|
95746de5aa | ||
|
|
af7043f4bf | ||
|
|
4f3c780d05 | ||
|
|
249b27593e | ||
|
|
33a079e55f | ||
|
|
27eb1c36bd | ||
|
|
1201271949 | ||
|
|
5c12cca7f5 | ||
|
|
206d597d9b | ||
|
|
1b4c621fef | ||
|
|
03d4b9e9c6 | ||
|
|
82a9d9f7cf | ||
|
|
3e79dcf7a4 | ||
|
|
c9f3ec12a7 | ||
|
|
b781696803 | ||
|
|
19e74a4fe1 | ||
|
|
130aa1e515 | ||
|
|
8dcb8c2ecb | ||
|
|
b599219852 | ||
|
|
58078989f8 | ||
|
|
4276c0970b | ||
|
|
4abd4a4bac | ||
|
|
2b2ae718f7 | ||
|
|
ef201e3319 | ||
|
|
0ab28e6daa | ||
|
|
9b3e8bd22b | ||
|
|
15e8527e01 | ||
|
|
f991f3b454 | ||
|
|
d6a39403e2 | ||
|
|
aa065fd030 | ||
|
|
e92d1eae5a | ||
|
|
b1b132dfc3 | ||
|
|
90c379b3b9 | ||
|
|
b71ff6f179 | ||
|
|
8b44a00299 | ||
|
|
f5c1c0703d | ||
|
|
5036b1a1aa | ||
|
|
f63542dbd0 | ||
|
|
63e605d99c | ||
|
|
9e313bb2b3 | ||
|
|
a03fc4cf4a | ||
|
|
d18f92cfbf | ||
|
|
b96821ca44 | ||
|
|
c61ecdef7a | ||
|
|
b50ca63c2b | ||
|
|
f6624c0002 | ||
|
|
d62fc6e135 | ||
|
|
926c4d948f | ||
|
|
5fd87ca764 | ||
|
|
1042c50cc3 | ||
|
|
adff36c44b | ||
|
|
e8f6d2b990 | ||
|
|
a3b3262ad5 | ||
|
|
9efba82e94 | ||
|
|
781d465f8d | ||
|
|
766867f341 | ||
|
|
e237a44f56 | ||
|
|
249430449b | ||
|
|
73d935efe3 | ||
|
|
79251ef1d1 | ||
|
|
1081c0f16c | ||
|
|
3302b607f1 | ||
|
|
f13a3ee580 | ||
|
|
927969ac17 | ||
|
|
2d8aa4f8b5 | ||
|
|
645af4d2c0 | ||
|
|
34240d16b5 | ||
|
|
89210985d4 | ||
|
|
9d6258861a | ||
|
|
6661314be5 | ||
|
|
e187d026cc | ||
|
|
100b34085c | ||
|
|
ba75079409 | ||
|
|
cbb847704b | ||
|
|
739c99edce | ||
|
|
af2e2f5fd1 | ||
|
|
09fcf9c003 | ||
|
|
5dad0777d7 | ||
|
|
be3aa21651 | ||
|
|
abd729e45f | ||
|
|
162e9a9feb | ||
|
|
7500d017d5 | ||
|
|
e8c4004d5a | ||
|
|
a6f4d9d7cf | ||
|
|
3399278925 | ||
|
|
4457a317e6 | ||
|
|
ef3b02b718 | ||
|
|
9777d543b1 | ||
|
|
51acf72e0d | ||
|
|
6ad724d80c |
7
.github/CONTRIBUTING.md
vendored
Normal file
7
.github/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Guideline for Issues
|
||||||
|
|
||||||
|
- At first, See [FAQ](https://github.com/gitbucket/gitbucket/wiki/FAQ) and check issues whether there is a same question or request in the past.
|
||||||
|
- If you can't find same question and report, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
|
||||||
|
- We can also support in Japaneses other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
|
||||||
|
- Write an issue in English. At least, write subject in English.
|
||||||
|
- First priority of GitBucket is easy installation and reproduce GitHub behavior, so we might reject if your request is against it.
|
||||||
19
.github/ISSUE_TEMPLATE.md
vendored
Normal file
19
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
### Before submitting an issue to Gitbucket I have first:
|
||||||
|
|
||||||
|
- [] read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/.github/CONTRIBUTING.md)
|
||||||
|
- [] searched for similar already existing issue
|
||||||
|
- [] read the documentation and [wiki](https://github.com/gitbucket/gitbucket/wiki)
|
||||||
|
|
||||||
|
*(if you have performed all the above, remove the paragraph and continue describing the issue with template below)*
|
||||||
|
|
||||||
|
## Issue
|
||||||
|
**Impacted version**: xxxx
|
||||||
|
|
||||||
|
**Deployment mode**: *explain here how you use gitbucket : standalone app, under webcontainer (which one), with an http frontend (nginx, httpd, ...)*
|
||||||
|
|
||||||
|
**Problem description**:
|
||||||
|
- *be as explicit has you can*
|
||||||
|
- *describe the problem and its symptoms*
|
||||||
|
- *explain how to reproduce*
|
||||||
|
- *attach whatever information that can help understanding the context (screen capture, log files)*
|
||||||
|
- *do your best to use a correct english (re-read yourself)*
|
||||||
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
### Before submitting a pull-request to Gitbucket I have first:
|
||||||
|
|
||||||
|
- [] read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/.github/CONTRIBUTING.md)
|
||||||
|
- [] rebased my branch over master
|
||||||
|
- [] verified that project is compiling
|
||||||
|
- [] verified that tests are passing
|
||||||
|
- [] squashed my commits as appropriate *(keep several commits if it is relevant to understand the PR)*
|
||||||
|
- [] [marked as closed](https://help.github.com/articles/closing-issues-via-commit-messages/) all issue ID that this PR should correct
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# Guideline for Issues
|
|
||||||
|
|
||||||
- If you have any question about GitBucket, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raise an issue.
|
|
||||||
- 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.
|
|
||||||
- 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.
|
|
||||||
92
README.md
92
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
|
||||||
--------
|
--------
|
||||||
@@ -9,28 +12,21 @@ The current version of GitBucket provides a basic features below:
|
|||||||
|
|
||||||
- Public / Private Git repository (http and ssh access)
|
- Public / Private Git repository (http and ssh access)
|
||||||
- Repository viewer and online file editing
|
- Repository viewer and online file editing
|
||||||
- Repository search (Code and Issues)
|
|
||||||
- Wiki
|
- Wiki
|
||||||
- Issues
|
- Issues / Pull request
|
||||||
- Fork / Pull request
|
|
||||||
- Email notification
|
- Email notification
|
||||||
- Activity timeline
|
|
||||||
- Simple user and group management with LDAP integration
|
- Simple user and group management with LDAP integration
|
||||||
- Gravatar support
|
|
||||||
- Plug-in system
|
- Plug-in system
|
||||||
|
|
||||||
If you want to try the development version of GitBucket, see the documentation for developers at [Wiki](https://github.com/gitbucket/gitbucket/wiki).
|
If you want to try the development version of GitBucket, see [Developer's Guide](https://github.com/gitbucket/gitbucket/blob/master/doc/how_to_run.md).
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
--------
|
--------
|
||||||
|
GitBucket requires **Java8**. You have to install beforehand when it's not installed.
|
||||||
|
|
||||||
1. Download latest **gitbucket.war** from [the release page](https://github.com/gitbucket/gitbucket/releases).
|
1. Download latest **gitbucket.war** from [the release page](https://github.com/gitbucket/gitbucket/releases).
|
||||||
2. Deploy it to the Servlet 3.0 container such as Tomcat 7.x, Jetty 8.x, GlassFish 3.x or higher.
|
2. Deploy it to the Servlet 3.0 container such as Tomcat 7.x, Jetty 8.x, GlassFish 3.x or higher.
|
||||||
3. Access **http://[hostname]:[port]/gitbucket/** using your web browser.
|
3. Access **http://[hostname]:[port]/gitbucket/** using your web browser and logged-in with **root** / **root**.
|
||||||
|
|
||||||
If you are using Gitbucket behind a webserver please make sure you have increased the **client_max_body_size** (on nginx)
|
|
||||||
|
|
||||||
The default administrator account is **root** and password is **root**.
|
|
||||||
|
|
||||||
or you can start GitBucket by `java -jar gitbucket.war` without servlet container. In this case, GitBucket URL is **http://[hostname]:8080/**. You can specify following options.
|
or you can start GitBucket by `java -jar gitbucket.war` without servlet container. In this case, GitBucket URL is **http://[hostname]:8080/**. You can specify following options.
|
||||||
|
|
||||||
@@ -41,35 +37,7 @@ or you can start GitBucket by `java -jar gitbucket.war` without servlet containe
|
|||||||
|
|
||||||
To upgrade GitBucket, only replace gitbucket.war after stop GitBucket. All GitBucket data is stored in HOME/.gitbucket. So if you want to back up GitBucket data, copy this directory to the other disk.
|
To upgrade GitBucket, only replace gitbucket.war after stop GitBucket. All GitBucket data is stored in HOME/.gitbucket. So if you want to back up GitBucket data, copy this directory to the other disk.
|
||||||
|
|
||||||
For Installation on Windows Server with IIS see [this wiki page](https://github.com/gitbucket/gitbucket/wiki/Installation-on-IIS-and-Helicontech-Zoo)
|
About installation on Mac or Windows Server (with IIS), configuration of Apache or Nginx and also integration with other tools or services such as Jenkins or Slack, see [Wiki](https://github.com/gitbucket/gitbucket/wiki).
|
||||||
|
|
||||||
### Mac OS X
|
|
||||||
#### Installing Via Homebrew
|
|
||||||
|
|
||||||
```
|
|
||||||
$ brew install gitbucket
|
|
||||||
==> Downloading https://github.com/takezoe/gitbucket/releases/download/1.10/gitbucket.war
|
|
||||||
######################################################################## 100.0%
|
|
||||||
==> Caveats
|
|
||||||
Note: When using launchctl the port will be 8080.
|
|
||||||
|
|
||||||
To have launchd start gitbucket at login:
|
|
||||||
ln -sfv /usr/local/opt/gitbucket/*.plist ~/Library/LaunchAgents
|
|
||||||
Then to load gitbucket now:
|
|
||||||
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.gitbucket.plist
|
|
||||||
Or, if you don't want/need launchctl, you can just run:
|
|
||||||
java -jar /usr/local/opt/gitbucket/libexec/gitbucket.war
|
|
||||||
==> Summary
|
|
||||||
/usr/local/Cellar/gitbucket/1.10: 3 files, 42M, built in 11 seconds
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Manual Installation
|
|
||||||
On OS X, generate `gitbucket.plist` by [this script](https://raw.githubusercontent.com/gitbucket/gitbucket/master/contrib/macosx/makePlist) and copy it to `~/Library/LaunchAgents/`
|
|
||||||
|
|
||||||
Run the following commands in `Terminal` to
|
|
||||||
|
|
||||||
- start gitbucket: `launchctl load ~/Library/LaunchAgents/gitbucket.plist`
|
|
||||||
- stop gitbucket: `launchctl unload ~/Library/LaunchAgents/gitbucket.plist`
|
|
||||||
|
|
||||||
Plug-ins
|
Plug-ins
|
||||||
--------
|
--------
|
||||||
@@ -91,14 +59,48 @@ 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.0 - 30 Apr 2016
|
||||||
|
|
||||||
|
- MySQL and PostgreSQL support
|
||||||
|
- Data export and import
|
||||||
|
- Migration system has been switched to [solidbase](https://github.com/gitbucket/solidbase)
|
||||||
|
|
||||||
|
### 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
|
||||||
|
- New GitHub UI
|
||||||
|
- Improve mobile view
|
||||||
|
- Improve printing style
|
||||||
|
- Individual URL for pull request tabs
|
||||||
|
- SSH host configuration is separated from HTTP base URL
|
||||||
|
|
||||||
|
### 3.11 - 30 Jan 2016
|
||||||
|
- Upgrade Scalatra to 2.4
|
||||||
|
- Sidebar and Footer for Wiki
|
||||||
|
- Branch protection and receive hook extension point for plug-in
|
||||||
|
- Limit recent updated repositories list
|
||||||
|
- Issue actions look-alike GitHub
|
||||||
|
- Web API for labels
|
||||||
|
- Requires Java 8
|
||||||
|
|
||||||
### 3.10 - 30 Dec 2015
|
### 3.10 - 30 Dec 2015
|
||||||
- Move to Bootstrap3
|
- Move to Bootstrap3
|
||||||
- New URL for raw contents (`raw/master/doc/activity.md` instead of `blob/master/doc/activity.md?raw=true`)
|
- New URL for raw contents (`raw/master/doc/activity.md` instead of `blob/master/doc/activity.md?raw=true`)
|
||||||
- Update xsbt-web-pligin
|
- Update xsbt-web-plugin
|
||||||
- Update H2 database
|
- Update H2 database
|
||||||
|
|
||||||
### 3.9 - 5 Dec 2015
|
### 3.9 - 5 Dec 2015
|
||||||
@@ -342,7 +344,3 @@ Release Notes
|
|||||||
|
|
||||||
### 1.0 - 04 Jul 2013
|
### 1.0 - 04 Jul 2013
|
||||||
- This is a first public release
|
- This is a first public release
|
||||||
|
|
||||||
Sponsors
|
|
||||||
--------
|
|
||||||
[](https://www.jetbrains.com/idea/)
|
|
||||||
|
|||||||
168
build.sbt
Normal file
168
build.sbt
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
val Organization = "gitbucket"
|
||||||
|
val Name = "gitbucket"
|
||||||
|
val GitBucketVersion = "4.0.0"
|
||||||
|
val ScalatraVersion = "2.4.0"
|
||||||
|
val JettyVersion = "9.3.6.v20151106"
|
||||||
|
|
||||||
|
lazy val root = (project in file(".")).enablePlugins(SbtTwirl, JettyPlugin)
|
||||||
|
|
||||||
|
sourcesInBase := false
|
||||||
|
organization := Organization
|
||||||
|
name := Name
|
||||||
|
version := GitBucketVersion
|
||||||
|
scalaVersion := "2.11.8"
|
||||||
|
|
||||||
|
// dependency settings
|
||||||
|
resolvers ++= Seq(
|
||||||
|
Classpaths.typesafeReleases,
|
||||||
|
"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(
|
||||||
|
"org.scala-lang.modules" %% "scala-java8-compat" % "0.7.0",
|
||||||
|
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.1.1.201511131810-r",
|
||||||
|
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.1.1.201511131810-r",
|
||||||
|
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
||||||
|
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
||||||
|
"org.json4s" %% "json4s-jackson" % "3.3.0",
|
||||||
|
"io.github.gitbucket" %% "scalatra-forms" % "1.0.0",
|
||||||
|
"commons-io" % "commons-io" % "2.4",
|
||||||
|
"io.github.gitbucket" % "solidbase" % "1.0.0",
|
||||||
|
"io.github.gitbucket" % "markedj" % "1.0.8",
|
||||||
|
"org.apache.commons" % "commons-compress" % "1.10",
|
||||||
|
"org.apache.commons" % "commons-email" % "1.4",
|
||||||
|
"org.apache.httpcomponents" % "httpclient" % "4.5.1",
|
||||||
|
"org.apache.sshd" % "apache-sshd" % "1.0.0",
|
||||||
|
"org.apache.tika" % "tika-core" % "1.11",
|
||||||
|
"com.typesafe.slick" %% "slick" % "2.1.0",
|
||||||
|
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||||
|
"com.h2database" % "h2" % "1.4.190",
|
||||||
|
"mysql" % "mysql-connector-java" % "5.1.38",
|
||||||
|
"org.postgresql" % "postgresql" % "9.4.1208",
|
||||||
|
"ch.qos.logback" % "logback-classic" % "1.1.1",
|
||||||
|
"com.zaxxer" % "HikariCP" % "2.4.5",
|
||||||
|
"com.typesafe" % "config" % "1.3.0",
|
||||||
|
"com.typesafe.akka" %% "akka-actor" % "2.3.14",
|
||||||
|
"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"),
|
||||||
|
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
||||||
|
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
||||||
|
"junit" % "junit" % "4.12" % "test",
|
||||||
|
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test",
|
||||||
|
"org.scalaz" %% "scalaz-core" % "7.2.0" % "test"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Twirl settings
|
||||||
|
play.twirl.sbt.Import.TwirlKeys.templateImports += "gitbucket.core._"
|
||||||
|
|
||||||
|
// Compiler settings
|
||||||
|
scalacOptions := Seq("-deprecation", "-language:postfixOps", "-Ybackend:GenBCode", "-Ydelambdafy:method", "-target:jvm-1.8")
|
||||||
|
javacOptions in compile ++= Seq("-target", "8", "-source", "8")
|
||||||
|
javaOptions in Jetty += "-Dlogback.configurationFile=/logback-dev.xml"
|
||||||
|
testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "junitxml", "console")
|
||||||
|
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() )
|
||||||
|
fork in Test := true
|
||||||
|
packageOptions += Package.MainClass("JettyLauncher")
|
||||||
|
|
||||||
|
// Assembly settings
|
||||||
|
test in assembly := {}
|
||||||
|
assemblyMergeStrategy in assembly := {
|
||||||
|
case PathList("META-INF", xs @ _*) =>
|
||||||
|
(xs map {_.toLowerCase}) match {
|
||||||
|
case ("manifest.mf" :: Nil) => MergeStrategy.discard
|
||||||
|
case _ => MergeStrategy.discard
|
||||||
|
}
|
||||||
|
case x => MergeStrategy.first
|
||||||
|
}
|
||||||
|
|
||||||
|
// JRebel
|
||||||
|
jrebel.webLinks += (target in webappPrepare).value
|
||||||
|
jrebel.enabled := System.getenv().get("JREBEL") != null
|
||||||
|
javaOptions in Jetty ++= Option(System.getenv().get("JREBEL")).toSeq.flatMap { path =>
|
||||||
|
Seq("-noverify", "-XX:+UseConcMarkSweepGC", "-XX:+CMSClassUnloadingEnabled", s"-javaagent:${path}")
|
||||||
|
}
|
||||||
|
jrebelSettings
|
||||||
|
|
||||||
|
// Create executable war file
|
||||||
|
val executableConfig = config("executable").hide
|
||||||
|
Keys.ivyConfigurations += executableConfig
|
||||||
|
libraryDependencies ++= Seq(
|
||||||
|
"org.eclipse.jetty" % "jetty-security" % JettyVersion % "executable",
|
||||||
|
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "executable",
|
||||||
|
"org.eclipse.jetty" % "jetty-continuation" % JettyVersion % "executable",
|
||||||
|
"org.eclipse.jetty" % "jetty-server" % JettyVersion % "executable",
|
||||||
|
"org.eclipse.jetty" % "jetty-xml" % JettyVersion % "executable",
|
||||||
|
"org.eclipse.jetty" % "jetty-http" % JettyVersion % "executable",
|
||||||
|
"org.eclipse.jetty" % "jetty-servlet" % JettyVersion % "executable",
|
||||||
|
"org.eclipse.jetty" % "jetty-io" % JettyVersion % "executable",
|
||||||
|
"org.eclipse.jetty" % "jetty-util" % JettyVersion % "executable"
|
||||||
|
)
|
||||||
|
|
||||||
|
val executableKey = TaskKey[File]("executable")
|
||||||
|
executableKey := {
|
||||||
|
import org.apache.ivy.util.ChecksumHelper
|
||||||
|
import java.util.jar.{ Manifest => JarManifest }
|
||||||
|
import java.util.jar.Attributes.{ Name => AttrName }
|
||||||
|
|
||||||
|
val workDir = Keys.target.value / "executable"
|
||||||
|
val warName = Keys.name.value + ".war"
|
||||||
|
|
||||||
|
val log = streams.value.log
|
||||||
|
log info s"building executable webapp in ${workDir}"
|
||||||
|
|
||||||
|
// initialize temp directory
|
||||||
|
val temp = workDir / "webapp"
|
||||||
|
IO delete temp
|
||||||
|
|
||||||
|
// include jetty classes
|
||||||
|
val jettyJars = Keys.update.value select configurationFilter(name = executableConfig.name)
|
||||||
|
jettyJars foreach { jar =>
|
||||||
|
IO unzip (jar, temp, (name:String) =>
|
||||||
|
(name startsWith "javax/") ||
|
||||||
|
(name startsWith "org/")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// include original war file
|
||||||
|
val warFile = (Keys.`package`).value
|
||||||
|
IO unzip (warFile, temp)
|
||||||
|
|
||||||
|
// include launcher classes
|
||||||
|
val classDir = (Keys.classDirectory in Compile).value
|
||||||
|
val launchClasses = Seq("JettyLauncher.class" /*, "HttpsSupportConnector.class" */)
|
||||||
|
launchClasses foreach { name =>
|
||||||
|
IO copyFile (classDir / name, temp / name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// zip it up
|
||||||
|
IO delete (temp / "META-INF" / "MANIFEST.MF")
|
||||||
|
val contentMappings = (temp.*** --- PathFinder(temp)).get pair relativeTo(temp)
|
||||||
|
val manifest = new JarManifest
|
||||||
|
manifest.getMainAttributes put (AttrName.MANIFEST_VERSION, "1.0")
|
||||||
|
manifest.getMainAttributes put (AttrName.MAIN_CLASS, "JettyLauncher")
|
||||||
|
val outputFile = workDir / warName
|
||||||
|
IO jar (contentMappings, outputFile, manifest)
|
||||||
|
|
||||||
|
// generate checksums
|
||||||
|
Seq(
|
||||||
|
"md5" -> "MD5",
|
||||||
|
"sha1" -> "SHA-1",
|
||||||
|
"sha256" -> "SHA-256"
|
||||||
|
)
|
||||||
|
.foreach { case (extension, algorithm) =>
|
||||||
|
val checksumFile = workDir / (warName + "." + extension)
|
||||||
|
Checksums generate (outputFile, checksumFile, algorithm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// done
|
||||||
|
log info s"built executable webapp ${outputFile}"
|
||||||
|
outputFile
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
Keys.artifact in (Compile, executableKey) ~= {
|
||||||
|
_ copy (`type` = "war", extension = "war"))
|
||||||
|
}
|
||||||
|
addArtifact(Keys.artifact in (Compile, executableKey), executableKey)
|
||||||
|
*/
|
||||||
@@ -8,7 +8,7 @@ To release a new version of GitBucket, add the version definition to the [gitbuc
|
|||||||
object AutoUpdate {
|
object AutoUpdate {
|
||||||
...
|
...
|
||||||
/**
|
/**
|
||||||
* The history of versions. A head of this sequence is the current BitBucket version.
|
* The history of versions. A head of this sequence is the current GitBucket version.
|
||||||
*/
|
*/
|
||||||
val versions = Seq(
|
val versions = Seq(
|
||||||
Version(1, 0)
|
Version(1, 0)
|
||||||
@@ -20,7 +20,7 @@ Next, add a SQL file which updates database schema into [/src/main/resources/upd
|
|||||||
|
|
||||||
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.
|
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:
|
We can also add any Scala code for upgrade GitBucket which modifies resources other than database. Override ```Version.update``` like below:
|
||||||
|
|
||||||
```scala
|
```scala
|
||||||
val versions = Seq(
|
val versions = Seq(
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ This directory has following structure:
|
|||||||
* /HOME/gitbucket
|
* /HOME/gitbucket
|
||||||
* /repositories
|
* /repositories
|
||||||
* /USER_NAME
|
* /USER_NAME
|
||||||
* / REPO_NAME.git (substance of repository. GitServlet sees this directory)
|
* /REPO_NAME.git (substance of repository. GitServlet sees this directory)
|
||||||
* / REPO_NAME
|
* /REPO_NAME
|
||||||
* /issues (files which are attached to issue)
|
* /issues (files which are attached to issue)
|
||||||
* / REPO_NAME.wiki.git (wiki repository)
|
* /REPO_NAME.wiki.git (wiki repository)
|
||||||
* /data
|
* /data
|
||||||
* /USER_NAME
|
* /USER_NAME
|
||||||
* /files
|
* /files
|
||||||
|
|||||||
@@ -27,7 +27,16 @@ $ sbt package
|
|||||||
|
|
||||||
To build executable war file, run
|
To build executable war file, run
|
||||||
|
|
||||||
* Windows: Not available
|
```
|
||||||
* Linux: `./release/make-release-war.sh`
|
$ sbt executable
|
||||||
|
```
|
||||||
|
|
||||||
at the top of the source tree. It generates executable `gitbucket.war` into `target/scala-2.11`. We release this war file as release artifact.
|
at the top of the source tree. It generates executable `gitbucket.war` into `target/executable`. We release this war file as release artifact.
|
||||||
|
|
||||||
|
Run tests spec
|
||||||
|
---------
|
||||||
|
To run the full serie of tests, run the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
sbt test
|
||||||
|
```
|
||||||
|
|||||||
148
doc/jrebel.md
Normal file
148
doc/jrebel.md
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
JRebel integration (optional)
|
||||||
|
=============================
|
||||||
|
|
||||||
|
[JRebel](http://zeroturnaround.com/software/jrebel/) is a JVM plugin that makes developing web apps much faster.
|
||||||
|
JRebel is generally able to eliminate the need for the following slow "app restart" in sbt following a code change:
|
||||||
|
|
||||||
|
```
|
||||||
|
> jetty:start
|
||||||
|
```
|
||||||
|
|
||||||
|
While JRebel is not open source, it does reload your code faster than the `~;copy-resources;aux-compile` way of doing things using `sbt`.
|
||||||
|
|
||||||
|
It's only used during development, and doesn't change your deployed app in any way.
|
||||||
|
|
||||||
|
JRebel used to be free for Scala developers, but that changed recently, and now there's a cost associated with usage for Scala. There are trial plans and free non-commercial licenses available if you just want to try it out.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## 1. Get a JRebel license
|
||||||
|
|
||||||
|
Sign up for a [usage plan](https://my.jrebel.com/). You will need to create an account.
|
||||||
|
|
||||||
|
## 2. Download JRebel
|
||||||
|
|
||||||
|
Download the most recent ["nosetup" JRebel zip](http://zeroturnaround.com/software/jrebel/download/prev-releases/).
|
||||||
|
Next, unzip the downloaded file.
|
||||||
|
|
||||||
|
## 3. Activate
|
||||||
|
|
||||||
|
Follow the [instructions on the JRebel website](http://zeroturnaround.com/software/jrebel/download/prev-releases/) to activate your downloaded JRebel.
|
||||||
|
|
||||||
|
You can use the default settings for all the configurations.
|
||||||
|
|
||||||
|
You don't need to integrate with your IDE, since we're using sbt to do the servlet deployment.
|
||||||
|
|
||||||
|
## 4. Tell jvm where JRebel is
|
||||||
|
|
||||||
|
Fortunately, the gitbucket project is already set up to use JRebel.
|
||||||
|
You only need to tell jvm where to find the jrebel jar.
|
||||||
|
|
||||||
|
To do so, edit your shell resource file (usually `~/.bash_profile` on Mac, and `~/.bashrc` on Linux), and add the following line:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export JREBEL=/path/to/jrebel/jrebel.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
For example, if you unzipped your JRebel download in your home directory, you whould use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export JREBEL=~/jrebel/jrebel.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
Now reload your shell:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ source ~/.bash_profile # on Mac
|
||||||
|
$ source ~/.bashrc # on Linux
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. See it in action!
|
||||||
|
|
||||||
|
Now you're ready to use JRebel with the gitbucket.
|
||||||
|
When you run sbt as normal, you will see a long message from JRebel, indicating it has loaded.
|
||||||
|
Here's an abbreviated version of what you will see:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./sbt
|
||||||
|
[info] Loading project definition from /git/gitbucket/project
|
||||||
|
[info] Set current project to gitbucket (in build file:/git/gitbucket/)
|
||||||
|
>
|
||||||
|
```
|
||||||
|
|
||||||
|
You will start the servlet container slightly differently now that you're using sbt.
|
||||||
|
|
||||||
|
```
|
||||||
|
> jetty:start
|
||||||
|
:
|
||||||
|
[info] starting server ...
|
||||||
|
[success] Total time: 3 s, completed Jan 3, 2016 9:47:55 PM
|
||||||
|
2016-01-03 21:47:57 JRebel:
|
||||||
|
2016-01-03 21:47:57 JRebel: A newer version '6.3.1' is available for download
|
||||||
|
2016-01-03 21:47:57 JRebel: from http://zeroturnaround.com/software/jrebel/download/
|
||||||
|
2016-01-03 21:47:57 JRebel:
|
||||||
|
2016-01-03 21:47:58 JRebel: Contacting myJRebel server ..
|
||||||
|
2016-01-03 21:47:59 JRebel: Directory '/git/gitbucket/target/scala-2.11/classes' will be monitored for changes.
|
||||||
|
2016-01-03 21:47:59 JRebel: Directory '/git/gitbucket/target/scala-2.11/test-classes' will be monitored for changes.
|
||||||
|
2016-01-03 21:47:59 JRebel: Directory '/git/gitbucket/target/webapp' will be monitored for changes.
|
||||||
|
2016-01-03 21:48:00 JRebel:
|
||||||
|
2016-01-03 21:48:00 JRebel: #############################################################
|
||||||
|
2016-01-03 21:48:00 JRebel:
|
||||||
|
2016-01-03 21:48:00 JRebel: JRebel Legacy Agent 6.2.5 (201509291538)
|
||||||
|
2016-01-03 21:48:00 JRebel: (c) Copyright ZeroTurnaround AS, Estonia, Tartu.
|
||||||
|
2016-01-03 21:48:00 JRebel:
|
||||||
|
2016-01-03 21:48:00 JRebel: Over the last 30 days JRebel prevented
|
||||||
|
2016-01-03 21:48:00 JRebel: at least 182 redeploys/restarts saving you about 7.4 hours.
|
||||||
|
2016-01-03 21:48:00 JRebel:
|
||||||
|
2016-01-03 21:48:00 JRebel: Over the last 324 days JRebel prevented
|
||||||
|
2016-01-03 21:48:00 JRebel: at least 1538 redeploys/restarts saving you about 62.4 hours.
|
||||||
|
2016-01-03 21:48:00 JRebel:
|
||||||
|
2016-01-03 21:48:00 JRebel: Licensed to nazo king (using myJRebel).
|
||||||
|
2016-01-03 21:48:00 JRebel:
|
||||||
|
2016-01-03 21:48:00 JRebel:
|
||||||
|
2016-01-03 21:48:00 JRebel: #############################################################
|
||||||
|
2016-01-03 21:48:00 JRebel:
|
||||||
|
:
|
||||||
|
|
||||||
|
> ~ copy-resources
|
||||||
|
[success] Total time: 0 s, completed Jan 3, 2016 9:13:54 PM
|
||||||
|
1. Waiting for source changes... (press enter to interrupt)
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, change your code.
|
||||||
|
For example, you can change the title on `src/main/twirl/gitbucket/core/main.scala.html` like this:
|
||||||
|
|
||||||
|
```html
|
||||||
|
:
|
||||||
|
<a class="navbar-brand" href="@path/">
|
||||||
|
<img src="@assets/common/images/gitbucket.png" style="width: 24px; height: 24px;"/>GitBucket
|
||||||
|
@defining(AutoUpdate.getCurrentVersion){ version =>
|
||||||
|
<span class="header-version">@version.majorVersion.@version.minorVersion</span>
|
||||||
|
}
|
||||||
|
change code !!!!!!!!!!!!!!!!
|
||||||
|
</a>
|
||||||
|
:
|
||||||
|
```
|
||||||
|
|
||||||
|
If JRebel is doing is correctly installed you will see a notice for you:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Waiting for source changes... (press enter to interrupt)
|
||||||
|
2016-01-03 21:48:42 JRebel: Reloading class 'gitbucket.core.html.main$'.
|
||||||
|
[info] Wrote rebel.xml to /git/gitbucket/target/scala-2.11/resource_managed/main/rebel.xml
|
||||||
|
[info] Compiling 1 Scala source to /git/gitbucket/target/scala-2.11/classes...
|
||||||
|
[success] Total time: 3 s, completed Jan 3, 2016 9:48:55 PM
|
||||||
|
2. Waiting for source changes... (press enter to interrupt)
|
||||||
|
```
|
||||||
|
|
||||||
|
And you reload browser, JRebel give notice of that it has reloaded classes:
|
||||||
|
|
||||||
|
```
|
||||||
|
[success] Total time: 3 s, completed Jan 3, 2016 9:48:55 PM
|
||||||
|
2. Waiting for source changes... (press enter to interrupt)
|
||||||
|
2016-01-03 21:49:13 JRebel: Reloading class 'gitbucket.core.html.main$'.
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Limitations
|
||||||
|
|
||||||
|
JRebel is nearly always able to eliminate the need to explicitly reload your container after a code change. However, if you change any of your routes patterns, there is nothing JRebel can do, you will have to run `jetty:start`.
|
||||||
@@ -9,3 +9,4 @@ Developer's Guide
|
|||||||
* [Notification Email](notification.md)
|
* [Notification Email](notification.md)
|
||||||
* [Automatic Schema Updating](auto_update.md)
|
* [Automatic Schema Updating](auto_update.md)
|
||||||
* [Release Operation](release.md)
|
* [Release Operation](release.md)
|
||||||
|
* [JRebel integration (optional)](jrebel.md)
|
||||||
|
|||||||
@@ -6,28 +6,29 @@ Update version number
|
|||||||
|
|
||||||
Note to update version number in files below:
|
Note to update version number in files below:
|
||||||
|
|
||||||
### project/build.scala
|
### build.sbt
|
||||||
|
|
||||||
```scala
|
```scala
|
||||||
object MyBuild extends Build {
|
val Organization = "gitbucket"
|
||||||
val Organization = "gitbucket"
|
val Name = "gitbucket"
|
||||||
val Name = "gitbucket"
|
val GitBucketVersion = "4.0.0" // <---- update version!!
|
||||||
val Version = "3.3.0" // <---- update version!!
|
val ScalatraVersion = "2.4.0"
|
||||||
val ScalaVersion = "2.11.6"
|
val JettyVersion = "9.3.6.v20151106"
|
||||||
val ScalatraVersion = "2.3.1"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 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",
|
||||||
|
// add new version definition
|
||||||
/**
|
new Version("4.1.0",
|
||||||
* The history of versions. A head of this sequence is the current BitBucket version.
|
new LiquibaseMigration("update/gitbucket-core_4.1.xml")
|
||||||
*/
|
),
|
||||||
val versions = Seq(
|
new Version("4.0.0",
|
||||||
new Version(3, 3), // <---- add this line!!
|
new LiquibaseMigration("update/gitbucket-core_4.0.xml"),
|
||||||
new Version(3, 2),
|
new SqlMigration("update/gitbucket-core_4.0.sql")
|
||||||
|
)
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
Generate release files
|
Generate release files
|
||||||
@@ -37,11 +38,10 @@ Note: Release operation requires [Ant](http://ant.apache.org/) and [Maven](https
|
|||||||
|
|
||||||
### Make release war file
|
### Make release war file
|
||||||
|
|
||||||
Run `release/make-release-war.sh`. The release war file is generated into `target/scala-2.11/gitbucket.war`.
|
Run `sbt executable`. The release war file and fingerprint are generated into `target/executable/gitbucket.war`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ cd release
|
$sbt executable
|
||||||
$ ./make-release-war.sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Deploy assembly jar file
|
### Deploy assembly jar file
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,11 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
version=$1
|
|
||||||
output_dir=`dirname $0`
|
|
||||||
git rm -f ${output_dir}/jetty-*.jar
|
|
||||||
for name in 'io' 'servlet' 'xml' 'continuation' 'security' 'util' 'http' 'server' 'webapp'
|
|
||||||
do
|
|
||||||
jar_filename="jetty-${name}-${version}.jar"
|
|
||||||
wget "http://repo1.maven.org/maven2/org/eclipse/jetty/jetty-${name}/${version}/${jar_filename}" -O ${output_dir}/${jar_filename}
|
|
||||||
done
|
|
||||||
git add ${output_dir}/*.jar
|
|
||||||
git commit
|
|
||||||
34
project/Checksums.scala
Normal file
34
project/Checksums.scala
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import java.security.MessageDigest;
|
||||||
|
import scala.annotation._
|
||||||
|
import sbt._
|
||||||
|
import sbt.Using._
|
||||||
|
|
||||||
|
object Checksums {
|
||||||
|
private val bufferSize = 2048
|
||||||
|
|
||||||
|
def generate(source:File, target:File, algorithm:String):Unit =
|
||||||
|
IO write (target, compute(source, algorithm))
|
||||||
|
|
||||||
|
def compute(file:File, algorithm:String):String =
|
||||||
|
hex(raw(file, algorithm))
|
||||||
|
|
||||||
|
def raw(file:File, algorithm:String):Array[Byte] =
|
||||||
|
(Using fileInputStream file) { is =>
|
||||||
|
val md = MessageDigest getInstance algorithm
|
||||||
|
val buf = new Array[Byte](bufferSize)
|
||||||
|
md.reset()
|
||||||
|
@tailrec
|
||||||
|
def loop() {
|
||||||
|
val len = is read buf
|
||||||
|
if (len != -1) {
|
||||||
|
md update (buf, 0, len)
|
||||||
|
loop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loop()
|
||||||
|
md.digest()
|
||||||
|
}
|
||||||
|
|
||||||
|
def hex(bytes:Array[Byte]):String =
|
||||||
|
bytes map { it => "%02x" format (it.toInt & 0xff) } mkString ""
|
||||||
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
sbt.version=0.13.8
|
sbt.version=0.13.9
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
import com.earldouglas.xwp.JettyPlugin
|
|
||||||
import play.twirl.sbt.SbtTwirl
|
|
||||||
import sbt.Keys._
|
|
||||||
import sbt._
|
|
||||||
import sbtassembly.AssemblyKeys._
|
|
||||||
import sbtassembly._
|
|
||||||
import JettyPlugin.autoImport._
|
|
||||||
|
|
||||||
object MyBuild extends Build {
|
|
||||||
val Organization = "gitbucket"
|
|
||||||
val Name = "gitbucket"
|
|
||||||
val Version = "3.10.0"
|
|
||||||
val ScalaVersion = "2.11.6"
|
|
||||||
val ScalatraVersion = "2.3.1"
|
|
||||||
|
|
||||||
lazy val project = Project (
|
|
||||||
"gitbucket",
|
|
||||||
file(".")
|
|
||||||
)
|
|
||||||
// .settings(ScalatraPlugin.scalatraWithJRebel: _*)
|
|
||||||
.settings(
|
|
||||||
test in assembly := {},
|
|
||||||
assemblyMergeStrategy in assembly := {
|
|
||||||
case PathList("META-INF", xs @ _*) =>
|
|
||||||
(xs map {_.toLowerCase}) match {
|
|
||||||
case ("manifest.mf" :: Nil) => MergeStrategy.discard
|
|
||||||
case _ => MergeStrategy.discard
|
|
||||||
}
|
|
||||||
case x => MergeStrategy.first
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.settings(
|
|
||||||
sourcesInBase := false,
|
|
||||||
organization := Organization,
|
|
||||||
name := Name,
|
|
||||||
version := Version,
|
|
||||||
scalaVersion := ScalaVersion,
|
|
||||||
resolvers ++= Seq(
|
|
||||||
Classpaths.typesafeReleases,
|
|
||||||
"amateras-repo" at "http://amateras.sourceforge.jp/mvn/",
|
|
||||||
"amateras-snapshot-repo" at "http://amateras.sourceforge.jp/mvn-snapshot/"
|
|
||||||
),
|
|
||||||
scalacOptions := Seq("-deprecation", "-language:postfixOps"),
|
|
||||||
libraryDependencies ++= Seq(
|
|
||||||
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "3.4.2.201412180340-r",
|
|
||||||
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "3.4.2.201412180340-r",
|
|
||||||
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
|
||||||
"org.scalatra" %% "scalatra-specs2" % ScalatraVersion % "test",
|
|
||||||
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
|
||||||
"org.json4s" %% "json4s-jackson" % "3.2.11",
|
|
||||||
"jp.sf.amateras" %% "scalatra-forms" % "0.2.0",
|
|
||||||
"commons-io" % "commons-io" % "2.4",
|
|
||||||
"io.github.gitbucket" % "markedj" % "1.0.6-SNAPSHOT",
|
|
||||||
"org.apache.commons" % "commons-compress" % "1.9",
|
|
||||||
"org.apache.commons" % "commons-email" % "1.3.3",
|
|
||||||
"org.apache.httpcomponents" % "httpclient" % "4.3.6",
|
|
||||||
"org.apache.sshd" % "apache-sshd" % "1.0.0",
|
|
||||||
"org.apache.tika" % "tika-core" % "1.10",
|
|
||||||
"com.typesafe.slick" %% "slick" % "2.1.0",
|
|
||||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
|
||||||
"com.h2database" % "h2" % "1.4.190",
|
|
||||||
"ch.qos.logback" % "logback-classic" % "1.1.1",
|
|
||||||
"org.eclipse.jetty" % "jetty-webapp" % "8.1.16.v20140903" % "provided",
|
|
||||||
"javax.servlet" % "javax.servlet-api" % "3.0.1" % "provided",
|
|
||||||
"junit" % "junit" % "4.12" % "test",
|
|
||||||
"com.mchange" % "c3p0" % "0.9.5.2",
|
|
||||||
"com.typesafe" % "config" % "1.2.1",
|
|
||||||
"com.typesafe.akka" %% "akka-actor" % "2.3.10",
|
|
||||||
"com.enragedginger" %% "akka-quartz-scheduler" % "1.3.0-akka-2.3.x" exclude("c3p0","c3p0")
|
|
||||||
),
|
|
||||||
play.twirl.sbt.Import.TwirlKeys.templateImports += "gitbucket.core._",
|
|
||||||
javacOptions in compile ++= Seq("-target", "7", "-source", "7"),
|
|
||||||
javaOptions in Jetty += "-Dlogback.configurationFile=/logback-dev.xml",
|
|
||||||
testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "junitxml", "console"),
|
|
||||||
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() ),
|
|
||||||
fork in Test := true,
|
|
||||||
packageOptions += Package.MainClass("JettyLauncher")
|
|
||||||
).enablePlugins(SbtTwirl, JettyPlugin)
|
|
||||||
}
|
|
||||||
@@ -2,4 +2,5 @@ scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
|||||||
|
|
||||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.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")
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<project name="gitbucket" default="all" basedir="..">
|
|
||||||
|
|
||||||
<property environment="env"/>
|
|
||||||
<property name="target.dir" value="target"/>
|
|
||||||
<property name="embed.classes.dir" value="${target.dir}/embed-classes"/>
|
|
||||||
<property name="jetty.dir" value="embed-jetty"/>
|
|
||||||
<property name="scala.version" value="2.11"/>
|
|
||||||
<property name="gitbucket.version" value="${env.GITBUCKET_VERSION}"/>
|
|
||||||
<property name="jetty.version" value="8.1.16.v20140903"/>
|
|
||||||
<property name="servlet.version" value="3.0.0.v201112011016"/>
|
|
||||||
|
|
||||||
<condition property="sbt.exec" value="sbt.bat" else="sbt.sh">
|
|
||||||
<os family="windows" />
|
|
||||||
</condition>
|
|
||||||
|
|
||||||
<target name="clean">
|
|
||||||
<delete dir="${embed.classes.dir}"/>
|
|
||||||
<delete file="${target.dir}/scala-${scala.version}/gitbucket.war"/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="war" depends="clean">
|
|
||||||
<exec executable="${sbt.exec}" resolveexecutable="true" failonerror="true">
|
|
||||||
<arg line="clean compile test package" />
|
|
||||||
</exec>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="embed" depends="war">
|
|
||||||
<mkdir dir="${embed.classes.dir}"/>
|
|
||||||
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/javax.servlet-${servlet.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-continuation-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-http-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-io-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-security-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-server-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-servlet-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-util-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-webapp-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-xml-${jetty.version}.jar" />
|
|
||||||
|
|
||||||
<zip destfile="${target.dir}/scala-${scala.version}/gitbucket_${scala.version}-${gitbucket.version}.war"
|
|
||||||
basedir="${embed.classes.dir}"
|
|
||||||
update = "true"
|
|
||||||
includes="javax/**,org/**"/>
|
|
||||||
|
|
||||||
<zip destfile="${target.dir}/scala-${scala.version}/gitbucket_${scala.version}-${gitbucket.version}.war"
|
|
||||||
basedir="${target.dir}/scala-${scala.version}/classes"
|
|
||||||
update = "true"
|
|
||||||
includes="JettyLauncher.class,HttpsSupportConnector.class"/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="rename" depends="embed">
|
|
||||||
<move file="${target.dir}/scala-${scala.version}/gitbucket_${scala.version}-${gitbucket.version}.war"
|
|
||||||
tofile="${target.dir}/scala-${scala.version}/gitbucket.war"/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="checksum" depends="rename">
|
|
||||||
<checksum file="${target.dir}/scala-${scala.version}/gitbucket.war" algorithm="MD5" format="MD5SUM" forceOverwrite="yes" fileext=".md5"/>
|
|
||||||
<checksum file="${target.dir}/scala-${scala.version}/gitbucket.war" algorithm="SHA" format="MD5SUM" forceOverwrite="yes" fileext=".sha1"/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="all" depends="checksum">
|
|
||||||
</target>
|
|
||||||
|
|
||||||
|
|
||||||
</project>
|
|
||||||
@@ -5,6 +5,15 @@ cd ../
|
|||||||
./sbt.sh clean assembly
|
./sbt.sh clean assembly
|
||||||
|
|
||||||
cd release
|
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 \
|
mvn deploy:deploy-file \
|
||||||
-DgroupId=gitbucket\
|
-DgroupId=gitbucket\
|
||||||
-DartifactId=gitbucket-assembly\
|
-DartifactId=gitbucket-assembly\
|
||||||
@@ -12,4 +21,4 @@ mvn deploy:deploy-file \
|
|||||||
-Dpackaging=jar\
|
-Dpackaging=jar\
|
||||||
-Dfile=../target/scala-2.11/gitbucket-assembly-$GITBUCKET_VERSION.jar\
|
-Dfile=../target/scala-2.11/gitbucket-assembly-$GITBUCKET_VERSION.jar\
|
||||||
-DrepositoryId=sourceforge.jp\
|
-DrepositoryId=sourceforge.jp\
|
||||||
-Durl=scp://shell.sourceforge.jp/home/groups/a/am/amateras/htdocs/mvn/
|
-Durl=scp://shell.sourceforge.jp/home/groups/a/am/amateras/htdocs/$MVN_DEPLOY_PATH/
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
export GITBUCKET_VERSION=`cat ../project/build.scala | grep 'val Version' | cut -d \" -f 2`
|
export GITBUCKET_VERSION=`cat ../build.sbt | grep 'val GitBucketVersion' | cut -d \" -f 2`
|
||||||
echo "GITBUCKET_VERSION: $GITBUCKET_VERSION"
|
echo "GITBUCKET_VERSION: $GITBUCKET_VERSION"
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
D="$(dirname "$0")"
|
|
||||||
D="$(cd "${D}"; pwd)"
|
|
||||||
DD="$(dirname "${D}")"
|
|
||||||
(
|
|
||||||
for f in "${D}/env.sh" "${D}/build.xml"; do
|
|
||||||
if [ ! -s "${f}" ]; then
|
|
||||||
echo >&2 "$0: Unable to access file '${f}'"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
. "${D}/env.sh"
|
|
||||||
cd "${DD}"
|
|
||||||
ant -f "${D}/build.xml" all
|
|
||||||
)
|
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<extension>
|
<extension>
|
||||||
<groupId>org.apache.maven.wagon</groupId>
|
<groupId>org.apache.maven.wagon</groupId>
|
||||||
<artifactId>wagon-ssh</artifactId>
|
<artifactId>wagon-ssh</artifactId>
|
||||||
<version>1.0-beta-6</version>
|
<version>2.10</version>
|
||||||
</extension>
|
</extension>
|
||||||
</extensions>
|
</extensions>
|
||||||
</build>
|
</build>
|
||||||
|
|||||||
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.8.jar" %*
|
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" %*
|
||||||
|
|||||||
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.8.jar "$@"
|
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 "$@"
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.nio.SelectChannelConnector;
|
|
||||||
import org.eclipse.jetty.webapp.WebAppContext;
|
import org.eclipse.jetty.webapp.WebAppContext;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -30,16 +29,16 @@ public class JettyLauncher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Server server = new Server();
|
Server server = new Server(port);
|
||||||
|
|
||||||
SelectChannelConnector connector = new SelectChannelConnector();
|
// SelectChannelConnector connector = new SelectChannelConnector();
|
||||||
if(host != null) {
|
// if(host != null) {
|
||||||
connector.setHost(host);
|
// connector.setHost(host);
|
||||||
}
|
// }
|
||||||
connector.setMaxIdleTime(1000 * 60 * 60);
|
// connector.setMaxIdleTime(1000 * 60 * 60);
|
||||||
connector.setSoLingerTime(-1);
|
// connector.setSoLingerTime(-1);
|
||||||
connector.setPort(port);
|
// connector.setPort(port);
|
||||||
server.addConnector(connector);
|
// server.addConnector(connector);
|
||||||
|
|
||||||
WebAppContext context = new WebAppContext();
|
WebAppContext context = new WebAppContext();
|
||||||
|
|
||||||
|
|||||||
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"
|
|
||||||
}
|
|
||||||
@@ -6,12 +6,14 @@
|
|||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
|
<!--
|
||||||
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
||||||
<file>gitbucket.log</file>
|
<file>gitbucket.log</file>
|
||||||
<encoder>
|
<encoder>
|
||||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
-->
|
||||||
|
|
||||||
<root level="INFO">
|
<root level="INFO">
|
||||||
<appender-ref ref="STDOUT" />
|
<appender-ref ref="STDOUT" />
|
||||||
|
|||||||
@@ -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,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>
|
||||||
@@ -27,12 +27,10 @@ class ScalatraBootstrap extends LifeCycle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
context.mount(new IndexController, "/")
|
context.mount(new IndexController, "/")
|
||||||
context.mount(new SearchController, "/")
|
context.mount(new ApiController, "/api/v3")
|
||||||
context.mount(new FileUploadController, "/upload")
|
context.mount(new FileUploadController, "/upload")
|
||||||
|
context.mount(new SystemSettingsController, "/admin")
|
||||||
context.mount(new DashboardController, "/*")
|
context.mount(new DashboardController, "/*")
|
||||||
context.mount(new UserManagementController, "/*")
|
|
||||||
context.mount(new SystemSettingsController, "/*")
|
|
||||||
context.mount(new PluginsController, "/*")
|
|
||||||
context.mount(new AccountController, "/*")
|
context.mount(new AccountController, "/*")
|
||||||
context.mount(new RepositoryViewerController, "/*")
|
context.mount(new RepositoryViewerController, "/*")
|
||||||
context.mount(new WikiController, "/*")
|
context.mount(new WikiController, "/*")
|
||||||
|
|||||||
11
src/main/scala/gitbucket/core/GitBucketCoreModule.scala
Normal file
11
src/main/scala/gitbucket/core/GitBucketCoreModule.scala
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
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")
|
||||||
|
)
|
||||||
|
)
|
||||||
16
src/main/scala/gitbucket/core/api/ApiBranch.scala
Normal file
16
src/main/scala/gitbucket/core/api/ApiBranch.scala
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
import gitbucket.core.util.RepositoryName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/#get-branch
|
||||||
|
* https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection
|
||||||
|
*/
|
||||||
|
case class ApiBranch(
|
||||||
|
name: String,
|
||||||
|
// commit: ApiBranchCommit,
|
||||||
|
protection: ApiBranchProtection)(repositoryName:RepositoryName) extends FieldSerializable {
|
||||||
|
def _links = Map(
|
||||||
|
"self" -> ApiPath(s"/api/v3/repos/${repositoryName.fullName}/branches/${name}"),
|
||||||
|
"html" -> ApiPath(s"/${repositoryName.fullName}/tree/${name}"))
|
||||||
|
}
|
||||||
47
src/main/scala/gitbucket/core/api/ApiBranchProtection.scala
Normal file
47
src/main/scala/gitbucket/core/api/ApiBranchProtection.scala
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
import gitbucket.core.service.ProtectedBranchService
|
||||||
|
import org.json4s._
|
||||||
|
|
||||||
|
/** https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */
|
||||||
|
case class ApiBranchProtection(enabled: Boolean, required_status_checks: Option[ApiBranchProtection.Status]){
|
||||||
|
def status: ApiBranchProtection.Status = required_status_checks.getOrElse(ApiBranchProtection.statusNone)
|
||||||
|
}
|
||||||
|
|
||||||
|
object ApiBranchProtection{
|
||||||
|
/** form for enabling-and-disabling-branch-protection */
|
||||||
|
case class EnablingAndDisabling(protection: ApiBranchProtection)
|
||||||
|
|
||||||
|
def apply(info: ProtectedBranchService.ProtectedBranchInfo): ApiBranchProtection = ApiBranchProtection(
|
||||||
|
enabled = info.enabled,
|
||||||
|
required_status_checks = Some(Status(EnforcementLevel(info.enabled, info.includeAdministrators), info.contexts)))
|
||||||
|
val statusNone = Status(Off, Seq.empty)
|
||||||
|
case class Status(enforcement_level: EnforcementLevel, contexts: Seq[String])
|
||||||
|
sealed class EnforcementLevel(val name: String)
|
||||||
|
case object Off extends EnforcementLevel("off")
|
||||||
|
case object NonAdmins extends EnforcementLevel("non_admins")
|
||||||
|
case object Everyone extends EnforcementLevel("everyone")
|
||||||
|
object EnforcementLevel {
|
||||||
|
def apply(enabled: Boolean, includeAdministrators: Boolean): EnforcementLevel = if(enabled){
|
||||||
|
if(includeAdministrators){
|
||||||
|
Everyone
|
||||||
|
}else{
|
||||||
|
NonAdmins
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
Off
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit val enforcementLevelSerializer = new CustomSerializer[EnforcementLevel](format => (
|
||||||
|
{
|
||||||
|
case JString("off") => Off
|
||||||
|
case JString("non_admins") => NonAdmins
|
||||||
|
case JString("everyone") => Everyone
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case x: EnforcementLevel => JString(x.name)
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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{
|
||||||
|
|||||||
21
src/main/scala/gitbucket/core/api/ApiLabel.scala
Normal file
21
src/main/scala/gitbucket/core/api/ApiLabel.scala
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
import gitbucket.core.model.Label
|
||||||
|
import gitbucket.core.util.RepositoryName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/issues/labels/
|
||||||
|
*/
|
||||||
|
case class ApiLabel(
|
||||||
|
name: String,
|
||||||
|
color: String)(repositoryName: RepositoryName){
|
||||||
|
var url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/labels/${name}")
|
||||||
|
}
|
||||||
|
|
||||||
|
object ApiLabel{
|
||||||
|
def apply(label:Label, repositoryName: RepositoryName): ApiLabel =
|
||||||
|
ApiLabel(
|
||||||
|
name = label.labelName,
|
||||||
|
color = label.color
|
||||||
|
)(repositoryName)
|
||||||
|
}
|
||||||
18
src/main/scala/gitbucket/core/api/CreateALabel.scala
Normal file
18
src/main/scala/gitbucket/core/api/CreateALabel.scala
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/issues/labels/#create-a-label
|
||||||
|
* api form
|
||||||
|
*/
|
||||||
|
case class CreateALabel(
|
||||||
|
name: String,
|
||||||
|
color: String
|
||||||
|
) {
|
||||||
|
def isValid: Boolean = {
|
||||||
|
name.length<=100 &&
|
||||||
|
!name.startsWith("_") &&
|
||||||
|
!name.startsWith("-") &&
|
||||||
|
color.length==6 &&
|
||||||
|
color.matches("[a-fA-F0-9+_.]+")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,7 +30,9 @@ object JsonFormat {
|
|||||||
FieldSerializer[ApiCombinedCommitStatus]() +
|
FieldSerializer[ApiCombinedCommitStatus]() +
|
||||||
FieldSerializer[ApiPullRequest.Commit]() +
|
FieldSerializer[ApiPullRequest.Commit]() +
|
||||||
FieldSerializer[ApiIssue]() +
|
FieldSerializer[ApiIssue]() +
|
||||||
FieldSerializer[ApiComment]()
|
FieldSerializer[ApiComment]() +
|
||||||
|
FieldSerializer[ApiLabel]() +
|
||||||
|
ApiBranchProtection.enforcementLevelSerializer
|
||||||
|
|
||||||
def apiPathSerializer(c: Context) = new CustomSerializer[ApiPath](format =>
|
def apiPathSerializer(c: Context) = new CustomSerializer[ApiPath](format =>
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
import gitbucket.core.account.html
|
import gitbucket.core.account.html
|
||||||
import gitbucket.core.api._
|
|
||||||
import gitbucket.core.helper
|
import gitbucket.core.helper
|
||||||
import gitbucket.core.model.GroupMember
|
import gitbucket.core.model.GroupMember
|
||||||
import gitbucket.core.service._
|
import gitbucket.core.service._
|
||||||
@@ -12,24 +11,21 @@ import gitbucket.core.util.Implicits._
|
|||||||
import gitbucket.core.util.StringUtil._
|
import gitbucket.core.util.StringUtil._
|
||||||
import gitbucket.core.util._
|
import gitbucket.core.util._
|
||||||
|
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import io.github.gitbucket.scalatra.forms._
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import org.eclipse.jgit.api.Git
|
|
||||||
import org.eclipse.jgit.dircache.DirCache
|
|
||||||
import org.eclipse.jgit.lib.{FileMode, Constants}
|
|
||||||
import org.scalatra.i18n.Messages
|
import org.scalatra.i18n.Messages
|
||||||
|
|
||||||
|
|
||||||
class AccountController extends AccountControllerBase
|
class AccountController extends AccountControllerBase
|
||||||
with AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
|
with AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
|
||||||
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
|
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
|
||||||
with AccessTokenService with WebHookService
|
with AccessTokenService with WebHookService with RepositoryCreationService
|
||||||
|
|
||||||
|
|
||||||
trait AccountControllerBase extends AccountManagementControllerBase {
|
trait AccountControllerBase extends AccountManagementControllerBase {
|
||||||
self: AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
|
self: AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
|
||||||
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
|
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
|
||||||
with AccessTokenService with WebHookService =>
|
with AccessTokenService with WebHookService with RepositoryCreationService =>
|
||||||
|
|
||||||
case class AccountNewForm(userName: String, password: String, fullName: String, mailAddress: String,
|
case class AccountNewForm(userName: String, password: String, fullName: String, mailAddress: String,
|
||||||
url: Option[String], fileId: Option[String])
|
url: Option[String], fileId: Option[String])
|
||||||
@@ -133,7 +129,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
val members = getGroupMembers(account.userName)
|
val members = getGroupMembers(account.userName)
|
||||||
gitbucket.core.account.html.repositories(account,
|
gitbucket.core.account.html.repositories(account,
|
||||||
if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
||||||
getVisibleRepositories(context.loginAccount, context.baseUrl, Some(userName)),
|
getVisibleRepositories(context.loginAccount, Some(userName)),
|
||||||
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }))
|
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,25 +152,6 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/users/#get-a-single-user
|
|
||||||
*/
|
|
||||||
get("/api/v3/users/:userName") {
|
|
||||||
getAccountByUserName(params("userName")).map { account =>
|
|
||||||
JsonFormat(ApiUser(account))
|
|
||||||
} getOrElse NotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/users/#get-the-authenticated-user
|
|
||||||
*/
|
|
||||||
get("/api/v3/user") {
|
|
||||||
context.loginAccount.map { account =>
|
|
||||||
JsonFormat(ApiUser(account))
|
|
||||||
} getOrElse Unauthorized
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
get("/:userName/_edit")(oneselfOnly {
|
get("/:userName/_edit")(oneselfOnly {
|
||||||
val userName = params("userName")
|
val userName = params("userName")
|
||||||
getAccountByUserName(userName).map { x =>
|
getAccountByUserName(userName).map { x =>
|
||||||
@@ -366,8 +343,8 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
*/
|
*/
|
||||||
post("/new", newRepositoryForm)(usersOnly { form =>
|
post("/new", newRepositoryForm)(usersOnly { form =>
|
||||||
LockUtil.lock(s"${form.owner}/${form.name}"){
|
LockUtil.lock(s"${form.owner}/${form.name}"){
|
||||||
if(getRepository(form.owner, form.name, context.baseUrl).isEmpty){
|
if(getRepository(form.owner, form.name).isEmpty){
|
||||||
createRepository(form.owner, form.name, form.description, form.isPrivate, form.createReadme)
|
createRepository(context.loginAccount.get, form.owner, form.name, form.description, form.isPrivate, form.createReadme)
|
||||||
}
|
}
|
||||||
|
|
||||||
// redirect to the repository
|
// redirect to the repository
|
||||||
@@ -375,54 +352,6 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* Create user repository
|
|
||||||
* https://developer.github.com/v3/repos/#create
|
|
||||||
*/
|
|
||||||
post("/api/v3/user/repos")(usersOnly {
|
|
||||||
val owner = context.loginAccount.get.userName
|
|
||||||
(for {
|
|
||||||
data <- extractFromJsonBody[CreateARepository] if data.isValid
|
|
||||||
} yield {
|
|
||||||
LockUtil.lock(s"${owner}/${data.name}") {
|
|
||||||
if(getRepository(owner, data.name, context.baseUrl).isEmpty){
|
|
||||||
createRepository(owner, data.name, data.description, data.`private`, data.auto_init)
|
|
||||||
val repository = getRepository(owner, data.name, context.baseUrl).get
|
|
||||||
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(owner).get)))
|
|
||||||
} else {
|
|
||||||
ApiError(
|
|
||||||
"A repository with this name already exists on this account",
|
|
||||||
Some("https://developer.github.com/v3/repos/#create")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create group repository
|
|
||||||
* https://developer.github.com/v3/repos/#create
|
|
||||||
*/
|
|
||||||
post("/api/v3/orgs/:org/repos")(managersOnly {
|
|
||||||
val groupName = params("org")
|
|
||||||
(for {
|
|
||||||
data <- extractFromJsonBody[CreateARepository] if data.isValid
|
|
||||||
} yield {
|
|
||||||
LockUtil.lock(s"${groupName}/${data.name}") {
|
|
||||||
if(getRepository(groupName, data.name, context.baseUrl).isEmpty){
|
|
||||||
createRepository(groupName, data.name, data.description, data.`private`, data.auto_init)
|
|
||||||
val repository = getRepository(groupName, data.name, context.baseUrl).get
|
|
||||||
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(groupName).get)))
|
|
||||||
} else {
|
|
||||||
ApiError(
|
|
||||||
"A repository with this name already exists for this group",
|
|
||||||
Some("https://developer.github.com/v3/repos/#create")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/fork")(readableUsersOnly { repository =>
|
get("/:owner/:repository/fork")(readableUsersOnly { repository =>
|
||||||
val loginAccount = context.loginAccount.get
|
val loginAccount = context.loginAccount.get
|
||||||
val loginUserName = loginAccount.userName
|
val loginUserName = loginAccount.userName
|
||||||
@@ -447,7 +376,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
val accountName = form.accountName
|
val accountName = form.accountName
|
||||||
|
|
||||||
LockUtil.lock(s"${accountName}/${repository.name}"){
|
LockUtil.lock(s"${accountName}/${repository.name}"){
|
||||||
if(getRepository(accountName, repository.name, baseUrl).isDefined ||
|
if(getRepository(accountName, repository.name).isDefined ||
|
||||||
(accountName != loginUserName && !getGroupsByUserName(loginUserName).contains(accountName))){
|
(accountName != loginUserName && !getGroupsByUserName(loginUserName).contains(accountName))){
|
||||||
// redirect to the repository if repository already exists
|
// redirect to the repository if repository already exists
|
||||||
redirect(s"/${accountName}/${repository.name}")
|
redirect(s"/${accountName}/${repository.name}")
|
||||||
@@ -456,7 +385,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
val originUserName = repository.repository.originUserName.getOrElse(repository.owner)
|
val originUserName = repository.repository.originUserName.getOrElse(repository.owner)
|
||||||
val originRepositoryName = repository.repository.originRepositoryName.getOrElse(repository.name)
|
val originRepositoryName = repository.repository.originRepositoryName.getOrElse(repository.name)
|
||||||
|
|
||||||
createRepository(
|
insertRepository(
|
||||||
repositoryName = repository.name,
|
repositoryName = repository.name,
|
||||||
userName = accountName,
|
userName = accountName,
|
||||||
description = repository.repository.description,
|
description = repository.repository.description,
|
||||||
@@ -496,68 +425,6 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
private def createRepository(owner: String, name: String, description: Option[String], isPrivate: Boolean, createReadme: Boolean) {
|
|
||||||
val ownerAccount = getAccountByUserName(owner).get
|
|
||||||
val loginAccount = context.loginAccount.get
|
|
||||||
val loginUserName = loginAccount.userName
|
|
||||||
|
|
||||||
// Insert to the database at first
|
|
||||||
createRepository(name, owner, description, isPrivate)
|
|
||||||
|
|
||||||
// Add collaborators for group repository
|
|
||||||
if(ownerAccount.isGroupAccount){
|
|
||||||
getGroupMembers(owner).foreach { member =>
|
|
||||||
addCollaborator(owner, name, member.userName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert default labels
|
|
||||||
insertDefaultLabels(owner, name)
|
|
||||||
|
|
||||||
// Create the actual repository
|
|
||||||
val gitdir = getRepositoryDir(owner, name)
|
|
||||||
JGitUtil.initRepository(gitdir)
|
|
||||||
|
|
||||||
if(createReadme){
|
|
||||||
using(Git.open(gitdir)){ git =>
|
|
||||||
val builder = DirCache.newInCore.builder()
|
|
||||||
val inserter = git.getRepository.newObjectInserter()
|
|
||||||
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
|
|
||||||
val content = if(description.nonEmpty){
|
|
||||||
name + "\n" +
|
|
||||||
"===============\n" +
|
|
||||||
"\n" +
|
|
||||||
description.get
|
|
||||||
} else {
|
|
||||||
name + "\n" +
|
|
||||||
"===============\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.add(JGitUtil.createDirCacheEntry("README.md", FileMode.REGULAR_FILE,
|
|
||||||
inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8"))))
|
|
||||||
builder.finish()
|
|
||||||
|
|
||||||
JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
|
|
||||||
Constants.HEAD, loginAccount.fullName, loginAccount.mailAddress, "Initial commit")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Wiki repository
|
|
||||||
createWikiRepository(loginAccount, owner, name)
|
|
||||||
|
|
||||||
// Record activity
|
|
||||||
recordCreateRepositoryActivity(owner, name, loginUserName)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def insertDefaultLabels(userName: String, repositoryName: String): Unit = {
|
|
||||||
createLabel(userName, repositoryName, "bug", "fc2929")
|
|
||||||
createLabel(userName, repositoryName, "duplicate", "cccccc")
|
|
||||||
createLabel(userName, repositoryName, "enhancement", "84b6eb")
|
|
||||||
createLabel(userName, repositoryName, "invalid", "e6e6e6")
|
|
||||||
createLabel(userName, repositoryName, "question", "cc317c")
|
|
||||||
createLabel(userName, repositoryName, "wontfix", "ffffff")
|
|
||||||
}
|
|
||||||
|
|
||||||
private def existsAccount: Constraint = new Constraint(){
|
private def existsAccount: 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(getAccountByUserName(value).isEmpty) Some("User or group does not exist.") else None
|
if(getAccountByUserName(value).isEmpty) Some("User or group does not exist.") else None
|
||||||
|
|||||||
389
src/main/scala/gitbucket/core/controller/ApiController.scala
Normal file
389
src/main/scala/gitbucket/core/controller/ApiController.scala
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
package gitbucket.core.controller
|
||||||
|
|
||||||
|
import gitbucket.core.api._
|
||||||
|
import gitbucket.core.model._
|
||||||
|
import gitbucket.core.service.IssuesService.IssueSearchCondition
|
||||||
|
import gitbucket.core.service.PullRequestService._
|
||||||
|
import gitbucket.core.service._
|
||||||
|
import gitbucket.core.util.ControlUtil._
|
||||||
|
import gitbucket.core.util.Directory._
|
||||||
|
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||||
|
import gitbucket.core.util._
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.scalatra.{NoContent, UnprocessableEntity, Created}
|
||||||
|
import scala.collection.JavaConverters._
|
||||||
|
|
||||||
|
class ApiController extends ApiControllerBase
|
||||||
|
with RepositoryService
|
||||||
|
with AccountService
|
||||||
|
with ProtectedBranchService
|
||||||
|
with IssuesService
|
||||||
|
with LabelsService
|
||||||
|
with PullRequestService
|
||||||
|
with CommitStatusService
|
||||||
|
with RepositoryCreationService
|
||||||
|
with HandleCommentService
|
||||||
|
with WebHookService
|
||||||
|
with WebHookPullRequestService
|
||||||
|
with WebHookIssueCommentService
|
||||||
|
with WikiService
|
||||||
|
with ActivityService
|
||||||
|
with OwnerAuthenticator
|
||||||
|
with UsersAuthenticator
|
||||||
|
with GroupManagerAuthenticator
|
||||||
|
with ReferrerAuthenticator
|
||||||
|
with ReadableUsersAuthenticator
|
||||||
|
with CollaboratorsAuthenticator
|
||||||
|
|
||||||
|
trait ApiControllerBase extends ControllerBase {
|
||||||
|
self: RepositoryService
|
||||||
|
with AccountService
|
||||||
|
with ProtectedBranchService
|
||||||
|
with IssuesService
|
||||||
|
with LabelsService
|
||||||
|
with PullRequestService
|
||||||
|
with CommitStatusService
|
||||||
|
with RepositoryCreationService
|
||||||
|
with HandleCommentService
|
||||||
|
with OwnerAuthenticator
|
||||||
|
with UsersAuthenticator
|
||||||
|
with GroupManagerAuthenticator
|
||||||
|
with ReferrerAuthenticator
|
||||||
|
with ReadableUsersAuthenticator
|
||||||
|
with CollaboratorsAuthenticator =>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/users/#get-a-single-user
|
||||||
|
*/
|
||||||
|
get("/api/v3/users/:userName") {
|
||||||
|
getAccountByUserName(params("userName")).map { account =>
|
||||||
|
JsonFormat(ApiUser(account))
|
||||||
|
} getOrElse NotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/users/#get-the-authenticated-user
|
||||||
|
*/
|
||||||
|
get("/api/v3/user") {
|
||||||
|
context.loginAccount.map { account =>
|
||||||
|
JsonFormat(ApiUser(account))
|
||||||
|
} getOrElse Unauthorized
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create user repository
|
||||||
|
* https://developer.github.com/v3/repos/#create
|
||||||
|
*/
|
||||||
|
post("/api/v3/user/repos")(usersOnly {
|
||||||
|
val owner = context.loginAccount.get.userName
|
||||||
|
(for {
|
||||||
|
data <- extractFromJsonBody[CreateARepository] if data.isValid
|
||||||
|
} yield {
|
||||||
|
LockUtil.lock(s"${owner}/${data.name}") {
|
||||||
|
if(getRepository(owner, data.name).isEmpty){
|
||||||
|
createRepository(context.loginAccount.get, owner, data.name, data.description, data.`private`, data.auto_init)
|
||||||
|
val repository = getRepository(owner, data.name).get
|
||||||
|
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(owner).get)))
|
||||||
|
} else {
|
||||||
|
ApiError(
|
||||||
|
"A repository with this name already exists on this account",
|
||||||
|
Some("https://developer.github.com/v3/repos/#create")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) getOrElse NotFound
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create group repository
|
||||||
|
* https://developer.github.com/v3/repos/#create
|
||||||
|
*/
|
||||||
|
post("/api/v3/orgs/:org/repos")(managersOnly {
|
||||||
|
val groupName = params("org")
|
||||||
|
(for {
|
||||||
|
data <- extractFromJsonBody[CreateARepository] if data.isValid
|
||||||
|
} yield {
|
||||||
|
LockUtil.lock(s"${groupName}/${data.name}") {
|
||||||
|
if(getRepository(groupName, data.name).isEmpty){
|
||||||
|
createRepository(context.loginAccount.get, groupName, data.name, data.description, data.`private`, data.auto_init)
|
||||||
|
val repository = getRepository(groupName, data.name).get
|
||||||
|
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(groupName).get)))
|
||||||
|
} else {
|
||||||
|
ApiError(
|
||||||
|
"A repository with this name already exists for this group",
|
||||||
|
Some("https://developer.github.com/v3/repos/#create")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) getOrElse NotFound
|
||||||
|
})
|
||||||
|
|
||||||
|
/** https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */
|
||||||
|
patch("/api/v3/repos/:owner/:repo/branches/:branch")(ownerOnly { repository =>
|
||||||
|
import gitbucket.core.api._
|
||||||
|
(for{
|
||||||
|
branch <- params.get("branch") if repository.branchList.find(_ == branch).isDefined
|
||||||
|
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
|
||||||
|
} yield {
|
||||||
|
if(protection.enabled){
|
||||||
|
enableBranchProtection(repository.owner, repository.name, branch, protection.status.enforcement_level == ApiBranchProtection.Everyone, protection.status.contexts)
|
||||||
|
} else {
|
||||||
|
disableBranchProtection(repository.owner, repository.name, branch)
|
||||||
|
}
|
||||||
|
JsonFormat(ApiBranch(branch, protection)(RepositoryName(repository)))
|
||||||
|
}) getOrElse NotFound
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see https://developer.github.com/v3/rate_limit/#get-your-current-rate-limit-status
|
||||||
|
* but not enabled.
|
||||||
|
*/
|
||||||
|
get("/api/v3/rate_limit"){
|
||||||
|
contentType = formats("json")
|
||||||
|
// this message is same as github enterprise...
|
||||||
|
org.scalatra.NotFound(ApiError("Rate limiting is not enabled."))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/issues/:id/comments")(referrersOnly { repository =>
|
||||||
|
(for{
|
||||||
|
issueId <- params("id").toIntOpt
|
||||||
|
comments = getCommentsForApi(repository.owner, repository.name, issueId.toInt)
|
||||||
|
} yield {
|
||||||
|
JsonFormat(comments.map{ case (issueComment, user, issue) => ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest) })
|
||||||
|
}).getOrElse(NotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/issues/comments/#create-a-comment
|
||||||
|
*/
|
||||||
|
post("/api/v3/repos/:owner/:repository/issues/:id/comments")(readableUsersOnly { repository =>
|
||||||
|
(for{
|
||||||
|
issueId <- params("id").toIntOpt
|
||||||
|
issue <- getIssue(repository.owner, repository.name, issueId.toString)
|
||||||
|
body <- extractFromJsonBody[CreateAComment].map(_.body) if ! body.isEmpty
|
||||||
|
action = params.get("action").filter(_ => isEditable(issue.userName, issue.repositoryName, issue.openedUserName))
|
||||||
|
(issue, id) <- handleComment(issue, Some(body), repository, action)
|
||||||
|
issueComment <- getComment(repository.owner, repository.name, id.toString())
|
||||||
|
} yield {
|
||||||
|
JsonFormat(ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(context.loginAccount.get), issue.isPullRequest))
|
||||||
|
}) getOrElse NotFound
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all labels for this repository
|
||||||
|
* https://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/labels")(referrersOnly { repository =>
|
||||||
|
JsonFormat(getLabels(repository.owner, repository.name).map { label =>
|
||||||
|
ApiLabel(label, RepositoryName(repository))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a single label
|
||||||
|
* https://developer.github.com/v3/issues/labels/#get-a-single-label
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/labels/:labelName")(referrersOnly { repository =>
|
||||||
|
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
|
||||||
|
JsonFormat(ApiLabel(label, RepositoryName(repository)))
|
||||||
|
} getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a label
|
||||||
|
* https://developer.github.com/v3/issues/labels/#create-a-label
|
||||||
|
*/
|
||||||
|
post("/api/v3/repos/:owner/:repository/labels")(collaboratorsOnly { repository =>
|
||||||
|
(for{
|
||||||
|
data <- extractFromJsonBody[CreateALabel] if data.isValid
|
||||||
|
} yield {
|
||||||
|
LockUtil.lock(RepositoryName(repository).fullName) {
|
||||||
|
if (getLabel(repository.owner, repository.name, data.name).isEmpty) {
|
||||||
|
val labelId = createLabel(repository.owner, repository.name, data.name, data.color)
|
||||||
|
getLabel(repository.owner, repository.name, labelId).map { label =>
|
||||||
|
Created(JsonFormat(ApiLabel(label, RepositoryName(repository))))
|
||||||
|
} getOrElse NotFound()
|
||||||
|
} else {
|
||||||
|
// TODO ApiError should support errors field to enhance compatibility of GitHub API
|
||||||
|
UnprocessableEntity(ApiError(
|
||||||
|
"Validation Failed",
|
||||||
|
Some("https://developer.github.com/v3/issues/labels/#create-a-label")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a label
|
||||||
|
* https://developer.github.com/v3/issues/labels/#update-a-label
|
||||||
|
*/
|
||||||
|
patch("/api/v3/repos/:owner/:repository/labels/:labelName")(collaboratorsOnly { repository =>
|
||||||
|
(for{
|
||||||
|
data <- extractFromJsonBody[CreateALabel] if data.isValid
|
||||||
|
} yield {
|
||||||
|
LockUtil.lock(RepositoryName(repository).fullName) {
|
||||||
|
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
|
||||||
|
if (getLabel(repository.owner, repository.name, data.name).isEmpty) {
|
||||||
|
updateLabel(repository.owner, repository.name, label.labelId, data.name, data.color)
|
||||||
|
JsonFormat(ApiLabel(
|
||||||
|
getLabel(repository.owner, repository.name, label.labelId).get,
|
||||||
|
RepositoryName(repository)))
|
||||||
|
} else {
|
||||||
|
// TODO ApiError should support errors field to enhance compatibility of GitHub API
|
||||||
|
UnprocessableEntity(ApiError(
|
||||||
|
"Validation Failed",
|
||||||
|
Some("https://developer.github.com/v3/issues/labels/#create-a-label")))
|
||||||
|
}
|
||||||
|
} getOrElse NotFound()
|
||||||
|
}
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a label
|
||||||
|
* https://developer.github.com/v3/issues/labels/#delete-a-label
|
||||||
|
*/
|
||||||
|
delete("/api/v3/repos/:owner/:repository/labels/:labelName")(collaboratorsOnly { repository =>
|
||||||
|
LockUtil.lock(RepositoryName(repository).fullName) {
|
||||||
|
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
|
||||||
|
deleteLabel(repository.owner, repository.name, label.labelId)
|
||||||
|
NoContent()
|
||||||
|
} getOrElse NotFound()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/pulls/#list-pull-requests
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/pulls")(referrersOnly { repository =>
|
||||||
|
val page = IssueSearchCondition.page(request)
|
||||||
|
// TODO: more api spec condition
|
||||||
|
val condition = IssueSearchCondition(request)
|
||||||
|
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) =>
|
||||||
|
ApiPullRequest(
|
||||||
|
issue,
|
||||||
|
pullRequest,
|
||||||
|
ApiRepository(headRepo, ApiUser(headOwner)),
|
||||||
|
ApiRepository(repository, ApiUser(baseOwner)),
|
||||||
|
ApiUser(issueUser)) })
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/pulls/#get-a-single-pull-request
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/pulls/:id")(referrersOnly { repository =>
|
||||||
|
(for{
|
||||||
|
issueId <- params("id").toIntOpt
|
||||||
|
(issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId)
|
||||||
|
users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set())
|
||||||
|
baseOwner <- users.get(repository.owner)
|
||||||
|
headOwner <- users.get(pullRequest.requestUserName)
|
||||||
|
issueUser <- users.get(issue.openedUserName)
|
||||||
|
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
|
||||||
|
} yield {
|
||||||
|
JsonFormat(ApiPullRequest(
|
||||||
|
issue,
|
||||||
|
pullRequest,
|
||||||
|
ApiRepository(headRepo, ApiUser(headOwner)),
|
||||||
|
ApiRepository(repository, ApiUser(baseOwner)),
|
||||||
|
ApiUser(issueUser)))
|
||||||
|
}).getOrElse(NotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/pulls/#list-commits-on-a-pull-request
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/pulls/:id/commits")(referrersOnly { repository =>
|
||||||
|
val owner = repository.owner
|
||||||
|
val name = repository.name
|
||||||
|
params("id").toIntOpt.flatMap{ issueId =>
|
||||||
|
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
|
||||||
|
using(Git.open(getRepositoryDir(owner, name))){ git =>
|
||||||
|
val oldId = git.getRepository.resolve(pullreq.commitIdFrom)
|
||||||
|
val newId = git.getRepository.resolve(pullreq.commitIdTo)
|
||||||
|
val repoFullName = RepositoryName(repository)
|
||||||
|
val commits = git.log.addRange(oldId, newId).call.iterator.asScala.map(c => ApiCommitListItem(new CommitInfo(c), repoFullName)).toList
|
||||||
|
JsonFormat(commits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} getOrElse NotFound
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/#get
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository")(referrersOnly { repository =>
|
||||||
|
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(repository.owner).get)))
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/statuses/#create-a-status
|
||||||
|
*/
|
||||||
|
post("/api/v3/repos/:owner/:repo/statuses/:sha")(collaboratorsOnly { repository =>
|
||||||
|
(for{
|
||||||
|
ref <- params.get("sha")
|
||||||
|
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
||||||
|
data <- extractFromJsonBody[CreateAStatus] if data.isValid
|
||||||
|
creator <- context.loginAccount
|
||||||
|
state <- CommitState.valueOf(data.state)
|
||||||
|
statusId = createCommitStatus(repository.owner, repository.name, sha, data.context.getOrElse("default"),
|
||||||
|
state, data.target_url, data.description, new java.util.Date(), creator)
|
||||||
|
status <- getCommitStatus(repository.owner, repository.name, statusId)
|
||||||
|
} yield {
|
||||||
|
JsonFormat(ApiCommitStatus(status, ApiUser(creator)))
|
||||||
|
}) getOrElse NotFound
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref
|
||||||
|
*
|
||||||
|
* ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name.
|
||||||
|
*/
|
||||||
|
val listStatusesRoute = get("/api/v3/repos/:owner/:repo/commits/:ref/statuses")(referrersOnly { repository =>
|
||||||
|
(for{
|
||||||
|
ref <- params.get("ref")
|
||||||
|
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
||||||
|
} yield {
|
||||||
|
JsonFormat(getCommitStatuesWithCreator(repository.owner, repository.name, sha).map{ case(status, creator) =>
|
||||||
|
ApiCommitStatus(status, ApiUser(creator))
|
||||||
|
})
|
||||||
|
}) getOrElse NotFound
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref
|
||||||
|
*
|
||||||
|
* legacy route
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repo/statuses/:ref"){
|
||||||
|
listStatusesRoute.action()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref
|
||||||
|
*
|
||||||
|
* ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name.
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repo/commits/:ref/status")(referrersOnly { repository =>
|
||||||
|
(for{
|
||||||
|
ref <- params.get("ref")
|
||||||
|
owner <- getAccountByUserName(repository.owner)
|
||||||
|
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
||||||
|
} yield {
|
||||||
|
val statuses = getCommitStatuesWithCreator(repository.owner, repository.name, sha)
|
||||||
|
JsonFormat(ApiCombinedCommitStatus(sha, statuses, ApiRepository(repository, owner)))
|
||||||
|
}) getOrElse NotFound
|
||||||
|
})
|
||||||
|
|
||||||
|
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
|
||||||
|
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ import gitbucket.core.util.Directory._
|
|||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util._
|
import gitbucket.core.util._
|
||||||
|
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import io.github.gitbucket.scalatra.forms._
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import org.json4s._
|
import org.json4s._
|
||||||
import org.scalatra._
|
import org.scalatra._
|
||||||
@@ -28,7 +28,11 @@ abstract class ControllerBase extends ScalatraFilter
|
|||||||
with ClientSideValidationFormSupport with JacksonJsonSupport with I18nSupport with FlashMapSupport with Validations
|
with ClientSideValidationFormSupport with JacksonJsonSupport with I18nSupport with FlashMapSupport with Validations
|
||||||
with SystemSettingsService {
|
with SystemSettingsService {
|
||||||
|
|
||||||
implicit val jsonFormats = DefaultFormats
|
implicit val jsonFormats = gitbucket.core.api.JsonFormat.jsonFormats
|
||||||
|
|
||||||
|
before("/api/v3/*") {
|
||||||
|
contentType = formats("json")
|
||||||
|
}
|
||||||
|
|
||||||
// TODO Scala 2.11
|
// TODO Scala 2.11
|
||||||
// // Don't set content type via Accept header.
|
// // Don't set content type via Accept header.
|
||||||
@@ -176,7 +180,6 @@ abstract class ControllerBase extends ScalatraFilter
|
|||||||
* Context object for the current request.
|
* Context object for the current request.
|
||||||
*/
|
*/
|
||||||
case class Context(settings: SystemSettingsService.SystemSettings, loginAccount: Option[Account], request: HttpServletRequest){
|
case class Context(settings: SystemSettingsService.SystemSettings, loginAccount: Option[Account], request: HttpServletRequest){
|
||||||
|
|
||||||
val path = settings.baseUrl.getOrElse(request.getContextPath)
|
val path = settings.baseUrl.getOrElse(request.getContextPath)
|
||||||
val currentPath = request.getRequestURI.substring(request.getContextPath.length)
|
val currentPath = request.getRequestURI.substring(request.getContextPath.length)
|
||||||
val baseUrl = settings.baseUrl(request)
|
val baseUrl = settings.baseUrl(request)
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
val userName = context.loginAccount.get.userName
|
val userName = context.loginAccount.get.userName
|
||||||
val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName)
|
val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName)
|
||||||
val userRepos = getUserRepositories(userName, context.baseUrl, true).map(repo => repo.owner -> repo.name)
|
val userRepos = getUserRepositories(userName, true).map(repo => repo.owner -> repo.name)
|
||||||
val page = IssueSearchCondition.page(request)
|
val page = IssueSearchCondition.page(request)
|
||||||
|
|
||||||
html.issues(
|
html.issues(
|
||||||
@@ -108,7 +108,9 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
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) = {
|
||||||
@@ -131,7 +133,9 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
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,69 @@ 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.isImage)
|
||||||
|
}
|
||||||
|
} getOrElse BadRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/import") {
|
||||||
|
session.get(Keys.Session.LoginAccount).collect { case loginAccount: Account if loginAccount.isAdmin =>
|
||||||
|
execute({ (file, fileId) =>
|
||||||
|
if(file.getName.endsWith(".xml")){
|
||||||
|
import JDBCUtil._
|
||||||
|
val conn = request2Session(request).conn
|
||||||
|
conn.importAsXML(file.getInputStream)
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Import is available for only the XML file.")
|
||||||
|
}
|
||||||
|
}, _ => 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,36 +1,46 @@
|
|||||||
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.html
|
|
||||||
import gitbucket.core.model.Account
|
import gitbucket.core.model.Account
|
||||||
import gitbucket.core.service.{RepositoryService, ActivityService, AccountService}
|
import gitbucket.core.service._
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util.{LDAPUtil, Keys, UsersAuthenticator}
|
import gitbucket.core.util.ControlUtil._
|
||||||
|
import gitbucket.core.util.{LDAPUtil, Keys, UsersAuthenticator, ReferrerAuthenticator, StringUtil}
|
||||||
|
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import io.github.gitbucket.scalatra.forms._
|
||||||
|
|
||||||
|
|
||||||
class IndexController extends IndexControllerBase
|
class IndexController extends IndexControllerBase
|
||||||
with RepositoryService with ActivityService with AccountService with UsersAuthenticator
|
with RepositoryService with ActivityService with AccountService with RepositorySearchService with IssuesService
|
||||||
|
with UsersAuthenticator with ReferrerAuthenticator
|
||||||
|
|
||||||
|
|
||||||
trait IndexControllerBase extends ControllerBase {
|
trait IndexControllerBase extends ControllerBase {
|
||||||
self: RepositoryService with ActivityService with AccountService with UsersAuthenticator =>
|
self: RepositoryService with ActivityService with AccountService with RepositorySearchService
|
||||||
|
with UsersAuthenticator with ReferrerAuthenticator =>
|
||||||
|
|
||||||
case class SignInForm(userName: String, password: String)
|
case class SignInForm(userName: String, password: String)
|
||||||
|
|
||||||
val form = mapping(
|
val signinForm = mapping(
|
||||||
"userName" -> trim(label("Username", text(required))),
|
"userName" -> trim(label("Username", text(required))),
|
||||||
"password" -> trim(label("Password", text(required)))
|
"password" -> trim(label("Password", text(required)))
|
||||||
)(SignInForm.apply)
|
)(SignInForm.apply)
|
||||||
|
|
||||||
|
val searchForm = mapping(
|
||||||
|
"query" -> trim(text(required)),
|
||||||
|
"owner" -> trim(text(required)),
|
||||||
|
"repository" -> trim(text(required))
|
||||||
|
)(SearchForm.apply)
|
||||||
|
|
||||||
|
case class SearchForm(query: String, owner: String, repository: String)
|
||||||
|
|
||||||
|
|
||||||
get("/"){
|
get("/"){
|
||||||
val loginAccount = context.loginAccount
|
val loginAccount = context.loginAccount
|
||||||
if(loginAccount.isEmpty) {
|
if(loginAccount.isEmpty) {
|
||||||
html.index(getRecentActivities(),
|
gitbucket.core.html.index(getRecentActivities(),
|
||||||
getVisibleRepositories(loginAccount, context.baseUrl, withoutPhysicalInfo = true),
|
getVisibleRepositories(loginAccount, withoutPhysicalInfo = true),
|
||||||
loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl, withoutPhysicalInfo = true) }.getOrElse(Nil)
|
loginAccount.map{ account => getUserRepositories(account.userName, withoutPhysicalInfo = true) }.getOrElse(Nil)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val loginUserName = loginAccount.get.userName
|
val loginUserName = loginAccount.get.userName
|
||||||
@@ -39,9 +49,9 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
visibleOwnerSet ++= loginUserGroups
|
visibleOwnerSet ++= loginUserGroups
|
||||||
|
|
||||||
html.index(getRecentActivitiesByOwners(visibleOwnerSet),
|
gitbucket.core.html.index(getRecentActivitiesByOwners(visibleOwnerSet),
|
||||||
getVisibleRepositories(loginAccount, context.baseUrl, withoutPhysicalInfo = true),
|
getVisibleRepositories(loginAccount, withoutPhysicalInfo = true),
|
||||||
loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl, withoutPhysicalInfo = true) }.getOrElse(Nil)
|
loginAccount.map{ account => getUserRepositories(account.userName, withoutPhysicalInfo = true) }.getOrElse(Nil)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,10 +61,10 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
if(redirect.isDefined && redirect.get.startsWith("/")){
|
if(redirect.isDefined && redirect.get.startsWith("/")){
|
||||||
flash += Keys.Flash.Redirect -> redirect.get
|
flash += Keys.Flash.Redirect -> redirect.get
|
||||||
}
|
}
|
||||||
html.signin()
|
gitbucket.core.html.signin()
|
||||||
}
|
}
|
||||||
|
|
||||||
post("/signin", form){ form =>
|
post("/signin", signinForm){ form =>
|
||||||
authenticate(context.settings, form.userName, form.password) match {
|
authenticate(context.settings, form.userName, form.password) match {
|
||||||
case Some(account) => signin(account)
|
case Some(account) => signin(account)
|
||||||
case None => redirect("/signin")
|
case None => redirect("/signin")
|
||||||
@@ -110,13 +120,40 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
getAccountByUserName(params("userName")).isDefined
|
getAccountByUserName(params("userName")).isDefined
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
// TODO Move to RepositoryViwerController?
|
||||||
* @see https://developer.github.com/v3/rate_limit/#get-your-current-rate-limit-status
|
post("/search", searchForm){ form =>
|
||||||
* but not enabled.
|
redirect(s"/${form.owner}/${form.repository}/search?q=${StringUtil.urlEncode(form.query)}")
|
||||||
*/
|
|
||||||
get("/api/v3/rate_limit"){
|
|
||||||
contentType = formats("json")
|
|
||||||
// this message is same as github enterprise...
|
|
||||||
org.scalatra.NotFound(ApiError("Rate limiting is not enabled."))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Move to RepositoryViwerController?
|
||||||
|
get("/:owner/:repository/search")(referrersOnly { repository =>
|
||||||
|
defining(params("q").trim, params.getOrElse("type", "code")){ case (query, target) =>
|
||||||
|
val page = try {
|
||||||
|
val i = params.getOrElse("page", "1").toInt
|
||||||
|
if(i <= 0) 1 else i
|
||||||
|
} catch {
|
||||||
|
case e: NumberFormatException => 1
|
||||||
|
}
|
||||||
|
|
||||||
|
target.toLowerCase match {
|
||||||
|
case "issue" => gitbucket.core.search.html.issues(
|
||||||
|
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)
|
||||||
|
|
||||||
|
case _ => gitbucket.core.search.html.code(
|
||||||
|
searchFiles(repository.owner, repository.name, query),
|
||||||
|
countIssues(repository.owner, repository.name, query),
|
||||||
|
countWikiPages(repository.owner, repository.name, query),
|
||||||
|
query, page, repository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
import gitbucket.core.api._
|
|
||||||
import gitbucket.core.issues.html
|
import gitbucket.core.issues.html
|
||||||
import gitbucket.core.model.Issue
|
|
||||||
import gitbucket.core.service.IssuesService._
|
import gitbucket.core.service.IssuesService._
|
||||||
import gitbucket.core.service._
|
import gitbucket.core.service._
|
||||||
import gitbucket.core.util.ControlUtil._
|
import gitbucket.core.util.ControlUtil._
|
||||||
@@ -11,16 +9,16 @@ import gitbucket.core.util._
|
|||||||
import gitbucket.core.view
|
import gitbucket.core.view
|
||||||
import gitbucket.core.view.Markdown
|
import gitbucket.core.view.Markdown
|
||||||
|
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import io.github.gitbucket.scalatra.forms._
|
||||||
import org.scalatra.Ok
|
import org.scalatra.Ok
|
||||||
|
|
||||||
|
|
||||||
class IssuesController extends IssuesControllerBase
|
class IssuesController extends IssuesControllerBase
|
||||||
with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
|
with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService with HandleCommentService
|
||||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with WebHookIssueCommentService
|
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with WebHookIssueCommentService
|
||||||
|
|
||||||
trait IssuesControllerBase extends ControllerBase {
|
trait IssuesControllerBase extends ControllerBase {
|
||||||
self: IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
|
self: IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService with HandleCommentService
|
||||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with WebHookIssueCommentService =>
|
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with WebHookIssueCommentService =>
|
||||||
|
|
||||||
case class IssueCreateForm(title: String, content: Option[String],
|
case class IssueCreateForm(title: String, content: Option[String],
|
||||||
@@ -78,18 +76,6 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/issues/:id/comments")(referrersOnly { repository =>
|
|
||||||
(for{
|
|
||||||
issueId <- params("id").toIntOpt
|
|
||||||
comments = getCommentsForApi(repository.owner, repository.name, issueId.toInt)
|
|
||||||
} yield {
|
|
||||||
JsonFormat(comments.map{ case (issueComment, user, issue) => ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest) })
|
|
||||||
}).getOrElse(NotFound)
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
|
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
|
||||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||||
html.create(
|
html.create(
|
||||||
@@ -128,7 +114,7 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
getIssue(owner, name, issueId.toString).foreach { issue =>
|
getIssue(owner, name, issueId.toString).foreach { issue =>
|
||||||
// extract references and create refer comment
|
// extract references and create refer comment
|
||||||
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""))
|
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""), context.loginAccount.get)
|
||||||
|
|
||||||
// call web hooks
|
// call web hooks
|
||||||
callIssuesWebHook("opened", repository, issue, context.baseUrl, context.loginAccount.get)
|
callIssuesWebHook("opened", repository, issue, context.baseUrl, context.loginAccount.get)
|
||||||
@@ -150,7 +136,7 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
// update issue
|
// update issue
|
||||||
updateIssue(owner, name, issue.issueId, title, issue.content)
|
updateIssue(owner, name, issue.issueId, title, issue.content)
|
||||||
// extract references and create refer comment
|
// extract references and create refer comment
|
||||||
createReferComment(owner, name, issue.copy(title = title), title)
|
createReferComment(owner, name, issue.copy(title = title), title, context.loginAccount.get)
|
||||||
|
|
||||||
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
||||||
} else Unauthorized
|
} else Unauthorized
|
||||||
@@ -165,7 +151,7 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
// update issue
|
// update issue
|
||||||
updateIssue(owner, name, issue.issueId, issue.title, content)
|
updateIssue(owner, name, issue.issueId, issue.title, content)
|
||||||
// extract references and create refer comment
|
// extract references and create refer comment
|
||||||
createReferComment(owner, name, issue, content.getOrElse(""))
|
createReferComment(owner, name, issue, content.getOrElse(""), context.loginAccount.get)
|
||||||
|
|
||||||
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
||||||
} else Unauthorized
|
} else Unauthorized
|
||||||
@@ -174,30 +160,22 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
|
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
|
||||||
handleComment(form.issueId, Some(form.content), repository)() map { case (issue, id) =>
|
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
|
||||||
redirect(s"/${repository.owner}/${repository.name}/${
|
val actionOpt = params.get("action").filter(_ => isEditable(issue.userName, issue.repositoryName, issue.openedUserName))
|
||||||
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
|
handleComment(issue, Some(form.content), repository, actionOpt) map { case (issue, id) =>
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/${
|
||||||
|
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
|
||||||
|
}
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/issues/comments/#create-a-comment
|
|
||||||
*/
|
|
||||||
post("/api/v3/repos/:owner/:repository/issues/:id/comments")(readableUsersOnly { repository =>
|
|
||||||
(for{
|
|
||||||
issueId <- params("id").toIntOpt
|
|
||||||
body <- extractFromJsonBody[CreateAComment].map(_.body) if ! body.isEmpty
|
|
||||||
(issue, id) <- handleComment(issueId, Some(body), repository)()
|
|
||||||
issueComment <- getComment(repository.owner, repository.name, id.toString())
|
|
||||||
} yield {
|
|
||||||
JsonFormat(ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(context.loginAccount.get), issue.isPullRequest))
|
|
||||||
}) getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
|
post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
|
||||||
handleComment(form.issueId, form.content, repository)() map { case (issue, id) =>
|
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
|
||||||
redirect(s"/${repository.owner}/${repository.name}/${
|
val actionOpt = params.get("action").filter(_ => isEditable(issue.userName, issue.repositoryName, issue.openedUserName))
|
||||||
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
|
handleComment(issue, form.content, repository, actionOpt) map { case (issue, id) =>
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/${
|
||||||
|
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
|
||||||
|
}
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -315,8 +293,16 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
post("/:owner/:repository/issues/batchedit/state")(collaboratorsOnly { repository =>
|
post("/:owner/:repository/issues/batchedit/state")(collaboratorsOnly { repository =>
|
||||||
defining(params.get("value")){ action =>
|
defining(params.get("value")){ action =>
|
||||||
action match {
|
action match {
|
||||||
case Some("open") => executeBatch(repository) { handleComment(_, None, repository)( _ => Some("reopen")) }
|
case Some("open") => executeBatch(repository) { issueId =>
|
||||||
case Some("close") => executeBatch(repository) { handleComment(_, None, repository)( _ => Some("close")) }
|
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||||
|
handleComment(issue, None, repository, Some("reopen"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case Some("close") => executeBatch(repository) { issueId =>
|
||||||
|
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||||
|
handleComment(issue, None, repository, Some("close"))
|
||||||
|
}
|
||||||
|
}
|
||||||
case _ => // TODO BadRequest
|
case _ => // TODO BadRequest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -373,99 +359,6 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Same method exists in PullRequestController. Should it moved to IssueService?
|
|
||||||
private def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String) = {
|
|
||||||
StringUtil.extractIssueId(message).foreach { issueId =>
|
|
||||||
val content = fromIssue.issueId + ":" + fromIssue.title
|
|
||||||
if(getIssue(owner, repository, issueId).isDefined){
|
|
||||||
// Not add if refer comment already exist.
|
|
||||||
if(!getComments(owner, repository, issueId.toInt).exists { x => x.action == "refer" && x.content == content }) {
|
|
||||||
createComment(owner, repository, context.loginAccount.get.userName, issueId.toInt, content, "refer")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see [[https://github.com/takezoe/gitbucket/wiki/CommentAction]]
|
|
||||||
*/
|
|
||||||
private def handleComment(issueId: Int, content: Option[String], repository: RepositoryService.RepositoryInfo)
|
|
||||||
(getAction: Issue => Option[String] =
|
|
||||||
p1 => params.get("action").filter(_ => isEditable(p1.userName, p1.repositoryName, p1.openedUserName))) = {
|
|
||||||
|
|
||||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
|
||||||
val userName = context.loginAccount.get.userName
|
|
||||||
|
|
||||||
getIssue(owner, name, issueId.toString) flatMap { issue =>
|
|
||||||
val (action, recordActivity) =
|
|
||||||
getAction(issue)
|
|
||||||
.collect {
|
|
||||||
case "close" if(!issue.closed) => true ->
|
|
||||||
(Some("close") -> Some(if(issue.isPullRequest) recordClosePullRequestActivity _ else recordCloseIssueActivity _))
|
|
||||||
case "reopen" if(issue.closed) => false ->
|
|
||||||
(Some("reopen") -> Some(recordReopenIssueActivity _))
|
|
||||||
}
|
|
||||||
.map { case (closed, t) =>
|
|
||||||
updateClosed(owner, name, issueId, closed)
|
|
||||||
t
|
|
||||||
}
|
|
||||||
.getOrElse(None -> None)
|
|
||||||
|
|
||||||
val commentId = (content, action) match {
|
|
||||||
case (None, None) => None
|
|
||||||
case (None, Some(action)) => Some(createComment(owner, name, userName, issueId, action.capitalize, action))
|
|
||||||
case (Some(content), _) => Some(createComment(owner, name, userName, issueId, content, action.map(_+ "_comment").getOrElse("comment")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// record comment activity if comment is entered
|
|
||||||
content foreach {
|
|
||||||
(if(issue.isPullRequest) recordCommentPullRequestActivity _ else recordCommentIssueActivity _)
|
|
||||||
(owner, name, userName, issueId, _)
|
|
||||||
}
|
|
||||||
recordActivity foreach ( _ (owner, name, userName, issueId, issue.title) )
|
|
||||||
|
|
||||||
// extract references and create refer comment
|
|
||||||
content.map { content =>
|
|
||||||
createReferComment(owner, name, issue, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
// call web hooks
|
|
||||||
action match {
|
|
||||||
case None => commentId.map{ commentIdSome => callIssueCommentWebHook(repository, issue, commentIdSome, context.loginAccount.get) }
|
|
||||||
case Some(act) => val webHookAction = act match {
|
|
||||||
case "open" => "opened"
|
|
||||||
case "reopen" => "reopened"
|
|
||||||
case "close" => "closed"
|
|
||||||
case _ => act
|
|
||||||
}
|
|
||||||
if(issue.isPullRequest){
|
|
||||||
callPullRequestWebHook(webHookAction, repository, issue.issueId, context.baseUrl, context.loginAccount.get)
|
|
||||||
} else {
|
|
||||||
callIssuesWebHook(webHookAction, repository, issue, context.baseUrl, context.loginAccount.get)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// notifications
|
|
||||||
Notifier() match {
|
|
||||||
case f =>
|
|
||||||
content foreach {
|
|
||||||
f.toNotify(repository, issue, _){
|
|
||||||
Notifier.msgComment(s"${context.baseUrl}/${owner}/${name}/${
|
|
||||||
if(issue.isPullRequest) "pull" else "issues"}/${issueId}#comment-${commentId.get}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
action foreach {
|
|
||||||
f.toNotify(repository, issue, _){
|
|
||||||
Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
commentId.map( issue -> _ )
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def searchIssues(repository: RepositoryService.RepositoryInfo) = {
|
private def searchIssues(repository: RepositoryService.RepositoryInfo) = {
|
||||||
defining(repository.owner, repository.name){ case (owner, repoName) =>
|
defining(repository.owner, repository.name){ case (owner, repoName) =>
|
||||||
val page = IssueSearchCondition.page(request)
|
val page = IssueSearchCondition.page(request)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import gitbucket.core.issues.labels.html
|
|||||||
import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, LabelsService}
|
import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, LabelsService}
|
||||||
import gitbucket.core.util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
|
import gitbucket.core.util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import io.github.gitbucket.scalatra.forms._
|
||||||
import org.scalatra.i18n.Messages
|
import org.scalatra.i18n.Messages
|
||||||
import org.scalatra.Ok
|
import org.scalatra.Ok
|
||||||
|
|
||||||
@@ -19,10 +19,11 @@ trait LabelsControllerBase extends ControllerBase {
|
|||||||
case class LabelForm(labelName: String, color: String)
|
case class LabelForm(labelName: String, color: String)
|
||||||
|
|
||||||
val labelForm = mapping(
|
val labelForm = mapping(
|
||||||
"labelName" -> trim(label("Label name", text(required, labelName, maxlength(100)))),
|
"labelName" -> trim(label("Label name", text(required, labelName, uniqueLabelName, maxlength(100)))),
|
||||||
"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),
|
||||||
@@ -80,4 +81,16 @@ trait LabelsControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def uniqueLabelName: Constraint = new Constraint(){
|
||||||
|
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] = {
|
||||||
|
val owner = params("owner")
|
||||||
|
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.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import gitbucket.core.issues.milestones.html
|
|||||||
import gitbucket.core.service.{RepositoryService, MilestonesService, AccountService}
|
import gitbucket.core.service.{RepositoryService, MilestonesService, AccountService}
|
||||||
import gitbucket.core.util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
|
import gitbucket.core.util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import io.github.gitbucket.scalatra.forms._
|
||||||
|
|
||||||
class MilestonesController extends MilestonesControllerBase
|
class MilestonesController extends MilestonesControllerBase
|
||||||
with MilestonesService with RepositoryService with AccountService
|
with MilestonesService with RepositoryService with AccountService
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
package gitbucket.core.controller
|
|
||||||
|
|
||||||
import gitbucket.core.admin.plugins.html
|
|
||||||
import gitbucket.core.plugin.PluginRegistry
|
|
||||||
import gitbucket.core.util.AdminAuthenticator
|
|
||||||
|
|
||||||
class PluginsController extends ControllerBase with AdminAuthenticator {
|
|
||||||
get("/admin/plugins")(adminOnly {
|
|
||||||
html.plugins(PluginRegistry().getPlugins())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
import gitbucket.core.api._
|
import gitbucket.core.model.WebHook
|
||||||
import gitbucket.core.model.{Account, CommitState, Repository, PullRequest, Issue}
|
|
||||||
import gitbucket.core.pulls.html
|
import gitbucket.core.pulls.html
|
||||||
import gitbucket.core.service.CommitStatusService
|
import gitbucket.core.service.CommitStatusService
|
||||||
import gitbucket.core.service.MergeService
|
import gitbucket.core.service.MergeService
|
||||||
@@ -16,7 +15,7 @@ import gitbucket.core.util._
|
|||||||
import gitbucket.core.view
|
import gitbucket.core.view
|
||||||
import gitbucket.core.view.helpers
|
import gitbucket.core.view.helpers
|
||||||
|
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import io.github.gitbucket.scalatra.forms._
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.lib.PersonIdent
|
import org.eclipse.jgit.lib.PersonIdent
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
@@ -27,13 +26,13 @@ import scala.collection.JavaConverters._
|
|||||||
class PullRequestsController extends PullRequestsControllerBase
|
class PullRequestsController extends PullRequestsControllerBase
|
||||||
with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with LabelsService
|
with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with LabelsService
|
||||||
with CommitsService with ActivityService with WebHookPullRequestService with ReferrerAuthenticator with CollaboratorsAuthenticator
|
with CommitsService with ActivityService with WebHookPullRequestService with ReferrerAuthenticator with CollaboratorsAuthenticator
|
||||||
with CommitStatusService with MergeService
|
with CommitStatusService with MergeService with ProtectedBranchService
|
||||||
|
|
||||||
|
|
||||||
trait PullRequestsControllerBase extends ControllerBase {
|
trait PullRequestsControllerBase extends ControllerBase {
|
||||||
self: RepositoryService with AccountService with IssuesService with MilestonesService with LabelsService
|
self: RepositoryService with AccountService with IssuesService with MilestonesService with LabelsService
|
||||||
with CommitsService with ActivityService with PullRequestService with WebHookPullRequestService with ReferrerAuthenticator with CollaboratorsAuthenticator
|
with CommitsService with ActivityService with PullRequestService with WebHookPullRequestService with ReferrerAuthenticator with CollaboratorsAuthenticator
|
||||||
with CommitStatusService with MergeService =>
|
with CommitStatusService with MergeService with ProtectedBranchService =>
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(classOf[PullRequestsControllerBase])
|
private val logger = LoggerFactory.getLogger(classOf[PullRequestsControllerBase])
|
||||||
|
|
||||||
@@ -82,24 +81,6 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/pulls/#list-pull-requests
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/pulls")(referrersOnly { repository =>
|
|
||||||
val page = IssueSearchCondition.page(request)
|
|
||||||
// TODO: more api spec condition
|
|
||||||
val condition = IssueSearchCondition(request)
|
|
||||||
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) =>
|
|
||||||
ApiPullRequest(
|
|
||||||
issue,
|
|
||||||
pullRequest,
|
|
||||||
ApiRepository(headRepo, ApiUser(headOwner)),
|
|
||||||
ApiRepository(repository, ApiUser(baseOwner)),
|
|
||||||
ApiUser(issueUser)) })
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/pull/:id")(referrersOnly { repository =>
|
get("/:owner/:repository/pull/:id")(referrersOnly { repository =>
|
||||||
params("id").toIntOpt.flatMap{ issueId =>
|
params("id").toIntOpt.flatMap{ issueId =>
|
||||||
val owner = repository.owner
|
val owner = repository.owner
|
||||||
@@ -119,71 +100,43 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
commits,
|
commits,
|
||||||
diffs,
|
diffs,
|
||||||
hasWritePermission(owner, name, context.loginAccount),
|
hasWritePermission(owner, name, context.loginAccount),
|
||||||
repository)
|
repository,
|
||||||
|
flash.toMap.map(f => f._1 -> f._2.toString))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
ajaxGet("/:owner/:repository/pull/:id/mergeguide")(referrersOnly { repository =>
|
||||||
* https://developer.github.com/v3/pulls/#get-a-single-pull-request
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/pulls/:id")(referrersOnly { repository =>
|
|
||||||
(for{
|
|
||||||
issueId <- params("id").toIntOpt
|
|
||||||
(issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId)
|
|
||||||
users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set())
|
|
||||||
baseOwner <- users.get(repository.owner)
|
|
||||||
headOwner <- users.get(pullRequest.requestUserName)
|
|
||||||
issueUser <- users.get(issue.openedUserName)
|
|
||||||
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName, baseUrl)
|
|
||||||
} yield {
|
|
||||||
JsonFormat(ApiPullRequest(
|
|
||||||
issue,
|
|
||||||
pullRequest,
|
|
||||||
ApiRepository(headRepo, ApiUser(headOwner)),
|
|
||||||
ApiRepository(repository, ApiUser(baseOwner)),
|
|
||||||
ApiUser(issueUser)))
|
|
||||||
}).getOrElse(NotFound)
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/pulls/#list-commits-on-a-pull-request
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/pulls/:id/commits")(referrersOnly { repository =>
|
|
||||||
val owner = repository.owner
|
|
||||||
val name = repository.name
|
|
||||||
params("id").toIntOpt.flatMap{ issueId =>
|
|
||||||
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
|
|
||||||
using(Git.open(getRepositoryDir(owner, name))){ git =>
|
|
||||||
val oldId = git.getRepository.resolve(pullreq.commitIdFrom)
|
|
||||||
val newId = git.getRepository.resolve(pullreq.commitIdTo)
|
|
||||||
val repoFullName = RepositoryName(repository)
|
|
||||||
val commits = git.log.addRange(oldId, newId).call.iterator.asScala.map(c => ApiCommitListItem(new CommitInfo(c), repoFullName)).toList
|
|
||||||
JsonFormat(commits)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
ajaxGet("/:owner/:repository/pull/:id/mergeguide")(collaboratorsOnly { repository =>
|
|
||||||
params("id").toIntOpt.flatMap{ issueId =>
|
params("id").toIntOpt.flatMap{ issueId =>
|
||||||
val owner = repository.owner
|
val owner = repository.owner
|
||||||
val name = repository.name
|
val name = repository.name
|
||||||
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
|
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
|
||||||
val statuses = getCommitStatues(owner, name, pullreq.commitIdTo)
|
val hasConflict = LockUtil.lock(s"${owner}/${name}"){
|
||||||
val hasConfrict = LockUtil.lock(s"${owner}/${name}"){
|
|
||||||
checkConflict(owner, name, pullreq.branch, issueId)
|
checkConflict(owner, name, pullreq.branch, issueId)
|
||||||
}
|
}
|
||||||
val hasProblem = hasConfrict || (!statuses.isEmpty && CommitState.combine(statuses.map(_.state).toSet) != CommitState.SUCCESS)
|
val hasMergePermission = hasWritePermission(owner, name, context.loginAccount)
|
||||||
|
val branchProtection = getProtectedBranchInfo(owner, name, pullreq.branch)
|
||||||
|
val mergeStatus = PullRequestService.MergeStatus(
|
||||||
|
hasConflict = hasConflict,
|
||||||
|
commitStatues = getCommitStatues(owner, name, pullreq.commitIdTo),
|
||||||
|
branchProtection = branchProtection,
|
||||||
|
branchIsOutOfDate = JGitUtil.getShaByRef(owner, name, pullreq.branch) != Some(pullreq.commitIdFrom),
|
||||||
|
needStatusCheck = context.loginAccount.map{ u =>
|
||||||
|
branchProtection.needStatusCheck(u.userName)
|
||||||
|
}.getOrElse(true),
|
||||||
|
hasUpdatePermission = hasWritePermission(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount) &&
|
||||||
|
context.loginAccount.map{ u =>
|
||||||
|
!getProtectedBranchInfo(pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.requestBranch).needStatusCheck(u.userName)
|
||||||
|
}.getOrElse(false),
|
||||||
|
hasMergePermission = hasMergePermission,
|
||||||
|
commitIdTo = pullreq.commitIdTo)
|
||||||
html.mergeguide(
|
html.mergeguide(
|
||||||
hasConfrict,
|
mergeStatus,
|
||||||
hasProblem,
|
|
||||||
issue,
|
issue,
|
||||||
pullreq,
|
pullreq,
|
||||||
statuses,
|
|
||||||
repository,
|
repository,
|
||||||
getRepository(pullreq.requestUserName, pullreq.requestRepositoryName, context.baseUrl).get)
|
getRepository(pullreq.requestUserName, pullreq.requestRepositoryName).get)
|
||||||
}
|
}
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
})
|
})
|
||||||
@@ -203,6 +156,75 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
})
|
})
|
||||||
|
|
||||||
|
post("/:owner/:repository/pull/:id/update_branch")(referrersOnly { baseRepository =>
|
||||||
|
(for {
|
||||||
|
issueId <- params("id").toIntOpt
|
||||||
|
loginAccount <- context.loginAccount
|
||||||
|
(issue, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId)
|
||||||
|
owner = pullreq.requestUserName
|
||||||
|
name = pullreq.requestRepositoryName
|
||||||
|
if hasWritePermission(owner, name, context.loginAccount)
|
||||||
|
} yield {
|
||||||
|
val branchProtection = getProtectedBranchInfo(owner, name, pullreq.requestBranch)
|
||||||
|
if(branchProtection.needStatusCheck(loginAccount.userName)){
|
||||||
|
flash += "error" -> s"branch ${pullreq.requestBranch} is protected need status check."
|
||||||
|
} else {
|
||||||
|
val repository = getRepository(owner, name).get
|
||||||
|
LockUtil.lock(s"${owner}/${name}"){
|
||||||
|
val alias = if(pullreq.repositoryName == pullreq.requestRepositoryName && pullreq.userName == pullreq.requestUserName){
|
||||||
|
pullreq.branch
|
||||||
|
}else{
|
||||||
|
s"${pullreq.userName}:${pullreq.branch}"
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
s"Merge branch '${alias}' into ${pullreq.requestBranch}") match {
|
||||||
|
case None => // conflict
|
||||||
|
flash += "error" -> s"Can't automatic merging branch '${alias}' into ${pullreq.requestBranch}."
|
||||||
|
case Some(oldId) =>
|
||||||
|
// update pull request
|
||||||
|
updatePullRequests(owner, name, pullreq.requestBranch)
|
||||||
|
|
||||||
|
using(Git.open(Directory.getRepositoryDir(owner, name))) { git =>
|
||||||
|
// after update branch
|
||||||
|
|
||||||
|
val newCommitId = git.getRepository.resolve(s"refs/heads/${pullreq.requestBranch}")
|
||||||
|
val commits = git.log.addRange(oldId, newCommitId).call.iterator.asScala.map(c => new JGitUtil.CommitInfo(c)).toList
|
||||||
|
|
||||||
|
commits.foreach{ commit =>
|
||||||
|
if(!existIds.contains(commit.id)){
|
||||||
|
createIssueComment(owner, name, commit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// record activity
|
||||||
|
recordPushActivity(owner, name, loginAccount.userName, pullreq.branch, commits)
|
||||||
|
|
||||||
|
// close issue by commit message
|
||||||
|
if(pullreq.requestBranch == repository.repository.defaultBranch){
|
||||||
|
commits.map{ commit =>
|
||||||
|
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// call web hook
|
||||||
|
callPullRequestWebHookByRequestBranch("synchronize", repository, pullreq.requestBranch, baseUrl, loginAccount)
|
||||||
|
callWebHookOf(owner, name, WebHook.Push) {
|
||||||
|
for {
|
||||||
|
ownerAccount <- getAccountByUserName(owner)
|
||||||
|
} yield {
|
||||||
|
WebHookService.WebHookPushPayload(git, loginAccount, pullreq.requestBranch, repository, commits, ownerAccount, oldId = oldId, newId = newCommitId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flash += "info" -> s"Merge branch '${alias}' into ${pullreq.requestBranch}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||||
|
}
|
||||||
|
}) getOrElse NotFound
|
||||||
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/pull/:id/merge", mergeForm)(collaboratorsOnly { (form, repository) =>
|
post("/:owner/:repository/pull/:id/merge", mergeForm)(collaboratorsOnly { (form, repository) =>
|
||||||
params("id").toIntOpt.flatMap { issueId =>
|
params("id").toIntOpt.flatMap { issueId =>
|
||||||
val owner = repository.owner
|
val owner = repository.owner
|
||||||
@@ -228,7 +250,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.commitIdTo)
|
pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.commitIdTo)
|
||||||
|
|
||||||
// close issue by content of pull request
|
// close issue by content of pull request
|
||||||
val defaultBranch = getRepository(owner, name, context.baseUrl).get.repository.defaultBranch
|
val defaultBranch = getRepository(owner, name).get.repository.defaultBranch
|
||||||
if(pullreq.branch == defaultBranch){
|
if(pullreq.branch == defaultBranch){
|
||||||
commits.flatten.foreach { commit =>
|
commits.flatten.foreach { commit =>
|
||||||
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name)
|
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name)
|
||||||
@@ -261,7 +283,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
val headBranch:Option[String] = params.get("head")
|
val headBranch:Option[String] = params.get("head")
|
||||||
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
|
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
|
||||||
case (Some(originUserName), Some(originRepositoryName)) => {
|
case (Some(originUserName), Some(originRepositoryName)) => {
|
||||||
getRepository(originUserName, originRepositoryName, context.baseUrl).map { originRepository =>
|
getRepository(originUserName, originRepositoryName).map { originRepository =>
|
||||||
using(
|
using(
|
||||||
Git.open(getRepositoryDir(originUserName, originRepositoryName)),
|
Git.open(getRepositoryDir(originUserName, originRepositoryName)),
|
||||||
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
|
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
|
||||||
@@ -302,12 +324,12 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
forkedRepository.repository.originRepositoryName
|
forkedRepository.repository.originRepositoryName
|
||||||
} else {
|
} else {
|
||||||
// Sibling repository
|
// Sibling repository
|
||||||
getUserRepositories(originOwner, context.baseUrl).find { x =>
|
getUserRepositories(originOwner).find { x =>
|
||||||
x.repository.originUserName == forkedRepository.repository.originUserName &&
|
x.repository.originUserName == forkedRepository.repository.originUserName &&
|
||||||
x.repository.originRepositoryName == forkedRepository.repository.originRepositoryName
|
x.repository.originRepositoryName == forkedRepository.repository.originRepositoryName
|
||||||
}.map(_.repository.repositoryName)
|
}.map(_.repository.repositoryName)
|
||||||
};
|
};
|
||||||
originRepository <- getRepository(originOwner, originRepositoryName, context.baseUrl)
|
originRepository <- getRepository(originOwner, originRepositoryName)
|
||||||
) yield {
|
) yield {
|
||||||
using(
|
using(
|
||||||
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),
|
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),
|
||||||
@@ -375,7 +397,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
getForkedRepositories(forkedRepository.owner, forkedRepository.name).find(_._1 == originOwner).map(_._2)
|
getForkedRepositories(forkedRepository.owner, forkedRepository.name).find(_._1 == originOwner).map(_._2)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
originRepository <- getRepository(originOwner, originRepositoryName, context.baseUrl)
|
originRepository <- getRepository(originOwner, originRepositoryName)
|
||||||
) yield {
|
) yield {
|
||||||
using(
|
using(
|
||||||
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),
|
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),
|
||||||
@@ -441,7 +463,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
getIssue(owner, name, issueId.toString) foreach { issue =>
|
getIssue(owner, name, issueId.toString) foreach { issue =>
|
||||||
// extract references and create refer comment
|
// extract references and create refer comment
|
||||||
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""))
|
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""), context.loginAccount.get)
|
||||||
|
|
||||||
// notifications
|
// notifications
|
||||||
Notifier().toNotify(repository, issue, form.content.getOrElse("")){
|
Notifier().toNotify(repository, issue, form.content.getOrElse("")){
|
||||||
@@ -453,19 +475,6 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO Same method exists in IssueController. Should it moved to IssueService?
|
|
||||||
private def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String) = {
|
|
||||||
StringUtil.extractIssueId(message).foreach { issueId =>
|
|
||||||
val content = fromIssue.issueId + ":" + fromIssue.title
|
|
||||||
if(getIssue(owner, repository, issueId).isDefined){
|
|
||||||
// Not add if refer comment already exist.
|
|
||||||
if(!getComments(owner, repository, issueId.toInt).exists { x => x.action == "refer" && x.content == content }) {
|
|
||||||
createComment(owner, repository, context.loginAccount.get.userName, issueId.toInt, content, "refer")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses branch identifier and extracts owner and branch name as tuple.
|
* Parses branch identifier and extracts owner and branch name as tuple.
|
||||||
*
|
*
|
||||||
@@ -528,4 +537,5 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
repository,
|
repository,
|
||||||
hasWritePermission(owner, repoName, context.loginAccount))
|
hasWritePermission(owner, repoName, context.loginAccount))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,39 +2,46 @@ package gitbucket.core.controller
|
|||||||
|
|
||||||
import gitbucket.core.settings.html
|
import gitbucket.core.settings.html
|
||||||
import gitbucket.core.model.WebHook
|
import gitbucket.core.model.WebHook
|
||||||
import gitbucket.core.service.{RepositoryService, AccountService, WebHookService}
|
import gitbucket.core.service.{RepositoryService, AccountService, WebHookService, ProtectedBranchService, CommitStatusService}
|
||||||
import gitbucket.core.service.WebHookService._
|
import gitbucket.core.service.WebHookService._
|
||||||
import gitbucket.core.util._
|
import gitbucket.core.util._
|
||||||
import gitbucket.core.util.JGitUtil._
|
import gitbucket.core.util.JGitUtil._
|
||||||
import gitbucket.core.util.ControlUtil._
|
import gitbucket.core.util.ControlUtil._
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import io.github.gitbucket.scalatra.forms._
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import org.scalatra.i18n.Messages
|
import org.scalatra.i18n.Messages
|
||||||
import org.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
|
||||||
with RepositoryService with AccountService with WebHookService
|
with RepositoryService with AccountService with WebHookService with ProtectedBranchService with CommitStatusService
|
||||||
with OwnerAuthenticator with UsersAuthenticator
|
with OwnerAuthenticator with UsersAuthenticator
|
||||||
|
|
||||||
trait RepositorySettingsControllerBase extends ControllerBase {
|
trait RepositorySettingsControllerBase extends ControllerBase {
|
||||||
self: RepositoryService with AccountService with WebHookService
|
self: RepositoryService with AccountService with WebHookService with ProtectedBranchService with CommitStatusService
|
||||||
with OwnerAuthenticator with UsersAuthenticator =>
|
with OwnerAuthenticator with UsersAuthenticator =>
|
||||||
|
|
||||||
// for repository options
|
// for repository options
|
||||||
case class OptionsForm(repositoryName: String, description: Option[String], defaultBranch: String, isPrivate: Boolean)
|
case class OptionsForm(repositoryName: String, description: Option[String], isPrivate: Boolean)
|
||||||
|
|
||||||
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()))),
|
||||||
"defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100)))),
|
|
||||||
"isPrivate" -> trim(label("Repository Type", boolean()))
|
"isPrivate" -> trim(label("Repository Type", boolean()))
|
||||||
)(OptionsForm.apply)
|
)(OptionsForm.apply)
|
||||||
|
|
||||||
|
// for default branch
|
||||||
|
case class DefaultBranchForm(defaultBranch: String)
|
||||||
|
|
||||||
|
val defaultBranchForm = mapping(
|
||||||
|
"defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100))))
|
||||||
|
)(DefaultBranchForm.apply)
|
||||||
|
|
||||||
// for collaborator addition
|
// for collaborator addition
|
||||||
case class CollaboratorForm(userName: String)
|
case class CollaboratorForm(userName: String)
|
||||||
|
|
||||||
@@ -43,12 +50,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])
|
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,
|
||||||
)(WebHookForm.apply)
|
"ctype" -> label("ctype", text()),
|
||||||
|
"token" -> optional(trim(label("token", text(maxlength(100)))))
|
||||||
|
)(
|
||||||
|
(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)
|
||||||
@@ -75,12 +86,10 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
* Save the repository options.
|
* Save the repository options.
|
||||||
*/
|
*/
|
||||||
post("/:owner/:repository/settings/options", optionsForm)(ownerOnly { (form, repository) =>
|
post("/:owner/:repository/settings/options", optionsForm)(ownerOnly { (form, repository) =>
|
||||||
val defaultBranch = if(repository.branchList.isEmpty) "master" else form.defaultBranch
|
|
||||||
saveRepositoryOptions(
|
saveRepositoryOptions(
|
||||||
repository.owner,
|
repository.owner,
|
||||||
repository.name,
|
repository.name,
|
||||||
form.description,
|
form.description,
|
||||||
defaultBranch,
|
|
||||||
repository.repository.parentUserName.map { _ =>
|
repository.repository.parentUserName.map { _ =>
|
||||||
repository.repository.isPrivate
|
repository.repository.isPrivate
|
||||||
} getOrElse form.isPrivate
|
} getOrElse form.isPrivate
|
||||||
@@ -98,14 +107,45 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
FileUtils.moveDirectory(dir, getWikiRepositoryDir(repository.owner, form.repositoryName))
|
FileUtils.moveDirectory(dir, getWikiRepositoryDir(repository.owner, form.repositoryName))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Change repository HEAD
|
|
||||||
using(Git.open(getRepositoryDir(repository.owner, form.repositoryName))) { git =>
|
|
||||||
git.getRepository.updateRef(Constants.HEAD, true).link(Constants.R_HEADS + defaultBranch)
|
|
||||||
}
|
|
||||||
flash += "info" -> "Repository settings has been updated."
|
flash += "info" -> "Repository settings has been updated."
|
||||||
redirect(s"/${repository.owner}/${form.repositoryName}/settings/options")
|
redirect(s"/${repository.owner}/${form.repositoryName}/settings/options")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/** branch settings */
|
||||||
|
get("/:owner/:repository/settings/branches")(ownerOnly { repository =>
|
||||||
|
val protecteions = getProtectedBranchList(repository.owner, repository.name)
|
||||||
|
html.branches(repository, protecteions, flash.get("info"))
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Update default branch */
|
||||||
|
post("/:owner/:repository/settings/update_default_branch", defaultBranchForm)(ownerOnly { (form, repository) =>
|
||||||
|
if(repository.branchList.find(_ == form.defaultBranch).isEmpty){
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/settings/options")
|
||||||
|
} else {
|
||||||
|
saveRepositoryDefaultBranch(repository.owner, repository.name, form.defaultBranch)
|
||||||
|
// Change repository HEAD
|
||||||
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||||
|
git.getRepository.updateRef(Constants.HEAD, true).link(Constants.R_HEADS + form.defaultBranch)
|
||||||
|
}
|
||||||
|
flash += "info" -> "Repository default branch has been updated."
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/settings/branches")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/** Branch protection for branch */
|
||||||
|
get("/:owner/:repository/settings/branches/:branch")(ownerOnly { repository =>
|
||||||
|
import gitbucket.core.api._
|
||||||
|
val branch = params("branch")
|
||||||
|
if(repository.branchList.find(_ == branch).isEmpty){
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/settings/branches")
|
||||||
|
} else {
|
||||||
|
val protection = ApiBranchProtection(getProtectedBranchInfo(repository.owner, repository.name, branch))
|
||||||
|
val lastWeeks = getRecentStatuesContexts(repository.owner, repository.name, org.joda.time.LocalDateTime.now.minusWeeks(1).toDate).toSet
|
||||||
|
val knownContexts = (lastWeeks ++ protection.status.contexts).toSeq.sortBy(identity)
|
||||||
|
html.branchprotection(repository, branch, protection, knownContexts, flash.get("info"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the Collaborators page.
|
* Display the Collaborators page.
|
||||||
*/
|
*/
|
||||||
@@ -147,7 +187,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, "")
|
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)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -155,7 +195,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)
|
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")
|
||||||
})
|
})
|
||||||
@@ -184,7 +224,9 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
|
||||||
val url = params("url")
|
val url = params("url")
|
||||||
val dummyWebHookInfo = WebHook(repository.owner, repository.name, url)
|
val token = Some(params("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
|
||||||
@@ -243,7 +285,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)
|
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")
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package gitbucket.core.controller
|
|||||||
|
|
||||||
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
|
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
|
||||||
|
|
||||||
import gitbucket.core.api._
|
|
||||||
import gitbucket.core.plugin.PluginRegistry
|
import gitbucket.core.plugin.PluginRegistry
|
||||||
import gitbucket.core.repo.html
|
import gitbucket.core.repo.html
|
||||||
import gitbucket.core.helper
|
import gitbucket.core.helper
|
||||||
@@ -13,13 +12,12 @@ import gitbucket.core.util.StringUtil._
|
|||||||
import gitbucket.core.util.ControlUtil._
|
import gitbucket.core.util.ControlUtil._
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
import gitbucket.core.model.{Account, CommitState, WebHook}
|
import gitbucket.core.model.{Account, WebHook}
|
||||||
import gitbucket.core.service.CommitStatusService
|
|
||||||
import gitbucket.core.service.WebHookService._
|
import gitbucket.core.service.WebHookService._
|
||||||
import gitbucket.core.view
|
import gitbucket.core.view
|
||||||
import gitbucket.core.view.helpers
|
import gitbucket.core.view.helpers
|
||||||
|
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import io.github.gitbucket.scalatra.forms._
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import org.eclipse.jgit.api.{ArchiveCommand, Git}
|
import org.eclipse.jgit.api.{ArchiveCommand, Git}
|
||||||
import org.eclipse.jgit.archive.{TgzFormat, ZipFormat}
|
import org.eclipse.jgit.archive.{TgzFormat, ZipFormat}
|
||||||
@@ -34,7 +32,7 @@ import org.scalatra._
|
|||||||
class RepositoryViewerController extends RepositoryViewerControllerBase
|
class RepositoryViewerController extends RepositoryViewerControllerBase
|
||||||
with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
|
with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
|
||||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with CommitStatusService
|
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with CommitStatusService
|
||||||
with WebHookPullRequestService with WebHookPullRequestReviewCommentService
|
with WebHookPullRequestService with WebHookPullRequestReviewCommentService with ProtectedBranchService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The repository viewer.
|
* The repository viewer.
|
||||||
@@ -42,7 +40,7 @@ class RepositoryViewerController extends RepositoryViewerControllerBase
|
|||||||
trait RepositoryViewerControllerBase extends ControllerBase {
|
trait RepositoryViewerControllerBase extends ControllerBase {
|
||||||
self: RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
|
self: RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
|
||||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with CommitStatusService
|
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with CommitStatusService
|
||||||
with WebHookPullRequestService with WebHookPullRequestReviewCommentService =>
|
with WebHookPullRequestService with WebHookPullRequestReviewCommentService with ProtectedBranchService =>
|
||||||
|
|
||||||
ArchiveCommand.registerFormat("zip", new ZipFormat)
|
ArchiveCommand.registerFormat("zip", new ZipFormat)
|
||||||
ArchiveCommand.registerFormat("tar.gz", new TgzFormat)
|
ArchiveCommand.registerFormat("tar.gz", new TgzFormat)
|
||||||
@@ -111,6 +109,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
enableRefsLink = params("enableRefsLink").toBoolean,
|
enableRefsLink = params("enableRefsLink").toBoolean,
|
||||||
enableLineBreaks = params("enableLineBreaks").toBoolean,
|
enableLineBreaks = params("enableLineBreaks").toBoolean,
|
||||||
enableTaskList = params("enableTaskList").toBoolean,
|
enableTaskList = params("enableTaskList").toBoolean,
|
||||||
|
enableAnchor = false,
|
||||||
hasWritePermission = hasWritePermission(repository.owner, repository.name, context.loginAccount)
|
hasWritePermission = hasWritePermission(repository.owner, repository.name, context.loginAccount)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -122,13 +121,6 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
fileList(_)
|
fileList(_)
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/repos/#get
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository")(referrersOnly { repository =>
|
|
||||||
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(repository.owner).get)))
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the file list of the specified path and branch.
|
* Displays the file list of the specified path and branch.
|
||||||
*/
|
*/
|
||||||
@@ -160,73 +152,17 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/repos/statuses/#create-a-status
|
|
||||||
*/
|
|
||||||
post("/api/v3/repos/:owner/:repo/statuses/:sha")(collaboratorsOnly { repository =>
|
|
||||||
(for{
|
|
||||||
ref <- params.get("sha")
|
|
||||||
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
|
||||||
data <- extractFromJsonBody[CreateAStatus] if data.isValid
|
|
||||||
creator <- context.loginAccount
|
|
||||||
state <- CommitState.valueOf(data.state)
|
|
||||||
statusId = createCommitStatus(repository.owner, repository.name, sha, data.context.getOrElse("default"),
|
|
||||||
state, data.target_url, data.description, new java.util.Date(), creator)
|
|
||||||
status <- getCommitStatus(repository.owner, repository.name, statusId)
|
|
||||||
} yield {
|
|
||||||
JsonFormat(ApiCommitStatus(status, ApiUser(creator)))
|
|
||||||
}) getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref
|
|
||||||
*
|
|
||||||
* ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name.
|
|
||||||
*/
|
|
||||||
val listStatusesRoute = get("/api/v3/repos/:owner/:repo/commits/:ref/statuses")(referrersOnly { repository =>
|
|
||||||
(for{
|
|
||||||
ref <- params.get("ref")
|
|
||||||
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
|
||||||
} yield {
|
|
||||||
JsonFormat(getCommitStatuesWithCreator(repository.owner, repository.name, sha).map{ case(status, creator) =>
|
|
||||||
ApiCommitStatus(status, ApiUser(creator))
|
|
||||||
})
|
|
||||||
}) getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref
|
|
||||||
*
|
|
||||||
* legacy route
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repo/statuses/:ref"){
|
|
||||||
listStatusesRoute.action()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref
|
|
||||||
*
|
|
||||||
* ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name.
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repo/commits/:ref/status")(referrersOnly { repository =>
|
|
||||||
(for{
|
|
||||||
ref <- params.get("ref")
|
|
||||||
owner <- getAccountByUserName(repository.owner)
|
|
||||||
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
|
||||||
} yield {
|
|
||||||
val statuses = getCommitStatuesWithCreator(repository.owner, repository.name, sha)
|
|
||||||
JsonFormat(ApiCombinedCommitStatus(sha, statuses, ApiRepository(repository, owner)))
|
|
||||||
}) getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/new/*")(collaboratorsOnly { repository =>
|
get("/:owner/:repository/new/*")(collaboratorsOnly { repository =>
|
||||||
val (branch, path) = splitPath(repository, multiParams("splat").head)
|
val (branch, path) = splitPath(repository, multiParams("splat").head)
|
||||||
|
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")),
|
||||||
|
protectedBranch)
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/edit/*")(collaboratorsOnly { repository =>
|
get("/:owner/:repository/edit/*")(collaboratorsOnly { repository =>
|
||||||
val (branch, path) = splitPath(repository, multiParams("splat").head)
|
val (branch, path) = splitPath(repository, multiParams("splat").head)
|
||||||
|
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 =>
|
||||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
|
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
|
||||||
@@ -234,7 +170,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
getPathObjectId(git, path, revCommit).map { objectId =>
|
getPathObjectId(git, path, revCommit).map { objectId =>
|
||||||
val paths = path.split("/")
|
val paths = path.split("/")
|
||||||
html.editor(branch, repository, paths.take(paths.size - 1).toList, Some(paths.last),
|
html.editor(branch, repository, paths.take(paths.size - 1).toList, Some(paths.last),
|
||||||
JGitUtil.getContentInfo(git, path, objectId))
|
JGitUtil.getContentInfo(git, path, objectId),
|
||||||
|
protectedBranch)
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -303,7 +240,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
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 =>
|
||||||
JGitUtil.getObjectLoaderFromId(git, objectId){ loader =>
|
JGitUtil.getObjectLoaderFromId(git, objectId){ loader =>
|
||||||
contentType = "application/octet-stream"
|
contentType = FileUtil.getMimeType(path)
|
||||||
response.setContentLength(loader.getSize.toInt)
|
response.setContentLength(loader.getSize.toInt)
|
||||||
loader.copyTo(response.outputStream)
|
loader.copyTo(response.outputStream)
|
||||||
()
|
()
|
||||||
@@ -324,7 +261,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
if(raw){
|
if(raw){
|
||||||
// Download (This route is left for backword compatibility)
|
// Download (This route is left for backword compatibility)
|
||||||
JGitUtil.getObjectLoaderFromId(git, objectId){ loader =>
|
JGitUtil.getObjectLoaderFromId(git, objectId){ loader =>
|
||||||
contentType = "application/octet-stream"
|
contentType = FileUtil.getMimeType(path)
|
||||||
response.setContentLength(loader.getSize.toInt)
|
response.setContentLength(loader.getSize.toInt)
|
||||||
loader.copyTo(response.outputStream)
|
loader.copyTo(response.outputStream)
|
||||||
()
|
()
|
||||||
@@ -485,6 +422,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
* Displays branches.
|
* Displays branches.
|
||||||
*/
|
*/
|
||||||
get("/:owner/:repository/branches")(referrersOnly { repository =>
|
get("/:owner/:repository/branches")(referrersOnly { repository =>
|
||||||
|
val protectedBranches = getProtectedBranchList(repository.owner, repository.name).toSet
|
||||||
val branches = JGitUtil.getBranches(
|
val branches = JGitUtil.getBranches(
|
||||||
owner = repository.owner,
|
owner = repository.owner,
|
||||||
name = repository.name,
|
name = repository.name,
|
||||||
@@ -492,7 +430,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
origin = repository.repository.originUserName.isEmpty
|
origin = repository.repository.originUserName.isEmpty
|
||||||
)
|
)
|
||||||
.sortBy(br => (br.mergeInfo.isEmpty, br.commitTime))
|
.sortBy(br => (br.mergeInfo.isEmpty, br.commitTime))
|
||||||
.map(br => br -> getPullRequestByRequestCommit(repository.owner, repository.name, repository.repository.defaultBranch, br.name, br.commitId))
|
.map(br => (br, getPullRequestByRequestCommit(repository.owner, repository.name, repository.repository.defaultBranch, br.name, br.commitId), protectedBranches.contains(br.name)))
|
||||||
.reverse
|
.reverse
|
||||||
|
|
||||||
html.branches(branches, hasWritePermission(repository.owner, repository.name, context.loginAccount), repository)
|
html.branches(branches, hasWritePermission(repository.owner, repository.name, context.loginAccount), repository)
|
||||||
@@ -555,11 +493,14 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
html.forked(
|
html.forked(
|
||||||
getRepository(
|
getRepository(
|
||||||
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.baseUrl),
|
|
||||||
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)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -570,13 +511,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
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -629,7 +564,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
val parentPath = if (path == ".") Nil else path.split("/").toList
|
val parentPath = if (path == ".") Nil else path.split("/").toList
|
||||||
// process README.md or README.markdown
|
// process README.md or README.markdown
|
||||||
val readme = files.find { file =>
|
val readme = files.find { file =>
|
||||||
readmeFiles.contains(file.name.toLowerCase)
|
!file.isDirectory && readmeFiles.contains(file.name.toLowerCase)
|
||||||
}.map { file =>
|
}.map { file =>
|
||||||
val path = (file.name :: parentPath.reverse).reverse
|
val path = (file.name :: parentPath.reverse).reverse
|
||||||
path -> StringUtil.convertFromByteArray(JGitUtil.getContentFromId(
|
path -> StringUtil.convertFromByteArray(JGitUtil.getContentFromId(
|
||||||
@@ -638,10 +573,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),
|
||||||
@@ -683,7 +614,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
headName, loginAccount.fullName, loginAccount.mailAddress, message)
|
headName, loginAccount.fullName, loginAccount.mailAddress, message)
|
||||||
|
|
||||||
inserter.flush()
|
inserter.flush()
|
||||||
inserter.release()
|
inserter.close()
|
||||||
|
|
||||||
// update refs
|
// update refs
|
||||||
val refUpdate = git.getRepository.updateRef(headName)
|
val refUpdate = git.getRepository.updateRef(headName)
|
||||||
@@ -754,8 +685,6 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
.setTree(revCommit.getTree)
|
.setTree(revCommit.getTree)
|
||||||
.setOutputStream(response.getOutputStream)
|
.setOutputStream(response.getOutputStream)
|
||||||
.call()
|
.call()
|
||||||
|
|
||||||
Unit
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
package gitbucket.core.controller
|
|
||||||
|
|
||||||
import gitbucket.core.search.html
|
|
||||||
import gitbucket.core.service._
|
|
||||||
import gitbucket.core.util.{StringUtil, ControlUtil, ReferrerAuthenticator, Implicits}
|
|
||||||
import ControlUtil._
|
|
||||||
import Implicits._
|
|
||||||
import jp.sf.amateras.scalatra.forms._
|
|
||||||
|
|
||||||
class SearchController extends SearchControllerBase
|
|
||||||
with RepositoryService with AccountService with ActivityService with RepositorySearchService with IssuesService with ReferrerAuthenticator
|
|
||||||
|
|
||||||
trait SearchControllerBase extends ControllerBase { self: RepositoryService
|
|
||||||
with ActivityService with RepositorySearchService with ReferrerAuthenticator =>
|
|
||||||
|
|
||||||
val searchForm = mapping(
|
|
||||||
"query" -> trim(text(required)),
|
|
||||||
"owner" -> trim(text(required)),
|
|
||||||
"repository" -> trim(text(required))
|
|
||||||
)(SearchForm.apply)
|
|
||||||
|
|
||||||
case class SearchForm(query: String, owner: String, repository: String)
|
|
||||||
|
|
||||||
post("/search", searchForm){ form =>
|
|
||||||
redirect(s"/${form.owner}/${form.repository}/search?q=${StringUtil.urlEncode(form.query)}")
|
|
||||||
}
|
|
||||||
|
|
||||||
get("/:owner/:repository/search")(referrersOnly { repository =>
|
|
||||||
defining(params("q").trim, params.getOrElse("type", "code")){ case (query, target) =>
|
|
||||||
val page = try {
|
|
||||||
val i = params.getOrElse("page", "1").toInt
|
|
||||||
if(i <= 0) 1 else i
|
|
||||||
} catch {
|
|
||||||
case e: NumberFormatException => 1
|
|
||||||
}
|
|
||||||
|
|
||||||
target.toLowerCase match {
|
|
||||||
case "issue" => html.issues(
|
|
||||||
searchIssues(repository.owner, repository.name, query),
|
|
||||||
countFiles(repository.owner, repository.name, query),
|
|
||||||
query, page, repository)
|
|
||||||
|
|
||||||
case _ => html.code(
|
|
||||||
searchFiles(repository.owner, repository.name, query),
|
|
||||||
countIssues(repository.owner, repository.name, query),
|
|
||||||
query, page, repository)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,26 @@
|
|||||||
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}
|
import gitbucket.core.service.{AccountService, SystemSettingsService, RepositoryService}
|
||||||
import gitbucket.core.util.AdminAuthenticator
|
import gitbucket.core.util.AdminAuthenticator
|
||||||
import gitbucket.core.ssh.SshServer
|
import gitbucket.core.ssh.SshServer
|
||||||
|
import gitbucket.core.plugin.PluginRegistry
|
||||||
import SystemSettingsService._
|
import SystemSettingsService._
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.util.ControlUtil._
|
||||||
|
import gitbucket.core.util.Directory._
|
||||||
|
import gitbucket.core.util.StringUtil._
|
||||||
|
import io.github.gitbucket.scalatra.forms._
|
||||||
|
import org.apache.commons.io.{IOUtils, FileUtils}
|
||||||
|
import org.scalatra.i18n.Messages
|
||||||
|
|
||||||
class SystemSettingsController extends SystemSettingsControllerBase
|
class SystemSettingsController extends SystemSettingsControllerBase
|
||||||
with AccountService with AdminAuthenticator
|
with AccountService with RepositoryService with AdminAuthenticator
|
||||||
|
|
||||||
trait SystemSettingsControllerBase extends ControllerBase {
|
trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||||
self: AccountService with AdminAuthenticator =>
|
self: AccountService with RepositoryService with AdminAuthenticator =>
|
||||||
|
|
||||||
private val form = mapping(
|
private val form = mapping(
|
||||||
"baseUrl" -> trim(label("Base URL", optional(text()))),
|
"baseUrl" -> trim(label("Base URL", optional(text()))),
|
||||||
@@ -23,6 +32,7 @@ trait SystemSettingsControllerBase extends ControllerBase {
|
|||||||
"notification" -> trim(label("Notification", boolean())),
|
"notification" -> trim(label("Notification", boolean())),
|
||||||
"activityLogLimit" -> trim(label("Limit of activity logs", optional(number()))),
|
"activityLogLimit" -> trim(label("Limit of activity logs", optional(number()))),
|
||||||
"ssh" -> trim(label("SSH access", boolean())),
|
"ssh" -> trim(label("SSH access", boolean())),
|
||||||
|
"sshHost" -> trim(label("SSH host", optional(text()))),
|
||||||
"sshPort" -> trim(label("SSH port", optional(number()))),
|
"sshPort" -> trim(label("SSH port", optional(number()))),
|
||||||
"useSMTP" -> trim(label("SMTP", boolean())),
|
"useSMTP" -> trim(label("SMTP", boolean())),
|
||||||
"smtp" -> optionalIfNotChecked("useSMTP", mapping(
|
"smtp" -> optionalIfNotChecked("useSMTP", mapping(
|
||||||
@@ -50,9 +60,14 @@ trait SystemSettingsControllerBase extends ControllerBase {
|
|||||||
"keystore" -> trim(label("Keystore", optional(text())))
|
"keystore" -> trim(label("Keystore", optional(text())))
|
||||||
)(Ldap.apply))
|
)(Ldap.apply))
|
||||||
)(SystemSettings.apply).verifying { settings =>
|
)(SystemSettings.apply).verifying { settings =>
|
||||||
if(settings.ssh && settings.baseUrl.isEmpty){
|
Vector(
|
||||||
Seq("baseUrl" -> "Base URL is required if SSH access is enabled.")
|
if(settings.ssh && settings.baseUrl.isEmpty){
|
||||||
} else Nil
|
Some("baseUrl" -> "Base URL is required if SSH access is enabled.")
|
||||||
|
} else None,
|
||||||
|
if(settings.ssh && settings.sshHost.isEmpty){
|
||||||
|
Some("sshHost" -> "SSH host is required if SSH access is enabled.")
|
||||||
|
} else None
|
||||||
|
).flatten
|
||||||
}
|
}
|
||||||
|
|
||||||
private val pluginForm = mapping(
|
private val pluginForm = mapping(
|
||||||
@@ -61,6 +76,62 @@ trait SystemSettingsControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
case class PluginForm(pluginIds: List[String])
|
case class PluginForm(pluginIds: List[String])
|
||||||
|
|
||||||
|
case class DataExportForm(tableNames: List[String])
|
||||||
|
|
||||||
|
case class NewUserForm(userName: String, password: String, fullName: String,
|
||||||
|
mailAddress: String, isAdmin: Boolean,
|
||||||
|
url: Option[String], fileId: Option[String])
|
||||||
|
|
||||||
|
case class EditUserForm(userName: String, password: Option[String], fullName: String,
|
||||||
|
mailAddress: String, isAdmin: Boolean, url: Option[String],
|
||||||
|
fileId: Option[String], clearImage: Boolean, isRemoved: Boolean)
|
||||||
|
|
||||||
|
case class NewGroupForm(groupName: String, url: Option[String], fileId: Option[String],
|
||||||
|
members: String)
|
||||||
|
|
||||||
|
case class EditGroupForm(groupName: String, url: Option[String], fileId: Option[String],
|
||||||
|
members: String, clearImage: Boolean, isRemoved: Boolean)
|
||||||
|
|
||||||
|
|
||||||
|
val newUserForm = mapping(
|
||||||
|
"userName" -> trim(label("Username" ,text(required, maxlength(100), identifier, uniqueUserName))),
|
||||||
|
"password" -> trim(label("Password" ,text(required, maxlength(20)))),
|
||||||
|
"fullName" -> trim(label("Full Name" ,text(required, maxlength(100)))),
|
||||||
|
"mailAddress" -> trim(label("Mail Address" ,text(required, maxlength(100), uniqueMailAddress()))),
|
||||||
|
"isAdmin" -> trim(label("User Type" ,boolean())),
|
||||||
|
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
||||||
|
"fileId" -> trim(label("File ID" ,optional(text())))
|
||||||
|
)(NewUserForm.apply)
|
||||||
|
|
||||||
|
val editUserForm = mapping(
|
||||||
|
"userName" -> trim(label("Username" ,text(required, maxlength(100), identifier))),
|
||||||
|
"password" -> trim(label("Password" ,optional(text(maxlength(20))))),
|
||||||
|
"fullName" -> trim(label("Full Name" ,text(required, maxlength(100)))),
|
||||||
|
"mailAddress" -> trim(label("Mail Address" ,text(required, maxlength(100), uniqueMailAddress("userName")))),
|
||||||
|
"isAdmin" -> trim(label("User Type" ,boolean())),
|
||||||
|
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
||||||
|
"fileId" -> trim(label("File ID" ,optional(text()))),
|
||||||
|
"clearImage" -> trim(label("Clear image" ,boolean())),
|
||||||
|
"removed" -> trim(label("Disable" ,boolean(disableByNotYourself("userName"))))
|
||||||
|
)(EditUserForm.apply)
|
||||||
|
|
||||||
|
val newGroupForm = mapping(
|
||||||
|
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier, uniqueUserName))),
|
||||||
|
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
||||||
|
"fileId" -> trim(label("File ID" ,optional(text()))),
|
||||||
|
"members" -> trim(label("Members" ,text(required, members)))
|
||||||
|
)(NewGroupForm.apply)
|
||||||
|
|
||||||
|
val editGroupForm = mapping(
|
||||||
|
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier))),
|
||||||
|
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
||||||
|
"fileId" -> trim(label("File ID" ,optional(text()))),
|
||||||
|
"members" -> trim(label("Members" ,text(required, members))),
|
||||||
|
"clearImage" -> trim(label("Clear image" ,boolean())),
|
||||||
|
"removed" -> trim(label("Disable" ,boolean()))
|
||||||
|
)(EditGroupForm.apply)
|
||||||
|
|
||||||
|
|
||||||
get("/admin/system")(adminOnly {
|
get("/admin/system")(adminOnly {
|
||||||
html.system(flash.get("info"))
|
html.system(flash.get("info"))
|
||||||
})
|
})
|
||||||
@@ -68,20 +139,181 @@ trait SystemSettingsControllerBase extends ControllerBase {
|
|||||||
post("/admin/system", form)(adminOnly { form =>
|
post("/admin/system", form)(adminOnly { form =>
|
||||||
saveSystemSettings(form)
|
saveSystemSettings(form)
|
||||||
|
|
||||||
if(form.ssh && SshServer.isActive && context.settings.sshPort != form.sshPort){
|
if (form.sshAddress != context.settings.sshAddress) {
|
||||||
SshServer.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
if(form.ssh && !SshServer.isActive && form.baseUrl.isDefined){
|
|
||||||
SshServer.start(
|
|
||||||
form.sshPort.getOrElse(SystemSettingsService.DefaultSshPort),
|
|
||||||
form.baseUrl.get)
|
|
||||||
} else if(!form.ssh && SshServer.isActive){
|
|
||||||
SshServer.stop()
|
SshServer.stop()
|
||||||
|
for {
|
||||||
|
sshAddress <- form.sshAddress
|
||||||
|
baseUrl <- form.baseUrl
|
||||||
|
}
|
||||||
|
SshServer.start(sshAddress, baseUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
flash += "info" -> "System settings has been updated."
|
flash += "info" -> "System settings has been updated."
|
||||||
redirect("/admin/system")
|
redirect("/admin/system")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
get("/admin/plugins")(adminOnly {
|
||||||
|
html.plugins(PluginRegistry().getPlugins())
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
get("/admin/users")(adminOnly {
|
||||||
|
val includeRemoved = params.get("includeRemoved").map(_.toBoolean).getOrElse(false)
|
||||||
|
val users = getAllUsers(includeRemoved)
|
||||||
|
val members = users.collect { case account if(account.isGroupAccount) =>
|
||||||
|
account.userName -> getGroupMembers(account.userName).map(_.userName)
|
||||||
|
}.toMap
|
||||||
|
|
||||||
|
html.userlist(users, members, includeRemoved)
|
||||||
|
})
|
||||||
|
|
||||||
|
get("/admin/users/_newuser")(adminOnly {
|
||||||
|
html.user(None)
|
||||||
|
})
|
||||||
|
|
||||||
|
post("/admin/users/_newuser", newUserForm)(adminOnly { form =>
|
||||||
|
createAccount(form.userName, sha1(form.password), form.fullName, form.mailAddress, form.isAdmin, form.url)
|
||||||
|
updateImage(form.userName, form.fileId, false)
|
||||||
|
redirect("/admin/users")
|
||||||
|
})
|
||||||
|
|
||||||
|
get("/admin/users/:userName/_edituser")(adminOnly {
|
||||||
|
val userName = params("userName")
|
||||||
|
html.user(getAccountByUserName(userName, true))
|
||||||
|
})
|
||||||
|
|
||||||
|
post("/admin/users/:name/_edituser", editUserForm)(adminOnly { form =>
|
||||||
|
val userName = params("userName")
|
||||||
|
getAccountByUserName(userName, true).map { account =>
|
||||||
|
|
||||||
|
if(form.isRemoved){
|
||||||
|
// Remove repositories
|
||||||
|
// getRepositoryNamesOfUser(userName).foreach { repositoryName =>
|
||||||
|
// deleteRepository(userName, repositoryName)
|
||||||
|
// FileUtils.deleteDirectory(getRepositoryDir(userName, repositoryName))
|
||||||
|
// FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
|
||||||
|
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
|
||||||
|
// }
|
||||||
|
// Remove from GROUP_MEMBER, COLLABORATOR and REPOSITORY
|
||||||
|
removeUserRelatedData(userName)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAccount(account.copy(
|
||||||
|
password = form.password.map(sha1).getOrElse(account.password),
|
||||||
|
fullName = form.fullName,
|
||||||
|
mailAddress = form.mailAddress,
|
||||||
|
isAdmin = form.isAdmin,
|
||||||
|
url = form.url,
|
||||||
|
isRemoved = form.isRemoved))
|
||||||
|
|
||||||
|
updateImage(userName, form.fileId, form.clearImage)
|
||||||
|
redirect("/admin/users")
|
||||||
|
|
||||||
|
} getOrElse NotFound
|
||||||
|
})
|
||||||
|
|
||||||
|
get("/admin/users/_newgroup")(adminOnly {
|
||||||
|
html.usergroup(None, Nil)
|
||||||
|
})
|
||||||
|
|
||||||
|
post("/admin/users/_newgroup", newGroupForm)(adminOnly { form =>
|
||||||
|
createGroup(form.groupName, form.url)
|
||||||
|
updateGroupMembers(form.groupName, form.members.split(",").map {
|
||||||
|
_.split(":") match {
|
||||||
|
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
||||||
|
}
|
||||||
|
}.toList)
|
||||||
|
updateImage(form.groupName, form.fileId, false)
|
||||||
|
redirect("/admin/users")
|
||||||
|
})
|
||||||
|
|
||||||
|
get("/admin/users/:groupName/_editgroup")(adminOnly {
|
||||||
|
defining(params("groupName")){ groupName =>
|
||||||
|
html.usergroup(getAccountByUserName(groupName, true), getGroupMembers(groupName))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
post("/admin/users/:groupName/_editgroup", editGroupForm)(adminOnly { form =>
|
||||||
|
defining(params("groupName"), form.members.split(",").map {
|
||||||
|
_.split(":") match {
|
||||||
|
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
||||||
|
}
|
||||||
|
}.toList){ case (groupName, members) =>
|
||||||
|
getAccountByUserName(groupName, true).map { account =>
|
||||||
|
updateGroup(groupName, form.url, form.isRemoved)
|
||||||
|
|
||||||
|
if(form.isRemoved){
|
||||||
|
// Remove from GROUP_MEMBER
|
||||||
|
updateGroupMembers(form.groupName, Nil)
|
||||||
|
// Remove repositories
|
||||||
|
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
||||||
|
deleteRepository(groupName, repositoryName)
|
||||||
|
FileUtils.deleteDirectory(getRepositoryDir(groupName, repositoryName))
|
||||||
|
FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName))
|
||||||
|
FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Update GROUP_MEMBER
|
||||||
|
updateGroupMembers(form.groupName, members)
|
||||||
|
// Update COLLABORATOR for group repositories
|
||||||
|
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
||||||
|
removeCollaborators(form.groupName, repositoryName)
|
||||||
|
members.foreach { case (userName, isManager) =>
|
||||||
|
addCollaborator(form.groupName, repositoryName, userName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateImage(form.groupName, form.fileId, form.clearImage)
|
||||||
|
redirect("/admin/users")
|
||||||
|
|
||||||
|
} getOrElse NotFound
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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 session = request2Session(request)
|
||||||
|
val file = if(params("type") == "sql"){
|
||||||
|
session.conn.exportAsSQL(request.getParameterValues("tableNames").toSeq)
|
||||||
|
} else {
|
||||||
|
session.conn.exportAsXML(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(){
|
||||||
|
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||||
|
if(value.split(",").exists {
|
||||||
|
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
|
||||||
|
}) None else Some("Must select one manager at least.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected def disableByNotYourself(paramName: String): Constraint = new Constraint() {
|
||||||
|
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||||
|
params.get(paramName).flatMap { userName =>
|
||||||
|
if(userName == context.loginAccount.get.userName && params.get("removed") == Some("true"))
|
||||||
|
Some("You can't disable your account yourself")
|
||||||
|
else
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,204 +0,0 @@
|
|||||||
package gitbucket.core.controller
|
|
||||||
|
|
||||||
import gitbucket.core.service.{RepositoryService, AccountService}
|
|
||||||
import gitbucket.core.admin.users.html
|
|
||||||
import gitbucket.core.util._
|
|
||||||
import gitbucket.core.util.ControlUtil._
|
|
||||||
import gitbucket.core.util.StringUtil._
|
|
||||||
import gitbucket.core.util.Implicits._
|
|
||||||
import gitbucket.core.util.Directory._
|
|
||||||
import jp.sf.amateras.scalatra.forms._
|
|
||||||
import org.scalatra.i18n.Messages
|
|
||||||
import org.apache.commons.io.FileUtils
|
|
||||||
|
|
||||||
class UserManagementController extends UserManagementControllerBase
|
|
||||||
with AccountService with RepositoryService with AdminAuthenticator
|
|
||||||
|
|
||||||
trait UserManagementControllerBase extends AccountManagementControllerBase {
|
|
||||||
self: AccountService with RepositoryService with AdminAuthenticator =>
|
|
||||||
|
|
||||||
case class NewUserForm(userName: String, password: String, fullName: String,
|
|
||||||
mailAddress: String, isAdmin: Boolean,
|
|
||||||
url: Option[String], fileId: Option[String])
|
|
||||||
|
|
||||||
case class EditUserForm(userName: String, password: Option[String], fullName: String,
|
|
||||||
mailAddress: String, isAdmin: Boolean, url: Option[String],
|
|
||||||
fileId: Option[String], clearImage: Boolean, isRemoved: Boolean)
|
|
||||||
|
|
||||||
case class NewGroupForm(groupName: String, url: Option[String], fileId: Option[String],
|
|
||||||
members: String)
|
|
||||||
|
|
||||||
case class EditGroupForm(groupName: String, url: Option[String], fileId: Option[String],
|
|
||||||
members: String, clearImage: Boolean, isRemoved: Boolean)
|
|
||||||
|
|
||||||
val newUserForm = mapping(
|
|
||||||
"userName" -> trim(label("Username" ,text(required, maxlength(100), identifier, uniqueUserName))),
|
|
||||||
"password" -> trim(label("Password" ,text(required, maxlength(20)))),
|
|
||||||
"fullName" -> trim(label("Full Name" ,text(required, maxlength(100)))),
|
|
||||||
"mailAddress" -> trim(label("Mail Address" ,text(required, maxlength(100), uniqueMailAddress()))),
|
|
||||||
"isAdmin" -> trim(label("User Type" ,boolean())),
|
|
||||||
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
|
||||||
"fileId" -> trim(label("File ID" ,optional(text())))
|
|
||||||
)(NewUserForm.apply)
|
|
||||||
|
|
||||||
val editUserForm = mapping(
|
|
||||||
"userName" -> trim(label("Username" ,text(required, maxlength(100), identifier))),
|
|
||||||
"password" -> trim(label("Password" ,optional(text(maxlength(20))))),
|
|
||||||
"fullName" -> trim(label("Full Name" ,text(required, maxlength(100)))),
|
|
||||||
"mailAddress" -> trim(label("Mail Address" ,text(required, maxlength(100), uniqueMailAddress("userName")))),
|
|
||||||
"isAdmin" -> trim(label("User Type" ,boolean())),
|
|
||||||
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
|
||||||
"fileId" -> trim(label("File ID" ,optional(text()))),
|
|
||||||
"clearImage" -> trim(label("Clear image" ,boolean())),
|
|
||||||
"removed" -> trim(label("Disable" ,boolean(disableByNotYourself("userName"))))
|
|
||||||
)(EditUserForm.apply)
|
|
||||||
|
|
||||||
val newGroupForm = mapping(
|
|
||||||
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier, uniqueUserName))),
|
|
||||||
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
|
||||||
"fileId" -> trim(label("File ID" ,optional(text()))),
|
|
||||||
"members" -> trim(label("Members" ,text(required, members)))
|
|
||||||
)(NewGroupForm.apply)
|
|
||||||
|
|
||||||
val editGroupForm = mapping(
|
|
||||||
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier))),
|
|
||||||
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
|
||||||
"fileId" -> trim(label("File ID" ,optional(text()))),
|
|
||||||
"members" -> trim(label("Members" ,text(required, members))),
|
|
||||||
"clearImage" -> trim(label("Clear image" ,boolean())),
|
|
||||||
"removed" -> trim(label("Disable" ,boolean()))
|
|
||||||
)(EditGroupForm.apply)
|
|
||||||
|
|
||||||
get("/admin/users")(adminOnly {
|
|
||||||
val includeRemoved = params.get("includeRemoved").map(_.toBoolean).getOrElse(false)
|
|
||||||
val users = getAllUsers(includeRemoved)
|
|
||||||
val members = users.collect { case account if(account.isGroupAccount) =>
|
|
||||||
account.userName -> getGroupMembers(account.userName).map(_.userName)
|
|
||||||
}.toMap
|
|
||||||
|
|
||||||
html.list(users, members, includeRemoved)
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/admin/users/_newuser")(adminOnly {
|
|
||||||
html.user(None)
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/admin/users/_newuser", newUserForm)(adminOnly { form =>
|
|
||||||
createAccount(form.userName, sha1(form.password), form.fullName, form.mailAddress, form.isAdmin, form.url)
|
|
||||||
updateImage(form.userName, form.fileId, false)
|
|
||||||
redirect("/admin/users")
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/admin/users/:userName/_edituser")(adminOnly {
|
|
||||||
val userName = params("userName")
|
|
||||||
html.user(getAccountByUserName(userName, true))
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/admin/users/:name/_edituser", editUserForm)(adminOnly { form =>
|
|
||||||
val userName = params("userName")
|
|
||||||
getAccountByUserName(userName, true).map { account =>
|
|
||||||
|
|
||||||
if(form.isRemoved){
|
|
||||||
// Remove repositories
|
|
||||||
// getRepositoryNamesOfUser(userName).foreach { repositoryName =>
|
|
||||||
// deleteRepository(userName, repositoryName)
|
|
||||||
// FileUtils.deleteDirectory(getRepositoryDir(userName, repositoryName))
|
|
||||||
// FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
|
|
||||||
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
|
|
||||||
// }
|
|
||||||
// Remove from GROUP_MEMBER, COLLABORATOR and REPOSITORY
|
|
||||||
removeUserRelatedData(userName)
|
|
||||||
}
|
|
||||||
|
|
||||||
updateAccount(account.copy(
|
|
||||||
password = form.password.map(sha1).getOrElse(account.password),
|
|
||||||
fullName = form.fullName,
|
|
||||||
mailAddress = form.mailAddress,
|
|
||||||
isAdmin = form.isAdmin,
|
|
||||||
url = form.url,
|
|
||||||
isRemoved = form.isRemoved))
|
|
||||||
|
|
||||||
updateImage(userName, form.fileId, form.clearImage)
|
|
||||||
redirect("/admin/users")
|
|
||||||
|
|
||||||
} getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/admin/users/_newgroup")(adminOnly {
|
|
||||||
html.group(None, Nil)
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/admin/users/_newgroup", newGroupForm)(adminOnly { form =>
|
|
||||||
createGroup(form.groupName, form.url)
|
|
||||||
updateGroupMembers(form.groupName, form.members.split(",").map {
|
|
||||||
_.split(":") match {
|
|
||||||
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
|
||||||
}
|
|
||||||
}.toList)
|
|
||||||
updateImage(form.groupName, form.fileId, false)
|
|
||||||
redirect("/admin/users")
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/admin/users/:groupName/_editgroup")(adminOnly {
|
|
||||||
defining(params("groupName")){ groupName =>
|
|
||||||
html.group(getAccountByUserName(groupName, true), getGroupMembers(groupName))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/admin/users/:groupName/_editgroup", editGroupForm)(adminOnly { form =>
|
|
||||||
defining(params("groupName"), form.members.split(",").map {
|
|
||||||
_.split(":") match {
|
|
||||||
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
|
||||||
}
|
|
||||||
}.toList){ case (groupName, members) =>
|
|
||||||
getAccountByUserName(groupName, true).map { account =>
|
|
||||||
updateGroup(groupName, form.url, form.isRemoved)
|
|
||||||
|
|
||||||
if(form.isRemoved){
|
|
||||||
// Remove from GROUP_MEMBER
|
|
||||||
updateGroupMembers(form.groupName, Nil)
|
|
||||||
// Remove repositories
|
|
||||||
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
|
||||||
deleteRepository(groupName, repositoryName)
|
|
||||||
FileUtils.deleteDirectory(getRepositoryDir(groupName, repositoryName))
|
|
||||||
FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName))
|
|
||||||
FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Update GROUP_MEMBER
|
|
||||||
updateGroupMembers(form.groupName, members)
|
|
||||||
// Update COLLABORATOR for group repositories
|
|
||||||
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
|
||||||
removeCollaborators(form.groupName, repositoryName)
|
|
||||||
members.foreach { case (userName, isManager) =>
|
|
||||||
addCollaborator(form.groupName, repositoryName, userName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateImage(form.groupName, form.fileId, form.clearImage)
|
|
||||||
redirect("/admin/users")
|
|
||||||
|
|
||||||
} getOrElse NotFound
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
private def members: Constraint = new Constraint(){
|
|
||||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
|
||||||
if(value.split(",").exists {
|
|
||||||
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
|
|
||||||
}) None else Some("Must select one manager at least.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected def disableByNotYourself(paramName: String): Constraint = new Constraint() {
|
|
||||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
|
||||||
params.get(paramName).flatMap { userName =>
|
|
||||||
if(userName == context.loginAccount.get.userName && params.get("removed") == Some("true"))
|
|
||||||
Some("You can't disable your account yourself")
|
|
||||||
else
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,12 +7,13 @@ import gitbucket.core.util.StringUtil._
|
|||||||
import gitbucket.core.util.ControlUtil._
|
import gitbucket.core.util.ControlUtil._
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import io.github.gitbucket.scalatra.forms._
|
||||||
import org.eclipse.jgit.api.Git
|
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 +39,9 @@ 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, hasWritePermission(repository.owner, repository.name, context.loginAccount),
|
||||||
|
getWikiPage(repository.owner, repository.name, "_Sidebar"),
|
||||||
|
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")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -47,7 +50,9 @@ 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, hasWritePermission(repository.owner, repository.name, context.loginAccount),
|
||||||
|
getWikiPage(repository.owner, repository.name, "_Sidebar"),
|
||||||
|
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")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -124,7 +129,11 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
updateLastActivityDate(repository.owner, repository.name)
|
updateLastActivityDate(repository.owner, repository.name)
|
||||||
recordEditWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName, commitId)
|
recordEditWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName, commitId)
|
||||||
}
|
}
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
|
if(notReservedPageName(form.pageName)) {
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
|
||||||
|
} else {
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -140,7 +149,11 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
updateLastActivityDate(repository.owner, repository.name)
|
updateLastActivityDate(repository.owner, repository.name)
|
||||||
recordCreateWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName)
|
recordCreateWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName)
|
||||||
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
|
if(notReservedPageName(form.pageName)) {
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
|
||||||
|
} else {
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -186,13 +199,15 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||||
if(value.exists("\\/:*?\"<>|".contains(_))){
|
if(value.exists("\\/:*?\"<>|".contains(_))){
|
||||||
Some(s"${name} contains invalid character.")
|
Some(s"${name} contains invalid character.")
|
||||||
} else if(value.startsWith("_") || value.startsWith("-")){
|
} else if(notReservedPageName(value) && (value.startsWith("_") || value.startsWith("-"))){
|
||||||
Some(s"${name} starts with invalid character.")
|
Some(s"${name} starts with invalid character.")
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def notReservedPageName(value: String) = ! (Array[String]("_Sidebar","_Footer") contains value)
|
||||||
|
|
||||||
private def conflictForNew: Constraint = new Constraint(){
|
private def conflictForNew: 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] = {
|
||||||
targetWikiPage.map { _ =>
|
targetWikiPage.map { _ =>
|
||||||
|
|||||||
@@ -26,12 +26,16 @@ protected[model] trait TemplateComponent { self: Profile =>
|
|||||||
|
|
||||||
trait LabelTemplate extends BasicTemplate { self: Table[_] =>
|
trait LabelTemplate extends BasicTemplate { self: Table[_] =>
|
||||||
val labelId = column[Int]("LABEL_ID")
|
val labelId = column[Int]("LABEL_ID")
|
||||||
|
val labelName = column[String]("LABEL_NAME")
|
||||||
|
|
||||||
def byLabel(owner: String, repository: String, labelId: Int) =
|
def byLabel(owner: String, repository: String, labelId: Int) =
|
||||||
byRepository(owner, repository) && (this.labelId === labelId.bind)
|
byRepository(owner, repository) && (this.labelId === labelId.bind)
|
||||||
|
|
||||||
def byLabel(userName: Column[String], repositoryName: Column[String], labelId: Column[Int]) =
|
def byLabel(userName: Column[String], repositoryName: Column[String], labelId: Column[Int]) =
|
||||||
byRepository(userName, repositoryName) && (this.labelId === labelId)
|
byRepository(userName, repositoryName) && (this.labelId === labelId)
|
||||||
|
|
||||||
|
def byLabel(owner: String, repository: String, labelName: String) =
|
||||||
|
byRepository(owner, repository) && (this.labelName === labelName.bind)
|
||||||
}
|
}
|
||||||
|
|
||||||
trait MilestoneTemplate extends BasicTemplate { self: Table[_] =>
|
trait MilestoneTemplate extends BasicTemplate { self: Table[_] =>
|
||||||
@@ -54,4 +58,9 @@ protected[model] trait TemplateComponent { self: Profile =>
|
|||||||
byRepository(userName, repositoryName) && (this.commitId === commitId)
|
byRepository(userName, repositoryName) && (this.commitId === commitId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait BranchTemplate extends BasicTemplate{ self: Table[_] =>
|
||||||
|
val branch = column[String]("BRANCH")
|
||||||
|
def byBranch(owner: String, repository: String, branchName: String) = byRepository(owner, repository) && (branch === branchName.bind)
|
||||||
|
def byBranch(owner: Column[String], repository: Column[String], branchName: Column[String]) = byRepository(owner, repository) && (this.branch === branchName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ trait CommitStatusComponent extends TemplateComponent { self: Profile =>
|
|||||||
val creator = column[String]("CREATOR")
|
val creator = column[String]("CREATOR")
|
||||||
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||||
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||||
def * = (commitStatusId, userName, repositoryName, commitId, context, state, targetUrl, description, creator, registeredDate, updatedDate) <> (CommitStatus.tupled, CommitStatus.unapply)
|
def * = (commitStatusId, userName, repositoryName, commitId, context, state, targetUrl, description, creator, registeredDate, updatedDate) <> ((CommitStatus.apply _).tupled, CommitStatus.unapply)
|
||||||
def byPrimaryKey(id: Int) = commitStatusId === id.bind
|
def byPrimaryKey(id: Int) = commitStatusId === id.bind
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,20 @@ case class CommitStatus(
|
|||||||
registeredDate: java.util.Date,
|
registeredDate: java.util.Date,
|
||||||
updatedDate: java.util.Date
|
updatedDate: java.util.Date
|
||||||
)
|
)
|
||||||
|
object CommitStatus {
|
||||||
|
def pending(owner: String, repository: String, context: String) = CommitStatus(
|
||||||
|
commitStatusId = 0,
|
||||||
|
userName = owner,
|
||||||
|
repositoryName = repository,
|
||||||
|
commitId = "",
|
||||||
|
context = context,
|
||||||
|
state = CommitState.PENDING,
|
||||||
|
targetUrl = None,
|
||||||
|
description = Some("Waiting for status to be reported"),
|
||||||
|
creator = "",
|
||||||
|
registeredDate = new java.util.Date(),
|
||||||
|
updatedDate = new java.util.Date())
|
||||||
|
}
|
||||||
|
|
||||||
sealed abstract class CommitState(val name: String)
|
sealed abstract class CommitState(val name: String)
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ trait LabelComponent extends TemplateComponent { self: Profile =>
|
|||||||
|
|
||||||
class Labels(tag: Tag) extends Table[Label](tag, "LABEL") with LabelTemplate {
|
class Labels(tag: Tag) extends Table[Label](tag, "LABEL") with LabelTemplate {
|
||||||
override val labelId = column[Int]("LABEL_ID", O AutoInc)
|
override val labelId = column[Int]("LABEL_ID", O AutoInc)
|
||||||
val labelName = column[String]("LABEL_NAME")
|
override val labelName = column[String]("LABEL_NAME")
|
||||||
val color = column[String]("COLOR")
|
val color = column[String]("COLOR")
|
||||||
def * = (userName, repositoryName, labelId, labelName, color) <> (Label.tupled, Label.unapply)
|
def * = (userName, repositoryName, labelId, labelName, color) <> (Label.tupled, Label.unapply)
|
||||||
|
|
||||||
|
|||||||
@@ -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,6 +52,6 @@ trait CoreProfile extends ProfileProvider with Profile
|
|||||||
with SshKeyComponent
|
with SshKeyComponent
|
||||||
with WebHookComponent
|
with WebHookComponent
|
||||||
with WebHookEventComponent
|
with WebHookEventComponent
|
||||||
with PluginComponent
|
with ProtectedBranchComponent
|
||||||
|
|
||||||
object Profile extends CoreProfile
|
object Profile extends CoreProfile
|
||||||
|
|||||||
37
src/main/scala/gitbucket/core/model/ProtectedBranch.scala
Normal file
37
src/main/scala/gitbucket/core/model/ProtectedBranch.scala
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package gitbucket.core.model
|
||||||
|
|
||||||
|
import scala.slick.lifted.MappedTo
|
||||||
|
import scala.slick.jdbc._
|
||||||
|
|
||||||
|
trait ProtectedBranchComponent extends TemplateComponent { self: Profile =>
|
||||||
|
import profile.simple._
|
||||||
|
import self._
|
||||||
|
|
||||||
|
lazy val ProtectedBranches = TableQuery[ProtectedBranches]
|
||||||
|
class ProtectedBranches(tag: Tag) extends Table[ProtectedBranch](tag, "PROTECTED_BRANCH") with BranchTemplate {
|
||||||
|
val statusCheckAdmin = column[Boolean]("STATUS_CHECK_ADMIN")
|
||||||
|
def * = (userName, repositoryName, branch, statusCheckAdmin) <> (ProtectedBranch.tupled, ProtectedBranch.unapply)
|
||||||
|
def byPrimaryKey(userName: String, repositoryName: String, branch: String) = byBranch(userName, repositoryName, branch)
|
||||||
|
def byPrimaryKey(userName: Column[String], repositoryName: Column[String], branch: Column[String]) = byBranch(userName, repositoryName, branch)
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy val ProtectedBranchContexts = TableQuery[ProtectedBranchContexts]
|
||||||
|
class ProtectedBranchContexts(tag: Tag) extends Table[ProtectedBranchContext](tag, "PROTECTED_BRANCH_REQUIRE_CONTEXT") with BranchTemplate {
|
||||||
|
val context = column[String]("CONTEXT")
|
||||||
|
def * = (userName, repositoryName, branch, context) <> (ProtectedBranchContext.tupled, ProtectedBranchContext.unapply)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
case class ProtectedBranch(
|
||||||
|
userName: String,
|
||||||
|
repositoryName: String,
|
||||||
|
branch: String,
|
||||||
|
statusCheckAdmin: Boolean)
|
||||||
|
|
||||||
|
|
||||||
|
case class ProtectedBranchContext(
|
||||||
|
userName: String,
|
||||||
|
repositoryName: String,
|
||||||
|
branch: String,
|
||||||
|
context: String)
|
||||||
@@ -3,20 +3,43 @@ 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")
|
||||||
def * = (userName, repositoryName, url) <> ((WebHook.apply _).tupled, WebHook.unapply)
|
val token = column[Option[String]]("TOKEN", O.Nullable)
|
||||||
|
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]
|
||||||
)
|
)
|
||||||
|
|
||||||
object WebHook {
|
object WebHook {
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -67,6 +69,86 @@ trait Plugin {
|
|||||||
*/
|
*/
|
||||||
def repositoryRoutings(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[GitRepositoryRouting] = Nil
|
def repositoryRoutings(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[GitRepositoryRouting] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add receive hooks.
|
||||||
|
*/
|
||||||
|
val receiveHooks: Seq[ReceiveHook] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add receive hooks.
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
@@ -87,6 +169,30 @@ trait Plugin {
|
|||||||
(repositoryRoutings ++ repositoryRoutings(registry, context, settings)).foreach { routing =>
|
(repositoryRoutings ++ repositoryRoutings(registry, context, settings)).foreach { routing =>
|
||||||
registry.addRepositoryRouting(routing)
|
registry.addRepositoryRouting(routing)
|
||||||
}
|
}
|
||||||
|
(receiveHooks ++ receiveHooks(registry, context, settings)).foreach { 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,15 +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.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.model.Module
|
||||||
|
import liquibase.database.core.H2Database
|
||||||
import org.apache.commons.codec.binary.{Base64, StringUtils}
|
import org.apache.commons.codec.binary.{Base64, StringUtils}
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
@@ -29,6 +32,16 @@ class PluginRegistry {
|
|||||||
"md" -> MarkdownRenderer, "markdown" -> MarkdownRenderer
|
"md" -> MarkdownRenderer, "markdown" -> MarkdownRenderer
|
||||||
)
|
)
|
||||||
private val repositoryRoutings = new ListBuffer[GitRepositoryRouting]
|
private val repositoryRoutings = new ListBuffer[GitRepositoryRouting]
|
||||||
|
private val receiveHooks = new ListBuffer[ReceiveHook]
|
||||||
|
receiveHooks += new ProtectedBranchReceiveHook()
|
||||||
|
|
||||||
|
private val globalMenus = new ListBuffer[(Context) => Option[Link]]
|
||||||
|
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]]
|
||||||
|
|
||||||
def addPlugin(pluginInfo: PluginInfo): Unit = {
|
def addPlugin(pluginInfo: PluginInfo): Unit = {
|
||||||
plugins += pluginInfo
|
plugins += pluginInfo
|
||||||
@@ -98,17 +111,53 @@ class PluginRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private case class GlobalAction(
|
def addReceiveHook(commitHook: ReceiveHook): Unit = {
|
||||||
method: String,
|
receiveHooks += commitHook
|
||||||
path: String,
|
}
|
||||||
function: (HttpServletRequest, HttpServletResponse, Context) => Any
|
|
||||||
)
|
|
||||||
|
|
||||||
private case class RepositoryAction(
|
def getReceiveHooks: Seq[ReceiveHook] = receiveHooks.toSeq
|
||||||
method: String,
|
|
||||||
path: String,
|
def addGlobalMenu(globalMenu: (Context) => Option[Link]): Unit = {
|
||||||
function: (HttpServletRequest, HttpServletResponse, Context, RepositoryInfo) => Any
|
globalMenus += globalMenu
|
||||||
)
|
}
|
||||||
|
|
||||||
|
def getGlobalMenus: Seq[(Context) => Option[Link]] = globalMenus.toSeq
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,36 +189,21 @@ 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 =>
|
|
||||||
currentVersion.versionString match {
|
|
||||||
case "0.0" =>
|
|
||||||
conn.update("INSERT INTO PLUGIN (PLUGIN_ID, VERSION) VALUES (?, ?)", plugin.pluginId, headVersion.versionString)
|
|
||||||
case _ =>
|
|
||||||
conn.update("UPDATE PLUGIN SET VERSION = ? WHERE PLUGIN_ID = ?", headVersion.versionString, plugin.pluginId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
plugin.initialize(instance, context, settings)
|
plugin.initialize(instance, context, settings)
|
||||||
instance.addPlugin(PluginInfo(
|
instance.addPlugin(PluginInfo(
|
||||||
pluginId = plugin.pluginId,
|
pluginId = plugin.pluginId,
|
||||||
pluginName = plugin.pluginName,
|
pluginName = plugin.pluginName,
|
||||||
version = plugin.versions.head.versionString,
|
version = plugin.versions.head.getVersion,
|
||||||
description = plugin.description,
|
description = plugin.description,
|
||||||
pluginClass = plugin
|
pluginClass = plugin
|
||||||
))
|
))
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
case e: Exception => {
|
case e: Throwable => {
|
||||||
logger.error(s"Error during plugin initialization", e)
|
logger.error(s"Error during plugin initialization", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -192,6 +226,8 @@ 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,
|
||||||
|
|||||||
15
src/main/scala/gitbucket/core/plugin/ReceiveHook.scala
Normal file
15
src/main/scala/gitbucket/core/plugin/ReceiveHook.scala
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package gitbucket.core.plugin
|
||||||
|
|
||||||
|
import gitbucket.core.model.Profile._
|
||||||
|
import org.eclipse.jgit.transport.{ReceivePack, ReceiveCommand}
|
||||||
|
import profile.simple._
|
||||||
|
|
||||||
|
trait ReceiveHook {
|
||||||
|
|
||||||
|
def preReceive(owner: String, repository: String, receivePack: ReceivePack, command: ReceiveCommand, pusher: String)
|
||||||
|
(implicit session: Session): Option[String] = None
|
||||||
|
|
||||||
|
def postReceive(owner: String, repository: String, receivePack: ReceivePack, command: ReceiveCommand, pusher: String)
|
||||||
|
(implicit session: Session): Unit = ()
|
||||||
|
|
||||||
|
}
|
||||||
@@ -7,46 +7,51 @@ import gitbucket.core.model.{CommitState, CommitStatus, Account}
|
|||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util.StringUtil._
|
import gitbucket.core.util.StringUtil._
|
||||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
|
import org.joda.time.LocalDateTime
|
||||||
|
import gitbucket.core.model.Profile.dateColumnType
|
||||||
|
|
||||||
trait CommitStatusService {
|
trait CommitStatusService {
|
||||||
/** insert or update */
|
/** insert or update */
|
||||||
def createCommitStatus(userName: String, repositoryName: String, sha:String, context:String, state:CommitState, targetUrl:Option[String], description:Option[String], now:java.util.Date, creator:Account)(implicit s: Session): Int =
|
def createCommitStatus(userName: String, repositoryName: String, sha: String, context: String, state: CommitState,
|
||||||
CommitStatuses.filter(t => t.byCommit(userName, repositoryName, sha) && t.context===context.bind )
|
targetUrl: Option[String], description: Option[String], now: java.util.Date, creator: Account)(implicit s: Session): Int =
|
||||||
|
CommitStatuses
|
||||||
|
.filter(t => t.byCommit(userName, repositoryName, sha) && t.context === context.bind )
|
||||||
.map(_.commitStatusId).firstOption match {
|
.map(_.commitStatusId).firstOption match {
|
||||||
case Some(id:Int) => {
|
case Some(id: Int) => {
|
||||||
CommitStatuses.filter(_.byPrimaryKey(id)).map{
|
CommitStatuses.filter(_.byPrimaryKey(id)).map { t =>
|
||||||
t => (t.state , t.targetUrl , t.updatedDate , t.creator, t.description)
|
(t.state , t.targetUrl , t.updatedDate , t.creator, t.description)
|
||||||
}.update( (state, targetUrl, now, creator.userName, description) )
|
}.update((state, targetUrl, now, creator.userName, description))
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
case None => (CommitStatuses returning CommitStatuses.map(_.commitStatusId)) += CommitStatus(
|
case None => (CommitStatuses returning CommitStatuses.map(_.commitStatusId)) += CommitStatus(
|
||||||
userName = userName,
|
userName = userName,
|
||||||
repositoryName = repositoryName,
|
repositoryName = repositoryName,
|
||||||
commitId = sha,
|
commitId = sha,
|
||||||
context = context,
|
context = context,
|
||||||
state = state,
|
state = state,
|
||||||
targetUrl = targetUrl,
|
targetUrl = targetUrl,
|
||||||
description = description,
|
description = description,
|
||||||
creator = creator.userName,
|
creator = creator.userName,
|
||||||
registeredDate = now,
|
registeredDate = now,
|
||||||
updatedDate = now)
|
updatedDate = now)
|
||||||
}
|
}
|
||||||
|
|
||||||
def getCommitStatus(userName: String, repositoryName: String, id: Int)(implicit s: Session) :Option[CommitStatus] =
|
def getCommitStatus(userName: String, repositoryName: String, id: Int)(implicit s: Session) :Option[CommitStatus] =
|
||||||
CommitStatuses.filter(t => t.byPrimaryKey(id) && t.byRepository(userName, repositoryName)).firstOption
|
CommitStatuses.filter(t => t.byPrimaryKey(id) && t.byRepository(userName, repositoryName)).firstOption
|
||||||
|
|
||||||
def getCommitStatus(userName: String, repositoryName: String, sha: String, context: String)(implicit s: Session) :Option[CommitStatus] =
|
def getCommitStatus(userName: String, repositoryName: String, sha: String, context: String)(implicit s: Session) :Option[CommitStatus] =
|
||||||
CommitStatuses.filter(t => t.byCommit(userName, repositoryName, sha) && t.context===context.bind ).firstOption
|
CommitStatuses.filter(t => t.byCommit(userName, repositoryName, sha) && t.context === context.bind).firstOption
|
||||||
|
|
||||||
def getCommitStatues(userName: String, repositoryName: String, sha: String)(implicit s: Session) :List[CommitStatus] =
|
def getCommitStatues(userName: String, repositoryName: String, sha: String)(implicit s: Session) :List[CommitStatus] =
|
||||||
byCommitStatues(userName, repositoryName, sha).list
|
byCommitStatues(userName, repositoryName, sha).list
|
||||||
|
|
||||||
|
def getRecentStatuesContexts(userName: String, repositoryName: String, time: java.util.Date)(implicit s: Session) :List[String] =
|
||||||
|
CommitStatuses.filter(t => t.byRepository(userName, repositoryName)).filter(t => t.updatedDate > time.bind).groupBy(_.context).map(_._1).list
|
||||||
|
|
||||||
def getCommitStatuesWithCreator(userName: String, repositoryName: String, sha: String)(implicit s: Session) :List[(CommitStatus, Account)] =
|
def getCommitStatuesWithCreator(userName: String, repositoryName: String, sha: String)(implicit s: Session) :List[(CommitStatus, Account)] =
|
||||||
byCommitStatues(userName, repositoryName, sha).innerJoin(Accounts)
|
byCommitStatues(userName, repositoryName, sha).innerJoin(Accounts).filter { case (t, a) => t.creator === a.userName }.list
|
||||||
.filter{ case (t,a) => t.creator === a.userName }.list
|
|
||||||
|
|
||||||
protected def byCommitStatues(userName: String, repositoryName: String, sha: String)(implicit s: Session) =
|
protected def byCommitStatues(userName: String, repositoryName: String, sha: String)(implicit s: Session) =
|
||||||
CommitStatuses.filter(t => t.byCommit(userName, repositoryName, sha) ).sortBy(_.updatedDate desc)
|
CommitStatuses.filter(t => t.byCommit(userName, repositoryName, sha)).sortBy(_.updatedDate desc)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package gitbucket.core.service
|
||||||
|
|
||||||
|
import gitbucket.core.controller.Context
|
||||||
|
import gitbucket.core.model.Issue
|
||||||
|
import gitbucket.core.model.Profile._
|
||||||
|
import gitbucket.core.util.ControlUtil._
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.util.Notifier
|
||||||
|
import profile.simple._
|
||||||
|
|
||||||
|
trait HandleCommentService {
|
||||||
|
self: RepositoryService with IssuesService with ActivityService
|
||||||
|
with WebHookService with WebHookIssueCommentService with WebHookPullRequestService =>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see [[https://github.com/takezoe/gitbucket/wiki/CommentAction]]
|
||||||
|
*/
|
||||||
|
def handleComment(issue: Issue, content: Option[String], repository: RepositoryService.RepositoryInfo, actionOpt: Option[String])
|
||||||
|
(implicit context: Context, s: Session) = {
|
||||||
|
|
||||||
|
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||||
|
val userName = context.loginAccount.get.userName
|
||||||
|
|
||||||
|
val (action, recordActivity) = actionOpt
|
||||||
|
.collect {
|
||||||
|
case "close" if(!issue.closed) => true ->
|
||||||
|
(Some("close") -> Some(if(issue.isPullRequest) recordClosePullRequestActivity _ else recordCloseIssueActivity _))
|
||||||
|
case "reopen" if(issue.closed) => false ->
|
||||||
|
(Some("reopen") -> Some(recordReopenIssueActivity _))
|
||||||
|
}
|
||||||
|
.map { case (closed, t) =>
|
||||||
|
updateClosed(owner, name, issue.issueId, closed)
|
||||||
|
t
|
||||||
|
}
|
||||||
|
.getOrElse(None -> None)
|
||||||
|
|
||||||
|
val commentId = (content, action) match {
|
||||||
|
case (None, None) => None
|
||||||
|
case (None, Some(action)) => Some(createComment(owner, name, userName, issue.issueId, action.capitalize, action))
|
||||||
|
case (Some(content), _) => Some(createComment(owner, name, userName, issue.issueId, content, action.map(_+ "_comment").getOrElse("comment")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// record comment activity if comment is entered
|
||||||
|
content foreach {
|
||||||
|
(if(issue.isPullRequest) recordCommentPullRequestActivity _ else recordCommentIssueActivity _)
|
||||||
|
(owner, name, userName, issue.issueId, _)
|
||||||
|
}
|
||||||
|
recordActivity foreach ( _ (owner, name, userName, issue.issueId, issue.title) )
|
||||||
|
|
||||||
|
// extract references and create refer comment
|
||||||
|
content.map { content =>
|
||||||
|
createReferComment(owner, name, issue, content, context.loginAccount.get)
|
||||||
|
}
|
||||||
|
|
||||||
|
// call web hooks
|
||||||
|
action match {
|
||||||
|
case None => commentId.map{ commentIdSome => callIssueCommentWebHook(repository, issue, commentIdSome, context.loginAccount.get) }
|
||||||
|
case Some(act) => val webHookAction = act match {
|
||||||
|
case "open" => "opened"
|
||||||
|
case "reopen" => "reopened"
|
||||||
|
case "close" => "closed"
|
||||||
|
case _ => act
|
||||||
|
}
|
||||||
|
if(issue.isPullRequest){
|
||||||
|
callPullRequestWebHook(webHookAction, repository, issue.issueId, context.baseUrl, context.loginAccount.get)
|
||||||
|
} else {
|
||||||
|
callIssuesWebHook(webHookAction, repository, issue, context.baseUrl, context.loginAccount.get)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// notifications
|
||||||
|
Notifier() match {
|
||||||
|
case f =>
|
||||||
|
content foreach {
|
||||||
|
f.toNotify(repository, issue, _){
|
||||||
|
Notifier.msgComment(s"${context.baseUrl}/${owner}/${name}/${
|
||||||
|
if(issue.isPullRequest) "pull" else "issues"}/${issue.issueId}#comment-${commentId.get}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
action foreach {
|
||||||
|
f.toNotify(repository, issue, _){
|
||||||
|
Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/issues/${issue.issueId}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commentId.map( issue -> _ )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package gitbucket.core.service
|
package gitbucket.core.service
|
||||||
|
|
||||||
import gitbucket.core.model.Profile._
|
import gitbucket.core.model.Profile._
|
||||||
|
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||||
|
import gitbucket.core.util.StringUtil
|
||||||
import profile.simple._
|
import profile.simple._
|
||||||
|
|
||||||
import gitbucket.core.util.StringUtil._
|
import gitbucket.core.util.StringUtil._
|
||||||
@@ -12,6 +14,7 @@ import Q.interpolation
|
|||||||
|
|
||||||
|
|
||||||
trait IssuesService {
|
trait IssuesService {
|
||||||
|
self: AccountService =>
|
||||||
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) =
|
||||||
@@ -105,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
|
||||||
WHERE $issueIdQuery
|
COUNT(*) AS CS_SUCCESS,
|
||||||
GROUP BY PR.USER_NAME, PR.REPOSITORY_NAME, PR.ISSUE_ID) as SUMM
|
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
|
||||||
|
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
|
||||||
@@ -394,12 +413,35 @@ trait IssuesService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String, loginAccount: Account)(implicit s: Session) = {
|
||||||
|
StringUtil.extractIssueId(message).foreach { issueId =>
|
||||||
|
val content = fromIssue.issueId + ":" + fromIssue.title
|
||||||
|
if(getIssue(owner, repository, issueId).isDefined){
|
||||||
|
// Not add if refer comment already exist.
|
||||||
|
if(!getComments(owner, repository, issueId.toInt).exists { x => x.action == "refer" && x.content == content }) {
|
||||||
|
createComment(owner, repository, loginAccount.userName, issueId.toInt, content, "refer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def createIssueComment(owner: String, repository: String, commit: CommitInfo)(implicit s: Session) = {
|
||||||
|
StringUtil.extractIssueId(commit.fullMessage).foreach { issueId =>
|
||||||
|
if(getIssue(owner, repository, issueId).isDefined){
|
||||||
|
getAccountByMailAddress(commit.committerEmailAddress).foreach { account =>
|
||||||
|
createComment(owner, repository, account.userName, issueId.toInt, commit.fullMessage + " " + commit.id, "commit")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object IssuesService {
|
object IssuesService {
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
|
|
||||||
val IssueLimit = 30
|
val IssueLimit = 25
|
||||||
|
|
||||||
case class IssueSearchCondition(
|
case class IssueSearchCondition(
|
||||||
labels: Set[String] = Set.empty,
|
labels: Set[String] = Set.empty,
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ trait LabelsService {
|
|||||||
def getLabel(owner: String, repository: String, labelId: Int)(implicit s: Session): Option[Label] =
|
def getLabel(owner: String, repository: String, labelId: Int)(implicit s: Session): Option[Label] =
|
||||||
Labels.filter(_.byPrimaryKey(owner, repository, labelId)).firstOption
|
Labels.filter(_.byPrimaryKey(owner, repository, labelId)).firstOption
|
||||||
|
|
||||||
|
def getLabel(owner: String, repository: String, labelName: String)(implicit s: Session): Option[Label] =
|
||||||
|
Labels.filter(_.byLabel(owner, repository, labelName)).firstOption
|
||||||
|
|
||||||
def createLabel(owner: String, repository: String, labelName: String, color: String)(implicit s: Session): Int =
|
def createLabel(owner: String, repository: String, labelName: String, color: String)(implicit s: Session): Int =
|
||||||
Labels returning Labels.map(_.labelId) += Label(
|
Labels returning Labels.map(_.labelId) += Label(
|
||||||
userName = owner,
|
userName = owner,
|
||||||
|
|||||||
@@ -10,10 +10,9 @@ import org.eclipse.jgit.merge.MergeStrategy
|
|||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.transport.RefSpec
|
import org.eclipse.jgit.transport.RefSpec
|
||||||
import org.eclipse.jgit.errors.NoMergeBaseException
|
import org.eclipse.jgit.errors.NoMergeBaseException
|
||||||
import org.eclipse.jgit.lib.{ObjectId, CommitBuilder, PersonIdent}
|
import org.eclipse.jgit.lib.{ObjectId, CommitBuilder, PersonIdent, Repository}
|
||||||
import org.eclipse.jgit.revwalk.RevWalk
|
import org.eclipse.jgit.revwalk.RevWalk
|
||||||
|
|
||||||
|
|
||||||
trait MergeService {
|
trait MergeService {
|
||||||
import MergeService._
|
import MergeService._
|
||||||
/**
|
/**
|
||||||
@@ -52,26 +51,30 @@ trait MergeService {
|
|||||||
/**
|
/**
|
||||||
* Checks whether conflict will be caused in merging. Returns true if conflict will be caused.
|
* Checks whether conflict will be caused in merging. Returns true if conflict will be caused.
|
||||||
*/
|
*/
|
||||||
def checkConflict(userName: String, repositoryName: String, branch: String,
|
def tryMergeRemote(localUserName: String, localRepositoryName: String, localBranch: String,
|
||||||
requestUserName: String, requestRepositoryName: String, requestBranch: String): Boolean = {
|
remoteUserName: String, remoteRepositoryName: String, remoteBranch: String): Option[(ObjectId, ObjectId, ObjectId)] = {
|
||||||
using(Git.open(getRepositoryDir(requestUserName, requestRepositoryName))) { git =>
|
using(Git.open(getRepositoryDir(localUserName, localRepositoryName))) { git =>
|
||||||
val remoteRefName = s"refs/heads/${branch}"
|
val remoteRefName = s"refs/heads/${remoteBranch}"
|
||||||
val tmpRefName = s"refs/merge-check/${userName}/${branch}"
|
val tmpRefName = s"refs/remote-temp/${remoteUserName}/${remoteRepositoryName}/${remoteBranch}"
|
||||||
val refSpec = new RefSpec(s"${remoteRefName}:${tmpRefName}").setForceUpdate(true)
|
val refSpec = new RefSpec(s"${remoteRefName}:${tmpRefName}").setForceUpdate(true)
|
||||||
try {
|
try {
|
||||||
// fetch objects from origin repository branch
|
// fetch objects from origin repository branch
|
||||||
git.fetch
|
git.fetch
|
||||||
.setRemote(getRepositoryDir(userName, repositoryName).toURI.toString)
|
.setRemote(getRepositoryDir(remoteUserName, remoteRepositoryName).toURI.toString)
|
||||||
.setRefSpecs(refSpec)
|
.setRefSpecs(refSpec)
|
||||||
.call
|
.call
|
||||||
// merge conflict check
|
// merge conflict check
|
||||||
val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true)
|
val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true)
|
||||||
val mergeBaseTip = git.getRepository.resolve(s"refs/heads/${requestBranch}")
|
val mergeBaseTip = git.getRepository.resolve(s"refs/heads/${localBranch}")
|
||||||
val mergeTip = git.getRepository.resolve(tmpRefName)
|
val mergeTip = git.getRepository.resolve(tmpRefName)
|
||||||
try {
|
try {
|
||||||
!merger.merge(mergeBaseTip, mergeTip)
|
if(merger.merge(mergeBaseTip, mergeTip)){
|
||||||
|
Some((merger.getResultTreeId, mergeBaseTip, mergeTip))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
case e: NoMergeBaseException => true
|
case e: NoMergeBaseException => None
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
val refUpdate = git.getRepository.updateRef(refSpec.getDestination)
|
val refUpdate = git.getRepository.updateRef(refSpec.getDestination)
|
||||||
@@ -80,8 +83,54 @@ trait MergeService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Checks whether conflict will be caused in merging. Returns true if conflict will be caused.
|
||||||
|
*/
|
||||||
|
def checkConflict(userName: String, repositoryName: String, branch: String,
|
||||||
|
requestUserName: String, requestRepositoryName: String, requestBranch: String): Boolean =
|
||||||
|
tryMergeRemote(userName, repositoryName, branch, requestUserName, requestRepositoryName, requestBranch).isEmpty
|
||||||
|
|
||||||
|
def pullRemote(localUserName: String, localRepositoryName: String, localBranch: String,
|
||||||
|
remoteUserName: String, remoteRepositoryName: String, remoteBranch: String,
|
||||||
|
loginAccount: Account, message: String): Option[ObjectId] = {
|
||||||
|
tryMergeRemote(localUserName, localRepositoryName, localBranch, remoteUserName, remoteRepositoryName, remoteBranch).map{ case (newTreeId, oldBaseId, oldHeadId) =>
|
||||||
|
using(Git.open(getRepositoryDir(localUserName, localRepositoryName))) { git =>
|
||||||
|
val committer = new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
|
||||||
|
val newCommit = Util.createMergeCommit(git.getRepository, newTreeId, committer, message, Seq(oldBaseId, oldHeadId))
|
||||||
|
Util.updateRefs(git.getRepository, s"refs/heads/${localBranch}", newCommit, false, committer, Some("merge"))
|
||||||
|
}
|
||||||
|
oldBaseId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
object MergeService{
|
object MergeService{
|
||||||
|
object Util{
|
||||||
|
// return treeId
|
||||||
|
def createMergeCommit(repository: Repository, treeId: ObjectId, committer: PersonIdent, message: String, parents: Seq[ObjectId]): ObjectId = {
|
||||||
|
val mergeCommit = new CommitBuilder()
|
||||||
|
mergeCommit.setTreeId(treeId)
|
||||||
|
mergeCommit.setParentIds(parents:_*)
|
||||||
|
mergeCommit.setAuthor(committer)
|
||||||
|
mergeCommit.setCommitter(committer)
|
||||||
|
mergeCommit.setMessage(message)
|
||||||
|
// insertObject and got mergeCommit Object Id
|
||||||
|
val inserter = repository.newObjectInserter
|
||||||
|
val mergeCommitId = inserter.insert(mergeCommit)
|
||||||
|
inserter.flush()
|
||||||
|
inserter.close()
|
||||||
|
mergeCommitId
|
||||||
|
}
|
||||||
|
def updateRefs(repository: Repository, ref: String, newObjectId: ObjectId, force: Boolean, committer: PersonIdent, refLogMessage: Option[String] = None):Unit = {
|
||||||
|
// update refs
|
||||||
|
val refUpdate = repository.updateRef(ref)
|
||||||
|
refUpdate.setNewObjectId(newObjectId)
|
||||||
|
refUpdate.setForceUpdate(force)
|
||||||
|
refUpdate.setRefLogIdent(committer)
|
||||||
|
refLogMessage.map(refUpdate.setRefLogMessage(_, true))
|
||||||
|
refUpdate.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
case class MergeCacheInfo(git:Git, branch:String, issueId:Int){
|
case class MergeCacheInfo(git:Git, branch:String, issueId:Int){
|
||||||
val repository = git.getRepository
|
val repository = git.getRepository
|
||||||
val mergedBranchName = s"refs/pull/${issueId}/merge"
|
val mergedBranchName = s"refs/pull/${issueId}/merge"
|
||||||
@@ -90,17 +139,17 @@ object MergeService{
|
|||||||
lazy val mergeTip = repository.resolve(s"refs/pull/${issueId}/head")
|
lazy val mergeTip = repository.resolve(s"refs/pull/${issueId}/head")
|
||||||
def checkConflictCache(): Option[Boolean] = {
|
def checkConflictCache(): Option[Boolean] = {
|
||||||
Option(repository.resolve(mergedBranchName)).flatMap{ merged =>
|
Option(repository.resolve(mergedBranchName)).flatMap{ merged =>
|
||||||
if(parseCommit( merged ).getParents().toSet == Set( mergeBaseTip, mergeTip )){
|
if(parseCommit(merged).getParents().toSet == Set( mergeBaseTip, mergeTip )){
|
||||||
// merged branch exists
|
// merged branch exists
|
||||||
Some(false)
|
Some(false)
|
||||||
}else{
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}.orElse(Option(repository.resolve(conflictedBranchName)).flatMap{ conflicted =>
|
}.orElse(Option(repository.resolve(conflictedBranchName)).flatMap{ conflicted =>
|
||||||
if(parseCommit( conflicted ).getParents().toSet == Set( mergeBaseTip, mergeTip )){
|
if(parseCommit(conflicted).getParents().toSet == Set( mergeBaseTip, mergeTip )){
|
||||||
// conflict branch exists
|
// conflict branch exists
|
||||||
Some(true)
|
Some(true)
|
||||||
}else{
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -120,17 +169,12 @@ object MergeService{
|
|||||||
def updateBranch(treeId:ObjectId, message:String, branchName:String){
|
def updateBranch(treeId:ObjectId, message:String, branchName:String){
|
||||||
// creates merge commit
|
// creates merge commit
|
||||||
val mergeCommitId = createMergeCommit(treeId, committer, message)
|
val mergeCommitId = createMergeCommit(treeId, committer, message)
|
||||||
// update refs
|
Util.updateRefs(repository, branchName, mergeCommitId, true, committer)
|
||||||
val refUpdate = repository.updateRef(branchName)
|
|
||||||
refUpdate.setNewObjectId(mergeCommitId)
|
|
||||||
refUpdate.setForceUpdate(true)
|
|
||||||
refUpdate.setRefLogIdent(committer)
|
|
||||||
refUpdate.update()
|
|
||||||
}
|
}
|
||||||
if(!conflicted){
|
if(!conflicted){
|
||||||
updateBranch(merger.getResultTreeId, s"Merge ${mergeTip.name} into ${mergeBaseTip.name}", mergedBranchName)
|
updateBranch(merger.getResultTreeId, s"Merge ${mergeTip.name} into ${mergeBaseTip.name}", mergedBranchName)
|
||||||
git.branchDelete().setForce(true).setBranchNames(conflictedBranchName).call()
|
git.branchDelete().setForce(true).setBranchNames(conflictedBranchName).call()
|
||||||
}else{
|
} else {
|
||||||
updateBranch(mergeTipCommit.getTree().getId(), s"can't merge ${mergeTip.name} into ${mergeBaseTip.name}", conflictedBranchName)
|
updateBranch(mergeTipCommit.getTree().getId(), s"can't merge ${mergeTip.name} into ${mergeBaseTip.name}", conflictedBranchName)
|
||||||
git.branchDelete().setForce(true).setBranchNames(mergedBranchName).call()
|
git.branchDelete().setForce(true).setBranchNames(mergedBranchName).call()
|
||||||
}
|
}
|
||||||
@@ -145,28 +189,12 @@ object MergeService{
|
|||||||
// creates merge commit
|
// creates merge commit
|
||||||
val mergeCommitId = createMergeCommit(mergeResultCommit.getTree().getId(), committer, message)
|
val mergeCommitId = createMergeCommit(mergeResultCommit.getTree().getId(), committer, message)
|
||||||
// update refs
|
// update refs
|
||||||
val refUpdate = repository.updateRef(s"refs/heads/${branch}")
|
Util.updateRefs(repository, s"refs/heads/${branch}", mergeCommitId, false, committer, Some("merged"))
|
||||||
refUpdate.setNewObjectId(mergeCommitId)
|
|
||||||
refUpdate.setForceUpdate(false)
|
|
||||||
refUpdate.setRefLogIdent(committer)
|
|
||||||
refUpdate.setRefLogMessage("merged", true)
|
|
||||||
refUpdate.update()
|
|
||||||
}
|
}
|
||||||
// return treeId
|
// return treeId
|
||||||
private def createMergeCommit(treeId:ObjectId, committer:PersonIdent, message:String) = {
|
private def createMergeCommit(treeId: ObjectId, committer: PersonIdent, message: String) =
|
||||||
val mergeCommit = new CommitBuilder()
|
Util.createMergeCommit(repository, treeId, committer, message, Seq[ObjectId](mergeBaseTip, mergeTip))
|
||||||
mergeCommit.setTreeId(treeId)
|
|
||||||
mergeCommit.setParentIds(Array[ObjectId](mergeBaseTip, mergeTip): _*)
|
|
||||||
mergeCommit.setAuthor(committer)
|
|
||||||
mergeCommit.setCommitter(committer)
|
|
||||||
mergeCommit.setMessage(message)
|
|
||||||
// insertObject and got mergeCommit Object Id
|
|
||||||
val inserter = repository.newObjectInserter
|
|
||||||
val mergeCommitId = inserter.insert(mergeCommit)
|
|
||||||
inserter.flush()
|
|
||||||
inserter.release()
|
|
||||||
mergeCommitId
|
|
||||||
}
|
|
||||||
private def parseCommit(id:ObjectId) = using(new RevWalk( repository ))(_.parseCommit(id))
|
private def parseCommit(id:ObjectId) = using(new RevWalk( repository ))(_.parseCommit(id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
|
||||||
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user