mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-02 03:26:06 +01:00
Compare commits
1556 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d3c7a0c61 | ||
|
|
7375ff9f97 | ||
|
|
5df6ec8985 | ||
|
|
9a8479ee58 | ||
|
|
73766f11eb | ||
|
|
a22878e2c5 | ||
|
|
1a2eb9d1e7 | ||
|
|
277ace3c8e | ||
|
|
40f376dbd9 | ||
|
|
444af0935e | ||
|
|
fb15fa0e43 | ||
|
|
bcd3e14870 | ||
|
|
c18702dcea | ||
|
|
1341ef9c52 | ||
|
|
f605a8d085 | ||
|
|
e0f7a7a3c6 | ||
|
|
1d72bed442 | ||
|
|
284b8e7c16 | ||
|
|
ff9fb24094 | ||
|
|
fde4448dd0 | ||
|
|
d16ce90a3d | ||
|
|
3ed5525956 | ||
|
|
855d1e12aa | ||
|
|
e03797a58f | ||
|
|
f0d38cf8ec | ||
|
|
2723580e17 | ||
|
|
1977aa481d | ||
|
|
4b36a8f831 | ||
|
|
96b56e38ba | ||
|
|
849d117ad3 | ||
|
|
8d57fca779 | ||
|
|
0dc867306b | ||
|
|
eefb4c01ec | ||
|
|
ccce499f7f | ||
|
|
9f11eaa4d3 | ||
|
|
7b85c0e55f | ||
|
|
7e92f1abd5 | ||
|
|
825f2518e9 | ||
|
|
def1e877db | ||
|
|
6acbd5b2cf | ||
|
|
73b7aef4a9 | ||
|
|
3d73e3922b | ||
|
|
224e44151f | ||
|
|
d9c1293985 | ||
|
|
849a40d4b5 | ||
|
|
177387e9b0 | ||
|
|
cacce54714 | ||
|
|
12082322ee | ||
|
|
7e0a5b7fec | ||
|
|
d2cf4afc81 | ||
|
|
cce62de075 | ||
|
|
d9450df7e9 | ||
|
|
41fc81fab6 | ||
|
|
aa35498bdd | ||
|
|
14becd0bd6 | ||
|
|
7390e21934 | ||
|
|
6b37967162 | ||
|
|
a03b9584ee | ||
|
|
34649dfeda | ||
|
|
bc84cfc2c8 | ||
|
|
bcba1f068b | ||
|
|
e6f30ef86b | ||
|
|
9e67999ef0 | ||
|
|
be75cef752 | ||
|
|
19ead71b48 | ||
|
|
7ebe1d6c62 | ||
|
|
2331b58b87 | ||
|
|
d495b04d85 | ||
|
|
751a8703ef | ||
|
|
1e6d26221d | ||
|
|
44a8e98c7b | ||
|
|
415519716e | ||
|
|
597f86dc7b | ||
|
|
579ed19949 | ||
|
|
9221bfa045 | ||
|
|
3057a31a6c | ||
|
|
d47ccf587c | ||
|
|
3e78d423ac | ||
|
|
0299cee5ec | ||
|
|
97ceffe689 | ||
|
|
9019d93449 | ||
|
|
32006e02c0 | ||
|
|
5ba0f6d51e | ||
|
|
c004d501f6 | ||
|
|
6067fa0fca | ||
|
|
e2c6658e59 | ||
|
|
0a6e50cbbe | ||
|
|
3732963d4b | ||
|
|
83bcbef6ce | ||
|
|
47cb4d1c49 | ||
|
|
32799cead7 | ||
|
|
dbedc2166b | ||
|
|
e86b404ca2 | ||
|
|
321a3a72f0 | ||
|
|
b512e7c390 | ||
|
|
ae7ead6272 | ||
|
|
db55719a6f | ||
|
|
4e1094e75b | ||
|
|
3fd97662f5 | ||
|
|
d6946b93c3 | ||
|
|
20217058fe | ||
|
|
e67365a19f | ||
|
|
c2f1817c6a | ||
|
|
4a948d9b01 | ||
|
|
377bc2703b | ||
|
|
196890b26f | ||
|
|
491fc2c164 | ||
|
|
1bed38f175 | ||
|
|
b124c31f65 | ||
|
|
8c588cbd66 | ||
|
|
334753b1ad | ||
|
|
f6e7401d1b | ||
|
|
ab564cc2d4 | ||
|
|
b10839a5c2 | ||
|
|
bb3323fb0e | ||
|
|
0b15ecbacd | ||
|
|
9f6afaed07 | ||
|
|
925420734e | ||
|
|
fb6bb12c52 | ||
|
|
22d12d0488 | ||
|
|
6888d959e1 | ||
|
|
65e66f52f6 | ||
|
|
225abfa126 | ||
|
|
876757f2d4 | ||
|
|
7e77398645 | ||
|
|
73c76a5a88 | ||
|
|
e5fca0d6cc | ||
|
|
eed7e5177f | ||
|
|
dd54ab31cb | ||
|
|
0085cb24ad | ||
|
|
6a758902ef | ||
|
|
0d81a9a9b6 | ||
|
|
6e4f6da633 | ||
|
|
15118ca5c1 | ||
|
|
8161560757 | ||
|
|
9ba564c864 | ||
|
|
06b5b92673 | ||
|
|
a417c373f1 | ||
|
|
5f5cc8d454 | ||
|
|
b9b6589bd7 | ||
|
|
b79f6a5fa0 | ||
|
|
0acbaeae86 | ||
|
|
bd046da3d0 | ||
|
|
a889ed7c46 | ||
|
|
e24684cb2b | ||
|
|
5f939c18b4 | ||
|
|
140f1eb31b | ||
|
|
d412dd5009 | ||
|
|
8643bfeb37 | ||
|
|
31b6adf0e5 | ||
|
|
f1ac2b3507 | ||
|
|
172af307a6 | ||
|
|
135e1ef73d | ||
|
|
da55bf6af3 | ||
|
|
883a9c8b17 | ||
|
|
7da89940e3 | ||
|
|
3233b0ae3c | ||
|
|
4c2ed09915 | ||
|
|
256b6c480f | ||
|
|
dc311837f9 | ||
|
|
92aec48c99 | ||
|
|
a6ada8c457 | ||
|
|
dcc601502e | ||
|
|
dd58d8c804 | ||
|
|
2ade54b7e3 | ||
|
|
136c5854f3 | ||
|
|
c597238d9c | ||
|
|
2552a58e08 | ||
|
|
74ad5872a3 | ||
|
|
485d502bd3 | ||
|
|
47bc8d030e | ||
|
|
48fe7133f7 | ||
|
|
5d962dc5e4 | ||
|
|
31e8e5a951 | ||
|
|
858373c628 | ||
|
|
7f142d2c0d | ||
|
|
08b86232a8 | ||
|
|
6bf4f42fdb | ||
|
|
f3c7de36d8 | ||
|
|
19f556de57 | ||
|
|
e4467df411 | ||
|
|
8d305a1fb1 | ||
|
|
b47153e645 | ||
|
|
c71766c84b | ||
|
|
23e4d679ae | ||
|
|
182acb2e02 | ||
|
|
b255b15006 | ||
|
|
b458f88161 | ||
|
|
398d8f2f1c | ||
|
|
85c1a56cbf | ||
|
|
da216c6960 | ||
|
|
bc91b153bf | ||
|
|
bc50b47d3a | ||
|
|
aed15a7f25 | ||
|
|
a1f09117b0 | ||
|
|
0a4a4a51ca | ||
|
|
f7fd53bf09 | ||
|
|
cbfb863a54 | ||
|
|
9d56d72611 | ||
|
|
527c91ff9d | ||
|
|
c58c2d6700 | ||
|
|
5518eca952 | ||
|
|
6e2b67ec0b | ||
|
|
837b1e44a7 | ||
|
|
e04c230c6e | ||
|
|
a01b5a4a59 | ||
|
|
427b6ce846 | ||
|
|
b7b5af2b72 | ||
|
|
39fec57f72 | ||
|
|
238dedb6df | ||
|
|
af091117b7 | ||
|
|
ddea4e12f0 | ||
|
|
9767903252 | ||
|
|
bc75f9f8a2 | ||
|
|
63627fc1d0 | ||
|
|
c23985c1a7 | ||
|
|
af58e99dcf | ||
|
|
676670e9e3 | ||
|
|
823c52e941 | ||
|
|
7f42007648 | ||
|
|
7214ef21d2 | ||
|
|
18a4492975 | ||
|
|
99f73b1016 | ||
|
|
0c1ce6a088 | ||
|
|
ae6291ab83 | ||
|
|
617fcf7c99 | ||
|
|
9df4a74837 | ||
|
|
966d4251be | ||
|
|
84b2e9cdcd | ||
|
|
e29d63c91a | ||
|
|
805d2b8e79 | ||
|
|
9983fd1292 | ||
|
|
1de202e927 | ||
|
|
4eb9f4a485 | ||
|
|
a8801e4e41 | ||
|
|
ee1c84dbf2 | ||
|
|
e40e1fa6cd | ||
|
|
055f648ea2 | ||
|
|
37a399c3a2 | ||
|
|
bc0b11b60a | ||
|
|
65a1ca7146 | ||
|
|
2293030d4e | ||
|
|
2848f07b83 | ||
|
|
55224ddcd8 | ||
|
|
054ae75b6b | ||
|
|
c83fab611e | ||
|
|
29baf1223c | ||
|
|
2a60f607ff | ||
|
|
78f4d26aa0 | ||
|
|
f59e86f5ca | ||
|
|
1c2af36c92 | ||
|
|
badbe73f4e | ||
|
|
a9d58698cd | ||
|
|
bb3f086aa6 | ||
|
|
2db674bb03 | ||
|
|
4bc4a16a80 | ||
|
|
d88a105628 | ||
|
|
15d0c5b506 | ||
|
|
dbde79d2f2 | ||
|
|
e6e3786b47 | ||
|
|
4c1b8004fc | ||
|
|
ff4052f097 | ||
|
|
13c206d068 | ||
|
|
5b875d7c73 | ||
|
|
e33dd9008b | ||
|
|
8764910553 | ||
|
|
4c89c40944 | ||
|
|
0f0986afcf | ||
|
|
5d5f1f8bdd | ||
|
|
03e386b3ce | ||
|
|
435eac7ae6 | ||
|
|
bd5df3977d | ||
|
|
ba218053f9 | ||
|
|
1fe448a83b | ||
|
|
26a45d0117 | ||
|
|
320585a530 | ||
|
|
ca0f888a99 | ||
|
|
3b08dc2e41 | ||
|
|
cc128a49c1 | ||
|
|
e0148695f2 | ||
|
|
afe0b1dd71 | ||
|
|
353852d6da | ||
|
|
28585d1a3d | ||
|
|
9d69a48c65 | ||
|
|
2f95c76634 | ||
|
|
eac9f0e6ff | ||
|
|
043fc21e05 | ||
|
|
5854a75615 | ||
|
|
7b02946496 | ||
|
|
70f0ffd4f4 | ||
|
|
91b82c2652 | ||
|
|
b1017140aa | ||
|
|
fc806b8813 | ||
|
|
836913482b | ||
|
|
b3df3f44c6 | ||
|
|
4ffbf89e74 | ||
|
|
9851c7d93d | ||
|
|
a10188260c | ||
|
|
2201f2b202 | ||
|
|
c92e71bb7a | ||
|
|
d271fac350 | ||
|
|
ce4522fc30 | ||
|
|
a178c48de6 | ||
|
|
9d1323a044 | ||
|
|
43babfed94 | ||
|
|
6fa7ea30fb | ||
|
|
d78315695b | ||
|
|
16021865cb | ||
|
|
b516be242d | ||
|
|
0124f7cc3c | ||
|
|
f3eec35287 | ||
|
|
fb396a33b0 | ||
|
|
3370499421 | ||
|
|
d847e27cf9 | ||
|
|
9684b158ce | ||
|
|
8456808a8e | ||
|
|
9747899a19 | ||
|
|
099304605e | ||
|
|
30994d0465 | ||
|
|
71fdbe7b71 | ||
|
|
86432c5ffe | ||
|
|
4dfa1fb0f8 | ||
|
|
db59a7652f | ||
|
|
417470a81c | ||
|
|
cc639da17e | ||
|
|
f619f4a9bc | ||
|
|
5dffc2a64e | ||
|
|
bb63a8d14c | ||
|
|
c1263cc16d | ||
|
|
49f2e7d70f | ||
|
|
f93b535f70 | ||
|
|
e16d3c823b | ||
|
|
7a6fdbcf50 | ||
|
|
46041a3762 | ||
|
|
20b0553f7f | ||
|
|
5870cacf44 | ||
|
|
cb512cd98d | ||
|
|
90487eb7b7 | ||
|
|
706fa77de3 | ||
|
|
26b14ded58 | ||
|
|
3b1367dd8e | ||
|
|
e1f310317d | ||
|
|
937814ec5d | ||
|
|
b55fc649a6 | ||
|
|
f4e4506517 | ||
|
|
287a0b6669 | ||
|
|
5bddd352af | ||
|
|
9c6ea8fb9d | ||
|
|
32e8bf46a7 | ||
|
|
d61fe1bf84 | ||
|
|
47dbea947d | ||
|
|
97c6b0495e | ||
|
|
a602ece8e9 | ||
|
|
cf6dca84d8 | ||
|
|
79432ff8ad | ||
|
|
b8613431de | ||
|
|
698eafa562 | ||
|
|
d33886db89 | ||
|
|
cde09d3a59 | ||
|
|
5674f0e980 | ||
|
|
b9ade60eb2 | ||
|
|
96303723fa | ||
|
|
0f5dbc5788 | ||
|
|
8df0c3a439 | ||
|
|
ca6a86816a | ||
|
|
3ea939798f | ||
|
|
d947410e3c | ||
|
|
db59bc08ac | ||
|
|
95a8649f79 | ||
|
|
ffd10122ed | ||
|
|
c4c39f36e9 | ||
|
|
96900c3cbf | ||
|
|
69fa370d12 | ||
|
|
7496437d11 | ||
|
|
33b7d09af7 | ||
|
|
53d0974760 | ||
|
|
a87399f223 | ||
|
|
975dfb17e1 | ||
|
|
8b8bd0289b | ||
|
|
3bb69c623b | ||
|
|
dd427bdbef | ||
|
|
b40657a14a | ||
|
|
21ca5b2eec | ||
|
|
b78d584d8a | ||
|
|
e6b666a66a | ||
|
|
bab93ea4f5 | ||
|
|
7fe98253ae | ||
|
|
13385cbced | ||
|
|
3f20cec7b2 | ||
|
|
a0e4b020ca | ||
|
|
ea5d898b27 | ||
|
|
4e652b5ccd | ||
|
|
dd809896c8 | ||
|
|
93536d3365 | ||
|
|
098b18fe6d | ||
|
|
66efdac757 | ||
|
|
45545d3815 | ||
|
|
b65d41731b | ||
|
|
be19e97518 | ||
|
|
2ebf2b99bd | ||
|
|
be79ac2eb2 | ||
|
|
05afec3236 | ||
|
|
57879eb72e | ||
|
|
2bc915f51b | ||
|
|
1ca55805b5 | ||
|
|
93cc1be166 | ||
|
|
f88ce3f671 | ||
|
|
20aabfc273 | ||
|
|
601f8c4249 | ||
|
|
d0ccfc52b8 | ||
|
|
c22aef8ee2 | ||
|
|
3807e61a48 | ||
|
|
55722f87af | ||
|
|
212f3725ed | ||
|
|
193a312b22 | ||
|
|
6a2d2ebfd1 | ||
|
|
82beed1f44 | ||
|
|
0ede7e9921 | ||
|
|
6d200aa340 | ||
|
|
a0fbb90048 | ||
|
|
08e29e7077 | ||
|
|
d2317d0a97 | ||
|
|
972628eb65 | ||
|
|
51a56356cb | ||
|
|
3bef71f5f2 | ||
|
|
2bb1f6168a | ||
|
|
b13820fc0e | ||
|
|
723de9e81e | ||
|
|
3e161353ed | ||
|
|
2a8706630a | ||
|
|
121b6ee641 | ||
|
|
34e299bf52 | ||
|
|
0822b7b5f3 | ||
|
|
618110327a | ||
|
|
f58f476060 | ||
|
|
f5a544603a | ||
|
|
89515cd087 | ||
|
|
37731c4163 | ||
|
|
1d4720d784 | ||
|
|
a10b053489 | ||
|
|
6122c8a1e1 | ||
|
|
fa9254c240 | ||
|
|
10616bca7d | ||
|
|
307f7e15e9 | ||
|
|
86cf97d76b | ||
|
|
01f6590c04 | ||
|
|
8f0c22bae9 | ||
|
|
652a68c5b1 | ||
|
|
1f56e1360d | ||
|
|
38475ffefe | ||
|
|
7a44a4d726 | ||
|
|
9dbc0c3fd6 | ||
|
|
56bb43ea6b | ||
|
|
b287c1f60d | ||
|
|
258d53b7a6 | ||
|
|
2e11d6dd78 | ||
|
|
a2a2e22485 | ||
|
|
c182cde14b | ||
|
|
104c3bc89d | ||
|
|
2668977918 | ||
|
|
28424c96c4 | ||
|
|
9cfa8c594b | ||
|
|
5c70cd654c | ||
|
|
7aca24e51d | ||
|
|
cce0b67871 | ||
|
|
606cd83f44 | ||
|
|
32897c36f9 | ||
|
|
92e4e12655 | ||
|
|
c8e5b75165 | ||
|
|
09b9a52ad3 | ||
|
|
33378c6464 | ||
|
|
259bcfc14f | ||
|
|
c361d24ba4 | ||
|
|
d5e1b18b52 | ||
|
|
684a17a15b | ||
|
|
66b7b69d20 | ||
|
|
57254f6366 | ||
|
|
c64909ab1a | ||
|
|
34dd8541f4 | ||
|
|
50b4fb154d | ||
|
|
0b3781ec8a | ||
|
|
0e1d184715 | ||
|
|
d8c27046f6 | ||
|
|
fd09058a7d | ||
|
|
1c99b57709 | ||
|
|
9ee739d102 | ||
|
|
e2cde81b72 | ||
|
|
84a4b8fd92 | ||
|
|
d2c94909cb | ||
|
|
3683a5fb7d | ||
|
|
1223bf2fd8 | ||
|
|
a9bfe0dfab | ||
|
|
9af81c7093 | ||
|
|
1e8a5c3cde | ||
|
|
707ad866e1 | ||
|
|
c3a944b40e | ||
|
|
ab80cb8f60 | ||
|
|
4f45e047d2 | ||
|
|
bbe455ac49 | ||
|
|
b5f173fa46 | ||
|
|
4bd6ef143a | ||
|
|
fd4a696303 | ||
|
|
4af4c4e7c6 | ||
|
|
3b2e42fd61 | ||
|
|
b07d0b028f | ||
|
|
f3900ca8f9 | ||
|
|
62d43f120a | ||
|
|
c4f69fbd13 | ||
|
|
fece20ff40 | ||
|
|
bbef4b22ca | ||
|
|
481a2d213f | ||
|
|
8ed4075f1e | ||
|
|
9bf82733d1 | ||
|
|
30d66f95bc | ||
|
|
378c2c39a8 | ||
|
|
daf5fc434c | ||
|
|
e5bf90ed26 | ||
|
|
1bf3146220 | ||
|
|
ddd51850f0 | ||
|
|
e14a0c3770 | ||
|
|
b0b318ce30 | ||
|
|
6f666ca49f | ||
|
|
0cb2116bdf | ||
|
|
280113497b | ||
|
|
5f6e318329 | ||
|
|
f8921b6f10 | ||
|
|
31a08abff2 | ||
|
|
0fa1e11c5a | ||
|
|
e2c99a46be | ||
|
|
1edff41690 | ||
|
|
6d6f529d40 | ||
|
|
e2fd7d9d8e | ||
|
|
61146687b3 | ||
|
|
1d1f7fa581 | ||
|
|
67da88fab5 | ||
|
|
fb3ed70215 | ||
|
|
2fceeeee4e | ||
|
|
67102822e8 | ||
|
|
d00a0f1571 | ||
|
|
6175eb7c08 | ||
|
|
db5395ddbc | ||
|
|
7698f12112 | ||
|
|
1e8224536b | ||
|
|
a846c77c7e | ||
|
|
29812f4a82 | ||
|
|
a863951d97 | ||
|
|
146be677ba | ||
|
|
03b5f7feb8 | ||
|
|
6d54361a6d | ||
|
|
f440421ed1 | ||
|
|
e57464fc5e | ||
|
|
2a4b0f5ddb | ||
|
|
bb66e2201f | ||
|
|
4dc60e887f | ||
|
|
f6eb2e2dc8 | ||
|
|
9ecc10ab21 | ||
|
|
d7037a43c6 | ||
|
|
2471b8dfe0 | ||
|
|
0430cb49f9 | ||
|
|
7811926779 | ||
|
|
9bb66a4297 | ||
|
|
70772f0d74 | ||
|
|
728b00e4c3 | ||
|
|
97008ef984 | ||
|
|
6b86406e94 | ||
|
|
4252c364a4 | ||
|
|
4f4bc0321b | ||
|
|
6ecabe4588 | ||
|
|
93fa8484c5 | ||
|
|
ff2e55e82c | ||
|
|
259637ce3c | ||
|
|
743b9b759a | ||
|
|
73ba0b348b | ||
|
|
e93769cc81 | ||
|
|
68f9739eed | ||
|
|
c3d25b7a71 | ||
|
|
aaa582ff1a | ||
|
|
debc798aec | ||
|
|
6042f0e1e0 | ||
|
|
e10d02f45c | ||
|
|
aebf4ff728 | ||
|
|
1a2e89c9ed | ||
|
|
e10e2748b9 | ||
|
|
f422936e34 | ||
|
|
4e87f21405 | ||
|
|
dc2d79b16c | ||
|
|
88a3100563 | ||
|
|
8d3433a0e7 | ||
|
|
0fe30e5629 | ||
|
|
ea1e9037c4 | ||
|
|
24feeb17be | ||
|
|
6a7fc55572 | ||
|
|
cf047a8cee | ||
|
|
896420f8dc | ||
|
|
ebb9d9329a | ||
|
|
619f72d929 | ||
|
|
dc21e8388e | ||
|
|
8c35310cd6 | ||
|
|
642e8bbb7c | ||
|
|
3ee4143235 | ||
|
|
c136823170 | ||
|
|
92631fbfcf | ||
|
|
5a1b1a4485 | ||
|
|
3e82534c78 | ||
|
|
dd694d27b5 | ||
|
|
1900aefe32 | ||
|
|
2fe6b8c1e7 | ||
|
|
ecfaa0247a | ||
|
|
9a0cc9e043 | ||
|
|
b0360db105 | ||
|
|
0f9c95c15a | ||
|
|
8efd1da7e6 | ||
|
|
52ebba43d5 | ||
|
|
790eee7443 | ||
|
|
9f325290e8 | ||
|
|
93bf0a9a47 | ||
|
|
bdd0af21a9 | ||
|
|
aae5fe387b | ||
|
|
257c5aef51 | ||
|
|
3cae337487 | ||
|
|
779df30ec8 | ||
|
|
5609507991 | ||
|
|
1c24090c14 | ||
|
|
7da2c650d2 | ||
|
|
27fa9df2ee | ||
|
|
63c4e12259 | ||
|
|
1f66670819 | ||
|
|
a7b4f8de8d | ||
|
|
ad0d57fbf9 | ||
|
|
cfc594805b | ||
|
|
52461e673c | ||
|
|
a97edb7ef5 | ||
|
|
7a1c872861 | ||
|
|
0e5591017a | ||
|
|
a104157c9a | ||
|
|
ad244adbfa | ||
|
|
3721b328a6 | ||
|
|
dd688f48b7 | ||
|
|
296a0b2124 | ||
|
|
b9cc46e5ef | ||
|
|
375211fc30 | ||
|
|
b8b59f9dcd | ||
|
|
6760ff34ef | ||
|
|
c5de7811c4 | ||
|
|
82ef5457b0 | ||
|
|
d558476cd2 | ||
|
|
644701d995 | ||
|
|
1382d59206 | ||
|
|
b60e2c07c7 | ||
|
|
86f0307633 | ||
|
|
1db891a771 | ||
|
|
c9fa3291f5 | ||
|
|
e0f1658120 | ||
|
|
da105b7180 | ||
|
|
9c4f7cc530 | ||
|
|
d7eef8bd25 | ||
|
|
7b7c0e1eee | ||
|
|
2ae7798591 | ||
|
|
3f76453f34 | ||
|
|
8fbbe7f31e | ||
|
|
92a43b4f99 | ||
|
|
843722f82e | ||
|
|
ce79eaada8 | ||
|
|
c128086778 | ||
|
|
cc4fb8bf79 | ||
|
|
c3ac0f3d9f | ||
|
|
dfa4816633 | ||
|
|
06978a4fc4 | ||
|
|
3a2ecf6896 | ||
|
|
b357d52ec5 | ||
|
|
f8b6b1ebf8 | ||
|
|
91bd9d1111 | ||
|
|
1ec825050d | ||
|
|
a6a08d13e9 | ||
|
|
9a47c4a990 | ||
|
|
5063294177 | ||
|
|
b14917e2c6 | ||
|
|
c1bbec2a1c | ||
|
|
6227a4643a | ||
|
|
00af52815d | ||
|
|
5d3365a944 | ||
|
|
84ac2974fb | ||
|
|
c9a1515d1f | ||
|
|
5317ac5e03 | ||
|
|
9df1467ddf | ||
|
|
df79bd4515 | ||
|
|
cbb14f2ba8 | ||
|
|
1fe649e70f | ||
|
|
0d918add28 | ||
|
|
3926c98338 | ||
|
|
3bff6a1949 | ||
|
|
ec0c964ceb | ||
|
|
b4fd90c6d3 | ||
|
|
7dfd63cfa2 | ||
|
|
a562e5ca14 | ||
|
|
2885eef4ab | ||
|
|
087297d14c | ||
|
|
6e0fb95ac3 | ||
|
|
61e28146fb | ||
|
|
40d3f0ef9e | ||
|
|
99db825114 | ||
|
|
7341b377fe | ||
|
|
7f78a98de0 | ||
|
|
a64207f0ec | ||
|
|
d86f40e3a2 | ||
|
|
b74417f393 | ||
|
|
f5883abf04 | ||
|
|
02a367fd99 | ||
|
|
4870533710 | ||
|
|
9175cf5c71 | ||
|
|
8170a1b01d | ||
|
|
d1c6c763e2 | ||
|
|
0c683f7243 | ||
|
|
63de780527 | ||
|
|
c5ccbf2d1f | ||
|
|
8777535431 | ||
|
|
70192ce420 | ||
|
|
a74bbd3eeb | ||
|
|
02d79cb16a | ||
|
|
78ca9b3f1a | ||
|
|
017631e337 | ||
|
|
f9078dff2c | ||
|
|
b66381d677 | ||
|
|
49bf88f7a7 | ||
|
|
f93ceaa91d | ||
|
|
0fe122dc63 | ||
|
|
4e2a3fdbd0 | ||
|
|
3d251fa8ad | ||
|
|
af0b52448a | ||
|
|
8d200c72d3 | ||
|
|
f78cdb637d | ||
|
|
845f2d6faa | ||
|
|
525edbab80 | ||
|
|
c422b1c9a5 | ||
|
|
1043b13228 | ||
|
|
5e6c33df6c | ||
|
|
9541771703 | ||
|
|
f99d37cfad | ||
|
|
0cfe31ccd9 | ||
|
|
8fc1a5473b | ||
|
|
049b12b908 | ||
|
|
45f992b2bc | ||
|
|
9e2c66c341 | ||
|
|
2d0f59b6f2 | ||
|
|
fbba29e810 | ||
|
|
07a108760c | ||
|
|
b641bfb56a | ||
|
|
c65d80bc72 | ||
|
|
e0d266bf16 | ||
|
|
b62f7c5aee | ||
|
|
c89f04b926 | ||
|
|
ff8b4b4a88 | ||
|
|
07d63ae63a | ||
|
|
c0f5cb1641 | ||
|
|
50d84835cb | ||
|
|
8cdf4ef618 | ||
|
|
eff3a7acb4 | ||
|
|
18cd967a9c | ||
|
|
328d6c1d17 | ||
|
|
716eddac7b | ||
|
|
9b15af3bb7 | ||
|
|
b732e0d55a | ||
|
|
d92a1cee1c | ||
|
|
10a40bfcaf | ||
|
|
af397ba150 | ||
|
|
c7a2ec8290 | ||
|
|
145c155ba5 | ||
|
|
6f9ef32d96 | ||
|
|
aa5b9dbbbd | ||
|
|
f11be44c02 | ||
|
|
4276c8f23e | ||
|
|
9e1352c8b1 | ||
|
|
d46589ad29 | ||
|
|
09b7e67c52 | ||
|
|
79e1abe624 | ||
|
|
3db3bf1b74 | ||
|
|
a335c31385 | ||
|
|
9bd1f0a492 | ||
|
|
7a2c82461e | ||
|
|
21f7888f55 | ||
|
|
97349a9bb2 | ||
|
|
ce3b6ed7c2 | ||
|
|
e3fd564efd | ||
|
|
5cf96134d5 | ||
|
|
607c477e7d | ||
|
|
5e0619b500 | ||
|
|
17920e1195 | ||
|
|
721454aa90 | ||
|
|
d870896cfb | ||
|
|
270eb7cf1d | ||
|
|
527fd94145 | ||
|
|
04e4572088 | ||
|
|
0961eb5976 | ||
|
|
0311359922 | ||
|
|
ec09adf03e | ||
|
|
b031103df8 | ||
|
|
7701521a2e | ||
|
|
0c683d4f75 | ||
|
|
200d095034 | ||
|
|
94576a876a | ||
|
|
0fa1922bb0 | ||
|
|
c557905858 | ||
|
|
31b21d74b1 | ||
|
|
153244c390 | ||
|
|
e97b5c3c89 | ||
|
|
374893a5ae | ||
|
|
17f581f654 | ||
|
|
590b431ec1 | ||
|
|
98266fe0e1 | ||
|
|
2e236e90ba | ||
|
|
c5aee0810c | ||
|
|
f13d757976 | ||
|
|
7a0a62af2d | ||
|
|
ceab1d2fd2 | ||
|
|
639e7e0b3f | ||
|
|
89601305f6 | ||
|
|
4600b5a3bf | ||
|
|
b620307983 | ||
|
|
891ca70ade | ||
|
|
9ed2a50d26 | ||
|
|
cbf615d699 | ||
|
|
97b1a0090d | ||
|
|
9078aa6d08 | ||
|
|
8677146a8d | ||
|
|
2c14dfb781 | ||
|
|
057c5f073c | ||
|
|
e902da6595 | ||
|
|
8b5414c8f7 | ||
|
|
c86ece4dc0 | ||
|
|
1f71619b6b | ||
|
|
5b34b9c795 | ||
|
|
99d15899f6 | ||
|
|
c114a8b507 | ||
|
|
0dd37c2481 | ||
|
|
b5d7c96bba | ||
|
|
a76792ced4 | ||
|
|
39091240ff | ||
|
|
0ccb753892 | ||
|
|
63dda84c8b | ||
|
|
7ba1f85d48 | ||
|
|
bb9a23fe0f | ||
|
|
8536824d7e | ||
|
|
78073babe4 | ||
|
|
521d15219c | ||
|
|
7469a3c349 | ||
|
|
153a32e340 | ||
|
|
f155d4f150 | ||
|
|
d683dd2c38 | ||
|
|
7ebba741a8 | ||
|
|
d10f683098 | ||
|
|
0270133ecf | ||
|
|
d7b479d97d | ||
|
|
4366c512fe | ||
|
|
229a773ed2 | ||
|
|
d882f20436 | ||
|
|
9d7235af20 | ||
|
|
c2eb53d154 | ||
|
|
7629e347df | ||
|
|
2764caae29 | ||
|
|
a87bd2a928 | ||
|
|
202c920064 | ||
|
|
a08316bba0 | ||
|
|
520e5ebb7a | ||
|
|
5d5a4cacb1 | ||
|
|
b885a1a0d4 | ||
|
|
1705bd3ae9 | ||
|
|
e87c69f989 | ||
|
|
1c529eea3d | ||
|
|
738b0cfe9a | ||
|
|
913561cb2a | ||
|
|
05a91565dc | ||
|
|
79827efe9b | ||
|
|
8722cd89fc | ||
|
|
52fcc4ad1e | ||
|
|
59a096bfd6 | ||
|
|
5a1f541e13 | ||
|
|
94bd1c6a93 | ||
|
|
5b1aef5e52 | ||
|
|
89bfcdc44e | ||
|
|
fba81138ea | ||
|
|
d50e07265e | ||
|
|
c92891538e | ||
|
|
ccc1e9bc8b | ||
|
|
f33b398428 | ||
|
|
226a8af262 | ||
|
|
ebcc5ab4b1 | ||
|
|
10e16e8379 | ||
|
|
df1f3d8a00 | ||
|
|
5e2dfffe25 | ||
|
|
897f2ea6dd | ||
|
|
3ff39ec578 | ||
|
|
3d852a535d | ||
|
|
6f6a61f31a | ||
|
|
10f54f5790 | ||
|
|
0e7280585a | ||
|
|
1da7173f27 | ||
|
|
1cb1e68a01 | ||
|
|
b59c8a5512 | ||
|
|
fe63ad0976 | ||
|
|
941cb7b851 | ||
|
|
d1cf0d9fd7 | ||
|
|
64c2bb4d6b | ||
|
|
24c9f5c17e | ||
|
|
d368e4e80d | ||
|
|
5c0ff84fc4 | ||
|
|
502a21b6b6 | ||
|
|
0e9bf59c0f | ||
|
|
108f9fccdd | ||
|
|
ac884bd7c3 | ||
|
|
a4cb5c991c | ||
|
|
68f1f55f37 | ||
|
|
1dc779d5e8 | ||
|
|
f781c7a08c | ||
|
|
a8511a9f39 | ||
|
|
47714eec45 | ||
|
|
c46e9b2f4d | ||
|
|
26d579f13f | ||
|
|
6556d26742 | ||
|
|
608dce2205 | ||
|
|
f86e50c723 | ||
|
|
b60fe33886 | ||
|
|
5210a143fd | ||
|
|
dc78dc9b0d | ||
|
|
6b11c1a180 | ||
|
|
b3669f6d66 | ||
|
|
bbff75e037 | ||
|
|
7e10618ceb | ||
|
|
7f4def6b83 | ||
|
|
5790d246c8 | ||
|
|
19dee09c86 | ||
|
|
dfe2889912 | ||
|
|
223ba791fe | ||
|
|
0d49bbe7ac | ||
|
|
8381e8122a | ||
|
|
f38924c7fe | ||
|
|
43152c9341 | ||
|
|
cf84e8b7cc | ||
|
|
2b42e73530 | ||
|
|
60030959f2 | ||
|
|
7174523ac5 | ||
|
|
f573fef9eb | ||
|
|
b4250d8254 | ||
|
|
ac4d4de3c1 | ||
|
|
05e6d008fa | ||
|
|
dd4abb2073 | ||
|
|
612aba1365 | ||
|
|
94dce09570 | ||
|
|
cc241c5a7b | ||
|
|
13cf9d01f0 | ||
|
|
47453fec3f | ||
|
|
641d506559 | ||
|
|
3dec2b8159 | ||
|
|
a0bd969140 | ||
|
|
b30d42a37b | ||
|
|
a03acc68e7 | ||
|
|
05296473d3 | ||
|
|
2118f8c764 | ||
|
|
e366af98b5 | ||
|
|
81e2ac44c3 | ||
|
|
07bb326c06 | ||
|
|
bcc2c8cc2d | ||
|
|
2e0e17f1aa | ||
|
|
c517b44e82 | ||
|
|
f311339786 | ||
|
|
34853d0322 | ||
|
|
9c60b69c88 | ||
|
|
4f10bccf84 | ||
|
|
c7eaebf597 | ||
|
|
60e1052d33 | ||
|
|
7e77c102b0 | ||
|
|
a452c582ab | ||
|
|
0d3adb074d | ||
|
|
8ec4b52dda | ||
|
|
9265c68383 | ||
|
|
4bd2d78ecb | ||
|
|
e7aa766d0a | ||
|
|
7d8300b3ce | ||
|
|
af8a1234ed | ||
|
|
bd0ecd0a9d | ||
|
|
35c8f02f90 | ||
|
|
f160952817 | ||
|
|
9e5a302ab1 | ||
|
|
a1dc19fa26 | ||
|
|
e79ded934f | ||
|
|
ef3e7d9286 | ||
|
|
68b25ddbb5 | ||
|
|
f96040eade | ||
|
|
599a808054 | ||
|
|
382c5c55ec | ||
|
|
afb2306904 | ||
|
|
2642da3be3 | ||
|
|
dcbf283c9d | ||
|
|
f38fa0132c | ||
|
|
569053f7e0 | ||
|
|
037a97ff3d | ||
|
|
6e169ab3c2 | ||
|
|
6ac27e89b3 | ||
|
|
2235dab550 | ||
|
|
7604c2172f | ||
|
|
1e750f4b9d | ||
|
|
d1f0d01ae8 | ||
|
|
167a0f28b2 | ||
|
|
06be5266fd | ||
|
|
60e7165983 | ||
|
|
6dbfc12896 | ||
|
|
6d4b3e54d0 | ||
|
|
2968b92677 | ||
|
|
0d0bf4ad3f | ||
|
|
53fa60b0f8 | ||
|
|
99517fa508 | ||
|
|
2e239d16d4 | ||
|
|
6de5babd5b | ||
|
|
f3ad1a019d | ||
|
|
90ab882e8e | ||
|
|
53269096a6 | ||
|
|
254509f243 | ||
|
|
a697f186af | ||
|
|
2316a80be9 | ||
|
|
bbcb04b263 | ||
|
|
7afe7fbb5f | ||
|
|
7c7da7379d | ||
|
|
37358e9c8c | ||
|
|
41941df87a | ||
|
|
bf2ed81eb1 | ||
|
|
2d85d41e9c | ||
|
|
e5e7b2484c | ||
|
|
6058552654 | ||
|
|
f40c7ff4fa | ||
|
|
da62c6181e | ||
|
|
4d066738eb | ||
|
|
cb12d03262 | ||
|
|
9a6a2d9b78 | ||
|
|
ff0af477cb | ||
|
|
05adf9345f | ||
|
|
ba70fdda48 | ||
|
|
3885fcb2ec | ||
|
|
99800a27f5 | ||
|
|
107622942b | ||
|
|
9794f14a65 | ||
|
|
af759a815f | ||
|
|
0e7078c479 | ||
|
|
83107c7974 | ||
|
|
ff9b2dbe93 | ||
|
|
ebf4e5f2e9 | ||
|
|
21c30583e5 | ||
|
|
d6c9ace306 | ||
|
|
faf1252597 | ||
|
|
7b2ee25ea2 | ||
|
|
5a3207ae42 | ||
|
|
3eab4955b9 | ||
|
|
d772fc3ba2 | ||
|
|
7de0a3fd70 | ||
|
|
eb8710a336 | ||
|
|
25c55ecbd0 | ||
|
|
280df2cedd | ||
|
|
5ba9c86bee | ||
|
|
faa6591d27 | ||
|
|
841d442f0d | ||
|
|
3351eabc4f | ||
|
|
006e1bc61e | ||
|
|
35d1b4ea37 | ||
|
|
b0c5069695 | ||
|
|
dae0d0ad4b | ||
|
|
79e560b7bf | ||
|
|
cf79ac1069 | ||
|
|
8aab7a16c4 | ||
|
|
c16b89b0be | ||
|
|
25bbc00ff3 | ||
|
|
e667b6c139 | ||
|
|
195364223f | ||
|
|
84ce2cac8d | ||
|
|
f3507cf465 | ||
|
|
f74f2c47d3 | ||
|
|
72b25591a5 | ||
|
|
fe23a5c6da | ||
|
|
49fbc5cb62 | ||
|
|
5a19a307a9 | ||
|
|
c3ec52b391 | ||
|
|
f2d68be0a3 | ||
|
|
c1f98ac481 | ||
|
|
8287c84dc7 | ||
|
|
13bff2963e | ||
|
|
035f3f9e02 | ||
|
|
65e6de5ba4 | ||
|
|
82ced9233a | ||
|
|
e94411ebeb | ||
|
|
b92b429ffa | ||
|
|
e457cfb212 | ||
|
|
f1476c52e6 | ||
|
|
332246aed6 | ||
|
|
1c5201dcf1 | ||
|
|
36880ace27 | ||
|
|
0d55d6ef6b | ||
|
|
688bf645b4 | ||
|
|
d5a14482a6 | ||
|
|
cc1e0030df | ||
|
|
fcadcb34a2 | ||
|
|
dd8f440be0 | ||
|
|
17bc422e7a | ||
|
|
380cdbcf75 | ||
|
|
f4f2bf34fc | ||
|
|
ed713d80a9 | ||
|
|
c39703c61c | ||
|
|
537773f975 | ||
|
|
f37eca7c61 | ||
|
|
40a52d5ad5 | ||
|
|
d95bd20cbe | ||
|
|
70ca98d6a2 | ||
|
|
cf7caf55da | ||
|
|
b74bff3b2e | ||
|
|
b2e4853976 | ||
|
|
aef3c5c121 | ||
|
|
4afbfcb016 | ||
|
|
09f8cff4c9 | ||
|
|
9ecd162040 | ||
|
|
8617f02b01 | ||
|
|
9d71d39917 | ||
|
|
5430564065 | ||
|
|
54bc8c16d8 | ||
|
|
9c14ddda18 | ||
|
|
0affdb6ad0 | ||
|
|
532978522a | ||
|
|
05a9a0b45c | ||
|
|
24f8ad11ad | ||
|
|
ce943a0e6c | ||
|
|
204c0cd0f8 | ||
|
|
c213008f1c | ||
|
|
e6ad069509 | ||
|
|
38c7e3cdf8 | ||
|
|
2be79f6590 | ||
|
|
2f7125b6c0 | ||
|
|
bb03a6fc9b | ||
|
|
7b774aee1a | ||
|
|
d53619c247 | ||
|
|
d34118bdfd | ||
|
|
c57bc487a3 | ||
|
|
296fc9a3df | ||
|
|
fd8b5780f3 | ||
|
|
602b6c635a | ||
|
|
a79180699e | ||
|
|
e9901a8abf | ||
|
|
4e63d64c13 | ||
|
|
4261b7adbe | ||
|
|
f30c9f6171 | ||
|
|
c00b704843 | ||
|
|
e89b2020a3 | ||
|
|
18ca3cbd80 | ||
|
|
062d6cd066 | ||
|
|
b4dd067d61 | ||
|
|
fd22e2911a | ||
|
|
73d9e69e43 | ||
|
|
7e4c29f4cf | ||
|
|
32672262ef | ||
|
|
3c865ea20b | ||
|
|
d8698d02b7 | ||
|
|
d5b47e5adb | ||
|
|
accb1cf2ab | ||
|
|
aa8da1b046 | ||
|
|
c52ed32949 | ||
|
|
ec6f4ff734 | ||
|
|
06b0dbf2e5 | ||
|
|
98d24248c2 | ||
|
|
cec1dc98a9 | ||
|
|
36115734bb | ||
|
|
c1eccd391d | ||
|
|
7fe86fcdb2 | ||
|
|
7f81ec52c1 | ||
|
|
7c269de39b | ||
|
|
aa9e34e992 | ||
|
|
4d0ab514fb | ||
|
|
9d526b32e0 | ||
|
|
90a83c5c64 | ||
|
|
e6e5cc67d5 | ||
|
|
4a6eb95474 | ||
|
|
7bce8cf3b6 | ||
|
|
4d1605ded2 | ||
|
|
2bec2cfa93 | ||
|
|
ff07872a3d | ||
|
|
35733cd82e | ||
|
|
c88b051121 | ||
|
|
38df990033 | ||
|
|
c7776b5b37 | ||
|
|
f89afc175f | ||
|
|
1f252efdfb | ||
|
|
420ca85393 | ||
|
|
d60695992b | ||
|
|
3c0681d55d | ||
|
|
3fc0fa5a02 | ||
|
|
d84d40afea | ||
|
|
ddbbd38517 | ||
|
|
d588531ab8 | ||
|
|
bdc06feb88 | ||
|
|
940e2f4759 | ||
|
|
3fc792fcf8 | ||
|
|
f5520e7991 | ||
|
|
897c5ecac7 | ||
|
|
4479ef31e2 | ||
|
|
ec827ab371 | ||
|
|
6fe65c76b1 | ||
|
|
e79d463cf7 | ||
|
|
1508e5db49 | ||
|
|
8de391825a | ||
|
|
da1172a882 | ||
|
|
288a434598 | ||
|
|
1d6ae1e589 | ||
|
|
dd29456384 | ||
|
|
95a658defa | ||
|
|
cd298eb5c1 | ||
|
|
f7de3bab74 | ||
|
|
13578dcee8 | ||
|
|
6d76e93ede | ||
|
|
6b57cca64d | ||
|
|
e0bd5a24f4 | ||
|
|
2b2bf88a37 | ||
|
|
a0fa53e8cb | ||
|
|
c841d4a77a | ||
|
|
bf4b2dc72c | ||
|
|
078ed868fb | ||
|
|
bfc1d1d6b0 | ||
|
|
42ecae944e | ||
|
|
b9aa6a234b | ||
|
|
5f2d62030f | ||
|
|
fd0169d012 | ||
|
|
7e26b4695d | ||
|
|
cdfdff5c32 | ||
|
|
df5600f03f | ||
|
|
231fd268df | ||
|
|
582df3239f | ||
|
|
3ea102e238 | ||
|
|
52ab3c625e | ||
|
|
dee13542cd | ||
|
|
e90ba9e65b | ||
|
|
ca86076a02 | ||
|
|
6c75a29cb0 | ||
|
|
e10777576f | ||
|
|
08eaf2104b | ||
|
|
14de86afa0 | ||
|
|
69c5f9e19a | ||
|
|
03e903eef9 | ||
|
|
f3a1815bc5 | ||
|
|
ef03f77dc9 | ||
|
|
1a509a9a13 | ||
|
|
1e566f4a20 | ||
|
|
709c8f32b5 | ||
|
|
f2787a547f | ||
|
|
629b714dab | ||
|
|
1b0269c567 | ||
|
|
6158dc9607 | ||
|
|
5462f0a7a1 | ||
|
|
6d453ea80b | ||
|
|
5952648fae | ||
|
|
6b49bd557f | ||
|
|
c071284a56 | ||
|
|
5930cf48d5 | ||
|
|
9dd070887a | ||
|
|
cf687a0f2c | ||
|
|
f5c0cfdcdd | ||
|
|
03e2974709 | ||
|
|
d2373a00ea | ||
|
|
e769460397 | ||
|
|
a0a284ad26 | ||
|
|
1ebf4276e7 | ||
|
|
908931b9ed | ||
|
|
50655d1ac2 | ||
|
|
92e19ee19f | ||
|
|
52f3a90d18 | ||
|
|
11371c9e4f | ||
|
|
1b71b81953 | ||
|
|
c9d9d22215 | ||
|
|
5300641822 | ||
|
|
b31b7e1e86 | ||
|
|
cfb2f5beb9 | ||
|
|
ee9f24b2a6 | ||
|
|
8c86e23a4c | ||
|
|
fe98d35d4e | ||
|
|
8e10693402 | ||
|
|
f31848721c | ||
|
|
6101e141d8 | ||
|
|
71d84e7475 | ||
|
|
735ad4c972 | ||
|
|
50cb59f569 | ||
|
|
b58c19b88b | ||
|
|
6fe9ebbd2d | ||
|
|
4ea23a96ae | ||
|
|
ebf5d00fd2 | ||
|
|
b015645ed0 | ||
|
|
ce3b10ef03 | ||
|
|
d7af5551eb | ||
|
|
1d03a82be4 | ||
|
|
aa5fdfa395 | ||
|
|
7e05bcc81d | ||
|
|
e52aa7ad3c | ||
|
|
42faf9bda2 | ||
|
|
984164ba60 | ||
|
|
d26faac0e6 | ||
|
|
b54a9ace9f | ||
|
|
ad0131de66 | ||
|
|
b9ac48ebef | ||
|
|
71751ae4bc | ||
|
|
1c6f4a1d1e | ||
|
|
fd84b3f1c4 | ||
|
|
9d4a052ecc | ||
|
|
f93c8965be | ||
|
|
beef86ce8c | ||
|
|
03b75d5379 | ||
|
|
66855e65bb | ||
|
|
b8da93912f | ||
|
|
d675115615 | ||
|
|
0296a0bde6 | ||
|
|
8409384232 | ||
|
|
cfaee56a08 | ||
|
|
7d65717784 | ||
|
|
7079d50fdf | ||
|
|
41a613e151 | ||
|
|
1f2b6a0acc | ||
|
|
25d402c9d1 | ||
|
|
045b7cf019 | ||
|
|
57109dd72e | ||
|
|
7a8958741d | ||
|
|
f317d74bb4 | ||
|
|
5f0eb91a81 | ||
|
|
66f3a1fe7d | ||
|
|
59d85531ce | ||
|
|
4bd4c3e833 | ||
|
|
47f082e2fc | ||
|
|
1af52d16c0 | ||
|
|
2f52ed3ee0 | ||
|
|
a09407da8e | ||
|
|
1b878b59b8 | ||
|
|
80452ab4cd | ||
|
|
4d9c8e8d3e | ||
|
|
e15bd77789 | ||
|
|
a5f12a50e6 | ||
|
|
07ef06ad95 | ||
|
|
b61836adf7 | ||
|
|
34e2663492 | ||
|
|
8b90f87589 | ||
|
|
8c1e45da6c | ||
|
|
88caff38f0 | ||
|
|
62a6d74393 | ||
|
|
cb94447290 | ||
|
|
e4cf509d0f | ||
|
|
205119cc01 | ||
|
|
f10f98abf2 | ||
|
|
3a7391fbb3 | ||
|
|
2155734e23 | ||
|
|
6806e66d64 | ||
|
|
db8305b5e9 | ||
|
|
e8330eedc3 | ||
|
|
c01c4a860c | ||
|
|
6e778f209d | ||
|
|
b760361184 | ||
|
|
7150befa54 | ||
|
|
5bf0b275cb | ||
|
|
c86bf1d68b | ||
|
|
e61bde1415 | ||
|
|
e4b3f0ddef | ||
|
|
ec73294900 | ||
|
|
30eb949ce1 | ||
|
|
f5d69a3df6 | ||
|
|
3cc39489bd | ||
|
|
ace5d7de9e | ||
|
|
1682eb3915 | ||
|
|
6fd1a990ae | ||
|
|
cfa36a21b5 | ||
|
|
95163d4864 | ||
|
|
5a9645829d | ||
|
|
be78d93c1f | ||
|
|
ac63558645 | ||
|
|
88fb2e49dc | ||
|
|
6e96ad0f17 | ||
|
|
e54754d04f | ||
|
|
e4b2ebe2a4 | ||
|
|
0028431dde | ||
|
|
91d94de1d2 | ||
|
|
0c131ec990 | ||
|
|
54280d5572 | ||
|
|
6d3640a8b0 | ||
|
|
8226073506 | ||
|
|
f4a5e18c69 | ||
|
|
133af93548 | ||
|
|
3546a5d392 | ||
|
|
fb921e951e | ||
|
|
22685d8e3b | ||
|
|
b2d050d136 | ||
|
|
e3ff1dcd96 | ||
|
|
897890e1b4 | ||
|
|
2c95ea00e8 | ||
|
|
00d6ed7dbb | ||
|
|
b23133c79c | ||
|
|
eef2f26707 | ||
|
|
7483ad1732 | ||
|
|
188237db24 | ||
|
|
134624967b | ||
|
|
a1b8d1cd84 | ||
|
|
e7b9293f3b | ||
|
|
93e4a8931d | ||
|
|
c0713eaeda | ||
|
|
000afa1ed6 | ||
|
|
323e25951f | ||
|
|
49d0c0de87 | ||
|
|
e31a835c4e | ||
|
|
dedf5094c1 | ||
|
|
fed4619a92 | ||
|
|
9eb1a20b3f | ||
|
|
ac21b9cc20 | ||
|
|
4a486e3bf8 | ||
|
|
269374e6bb | ||
|
|
e4d97e4059 | ||
|
|
ba567d81cb | ||
|
|
4fb6005f44 | ||
|
|
69ec4175eb | ||
|
|
d46e90dcdb | ||
|
|
900e91e101 | ||
|
|
05d7e33d86 | ||
|
|
7f0aff8c03 | ||
|
|
512e59193d | ||
|
|
8f056e4a82 | ||
|
|
d06a986293 | ||
|
|
d866847c0d | ||
|
|
83472bc354 | ||
|
|
ac784f8905 | ||
|
|
6035281ca1 | ||
|
|
ce8168d97a | ||
|
|
27670525a3 | ||
|
|
4796d7f450 | ||
|
|
79ec96343f | ||
|
|
4572a455c8 | ||
|
|
cb591925ea | ||
|
|
0bc6102096 | ||
|
|
3282a8d76a | ||
|
|
d04befb8d0 | ||
|
|
2c52a4c40c | ||
|
|
f53f71ecf1 | ||
|
|
fc7481c60c | ||
|
|
e59ae9c6e9 | ||
|
|
aae40a7087 | ||
|
|
9f9148fc1f | ||
|
|
20e5832ce3 | ||
|
|
fc29b34573 | ||
|
|
5ea9150af8 | ||
|
|
159a5835e0 | ||
|
|
78d48c8be3 | ||
|
|
9bb6b216e9 | ||
|
|
dc59d1f3ca | ||
|
|
1ab3f53a31 | ||
|
|
fd7d387fb0 | ||
|
|
17a64506f8 | ||
|
|
b68977597b | ||
|
|
2fb9f83227 | ||
|
|
6fd312f784 | ||
|
|
12d59231c5 | ||
|
|
3a7e2c0249 | ||
|
|
62f2defd91 | ||
|
|
9048e07b6b | ||
|
|
0903721a62 | ||
|
|
bf3380755b | ||
|
|
5d327ccd53 | ||
|
|
eb82af9006 | ||
|
|
2cc2902930 | ||
|
|
f4cb0625bc | ||
|
|
edd40ebe9d | ||
|
|
4e8c130cbf | ||
|
|
0760b6a89c | ||
|
|
f34f60b255 | ||
|
|
3c2675fd0d | ||
|
|
f163e348e0 | ||
|
|
71a3d79c82 | ||
|
|
bd1ba67647 | ||
|
|
828688ddd0 | ||
|
|
60cd1320d2 | ||
|
|
a31de89f9c | ||
|
|
a129f53e0c | ||
|
|
6aa86ac2e3 | ||
|
|
28cafbcad2 | ||
|
|
991f60ce44 | ||
|
|
5dbeabcc58 | ||
|
|
b6bcebc588 | ||
|
|
2f3aa57d23 | ||
|
|
a123774bab | ||
|
|
7f56b50267 | ||
|
|
386f0dc142 | ||
|
|
bf90811cef | ||
|
|
72e2c6dca7 | ||
|
|
81fe467b20 | ||
|
|
07cdc6002d | ||
|
|
ee6d17d165 | ||
|
|
6dd1299dff | ||
|
|
d59e358caa | ||
|
|
5e1eb39b87 | ||
|
|
063170463f | ||
|
|
f8a9851bb3 | ||
|
|
b81a30ef12 | ||
|
|
62fb968c9a | ||
|
|
88b8567d2b | ||
|
|
7e4a295ef0 | ||
|
|
289ed85365 | ||
|
|
07dd459f3c | ||
|
|
f104fab593 | ||
|
|
0170f9b44a | ||
|
|
585d96949b | ||
|
|
46b3807f21 | ||
|
|
5e8a73e29d | ||
|
|
796a276b65 | ||
|
|
072290e544 | ||
|
|
5d5b642fa9 | ||
|
|
70761f4ac1 | ||
|
|
9d61f73e22 | ||
|
|
7a3c61a8d0 | ||
|
|
96872d7d41 | ||
|
|
485d6131d5 | ||
|
|
4893e9a58a | ||
|
|
79480c1d73 | ||
|
|
02c015574f | ||
|
|
653872df8e | ||
|
|
248079f041 | ||
|
|
2da756692b | ||
|
|
240a749b87 | ||
|
|
e4324258d3 | ||
|
|
2c33abe5d1 | ||
|
|
09ef1e0319 | ||
|
|
c091d96999 | ||
|
|
1978061a06 | ||
|
|
617370e822 | ||
|
|
0ed6a96781 | ||
|
|
b3c3bf51ba | ||
|
|
0e187fe888 | ||
|
|
43efcf3a99 | ||
|
|
ebc858aed9 | ||
|
|
f94af86ff9 | ||
|
|
da1c58bac6 | ||
|
|
c1c136f6c0 | ||
|
|
8a18119b53 | ||
|
|
777142b992 | ||
|
|
daa54029ed | ||
|
|
136a654639 | ||
|
|
f13e2c0d71 | ||
|
|
97101248a2 | ||
|
|
5150b4b1b6 | ||
|
|
a6d2381a68 | ||
|
|
29161feb49 | ||
|
|
1a4a1c2ccb | ||
|
|
96dac65e31 | ||
|
|
6005282d9f | ||
|
|
129020dbc4 | ||
|
|
54e0242030 | ||
|
|
0e57f4064f | ||
|
|
e50c4528a6 | ||
|
|
342810aa3a | ||
|
|
f84078c7ca | ||
|
|
eba81a6065 | ||
|
|
427e9197d8 | ||
|
|
67d6cf37a5 | ||
|
|
e6451c7ede | ||
|
|
bcd88a1342 | ||
|
|
5ea250d89d |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -14,3 +14,8 @@ project/plugins/project/
|
||||
.classpath
|
||||
.project
|
||||
.cache
|
||||
.settings
|
||||
|
||||
# IntelliJ specific
|
||||
.idea/
|
||||
.idea_modules/
|
||||
|
||||
3
.travis.yml
Normal file
3
.travis.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
language: scala
|
||||
scala:
|
||||
- 2.11.6
|
||||
202
LICENSE
Normal file
202
LICENSE
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
256
README.md
256
README.md
@@ -1,25 +1,31 @@
|
||||
GitBucket
|
||||
GitBucket [](https://gitter.im/takezoe/gitbucket) [](https://travis-ci.org/takezoe/gitbucket)
|
||||
=========
|
||||
|
||||
GitBucket is a Github clone by Scala, Easy to setup.
|
||||
GitBucket is the easily installable GitHub clone powered by Scala.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
The current version of GitBucket provides a basic features below:
|
||||
|
||||
- Public / Private Git repository (http access only)
|
||||
- Repository viewer (some advanced features are not implemented)
|
||||
- Public / Private Git repository (http and ssh access)
|
||||
- Repository viewer and online file editing
|
||||
- Repository search (Code and Issues)
|
||||
- Wiki
|
||||
- Issues
|
||||
- Fork / Pull request
|
||||
- Mail notification
|
||||
- Activity timeline
|
||||
- User management (for Administrators)
|
||||
- Group (like Organization in Github)
|
||||
- LDAP integration
|
||||
- Gravatar support
|
||||
|
||||
Following features are not implemented, but we will make them in the future release!
|
||||
|
||||
- Fork and pull request
|
||||
- Timeline
|
||||
- Search
|
||||
- Network graph
|
||||
- Statics
|
||||
- Statistics
|
||||
- Watch / Star
|
||||
- Team management (like Organization in Github)
|
||||
|
||||
If you want to try the development version of GitBucket, see the documentation for developers at [Wiki](https://github.com/takezoe/gitbucket/wiki).
|
||||
|
||||
@@ -27,14 +33,240 @@ Installation
|
||||
--------
|
||||
|
||||
1. Download latest **gitbucket.war** from [the release page](https://github.com/takezoe/gitbucket/releases).
|
||||
2. Deploy it to the servlet container such as Tomcat or Jetty.
|
||||
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.
|
||||
|
||||
If you are using Gitbucket behind a webserver please make sure you have increased the **client_max_body_size** (on nignx)
|
||||
|
||||
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.
|
||||
|
||||
- --port=[NUMBER]
|
||||
- --prefix=[CONTEXTPATH]
|
||||
- --host=[HOSTNAME]
|
||||
- --gitbucket.home=[DATA_DIR]
|
||||
|
||||
To upgrade GitBucket, only replace gitbucket.war. All GitBucket data is stored in HOME/.gitbucket. So if you want to back up GitBucket data, copy this directory to the other disk.
|
||||
|
||||
For Installation on Windows Server with IIS see [this wiki page](https://github.com/takezoe/gitbucket/wiki/Installation-on-IIS-and-Helicontech-Zoo)
|
||||
|
||||
### 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, copy the [gitbucket.plist](https://raw.github.com/takezoe/gitbucket/master/contrib/macosx/gitbucket.plist) file 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`
|
||||
|
||||
Release Notes
|
||||
--------
|
||||
### 3.1 - 28 Mar 2015
|
||||
- Web APIs for Jenkins github pull-request builder
|
||||
- Improved diff view
|
||||
- Bump Scalatra to 2.3.1, sbt to 0.13.8
|
||||
|
||||
### 3.0 - 3 Mar 2015
|
||||
- New plug-in system is available
|
||||
- Connection pooling by c3p0
|
||||
- New branch UI
|
||||
- Compare between specified commit ids
|
||||
|
||||
### 2.8 - 1 Feb 2015
|
||||
- New logo and icons
|
||||
- New system setting options to control visibility
|
||||
- Comment on side-by-side diff
|
||||
- Information message on sign-in page
|
||||
- Fork repository by group account
|
||||
|
||||
### 2.7 - 29 Dec 2014
|
||||
- Comment for commit and diff
|
||||
- Fix security issue in markdown rendering
|
||||
- Some bug fix and improvements
|
||||
|
||||
### 2.6 - 24 Nov 2014
|
||||
- Search box at issues and pull requests
|
||||
- Information from administrator
|
||||
- Pull request UI has been updated
|
||||
- Move to TravisCI from Buildhive
|
||||
- Some bug fix and improvements
|
||||
|
||||
### 2.5 - 4 Nov 2014
|
||||
- New Dashboard
|
||||
- Change datetime format
|
||||
- Create branch from Web UI
|
||||
- Task list in Markdown
|
||||
- Some bug fix and improvements
|
||||
|
||||
### 2.4.1 - 6 Oct 2014
|
||||
- Bug fix
|
||||
|
||||
### 2.4 - 6 Oct 2014
|
||||
- New UI is applied to Issues and Pull requests
|
||||
- Side-by-side diff is available
|
||||
- Fix relative path problem in Markdown links and images
|
||||
- Plugin System is disabled in default
|
||||
- Some bug fix and improvements
|
||||
|
||||
### 2.3 - 1 Sep 2014
|
||||
- Scala based plugin system
|
||||
- Embedded Jetty war extraction directory moved to `GITBUCKET_HOME/tmp`
|
||||
- Some bug fix and improvements
|
||||
|
||||
### 2.2.1 - 5 Aug 2014
|
||||
- Bug fix
|
||||
|
||||
### 2.2 - 4 Aug 2014
|
||||
- Plug-in system is available
|
||||
- Move to Scala 2.11, Scalatra 2.3 and Slick 2.1
|
||||
- tar.gz export for repository contents
|
||||
- LDAP authentication improvement (mail address became optional)
|
||||
- Show news feed of a private repository to members
|
||||
- Some bug fix and improvements
|
||||
|
||||
### 2.1 - 6 Jul 2014
|
||||
- Upgrade to Slick 2.0 from 1.9
|
||||
- Base part of the plug-in system is merged
|
||||
- Many bug fix and improvements
|
||||
|
||||
### 2.0 - 31 May 2014
|
||||
- Modern Github UI
|
||||
- Preview in AceEditor
|
||||
- Select lines by clicking line number in blob view
|
||||
|
||||
### 1.13 - 29 Apr 2014
|
||||
- Direct file editing in the repository viewer using AceEditor
|
||||
- File attachment for issues
|
||||
- Atom feed of user activity
|
||||
- Fix some bugs
|
||||
|
||||
### 1.12 - 29 Mar 2014
|
||||
- SSH repository access is available
|
||||
- Allow users can create and management their groups
|
||||
- Git submodule support
|
||||
- Close issues via commit messages
|
||||
- Show repository description below the name on repository page
|
||||
- Fix presentation of the source viewer
|
||||
- Upgrade to sbt 0.13
|
||||
- Fix some bugs
|
||||
|
||||
### 1.11.1 - 06 Mar 2014
|
||||
- Bug fix
|
||||
|
||||
### 1.11 - 01 Mar 2014
|
||||
- Base URL for redirection, notification and repository URL box is configurable
|
||||
- Remove ```--https``` option because it's possible to substitute in the base url
|
||||
- Headline anchor is available for Markdown contents such as Wiki page
|
||||
- Improve H2 connectivity
|
||||
- Label is available for pull requests not only issues
|
||||
- Delete branch button is added
|
||||
- Repository icons are updated
|
||||
- Select lines of source code by URL hash like `#L10` or `#L10-L15` in repository viewer
|
||||
- Display reference to issue from others in comment list
|
||||
- Fix some bugs
|
||||
|
||||
### 1.10 - 01 Feb 2014
|
||||
- Rename repository
|
||||
- Transfer repository owner
|
||||
- Change default data directory to `HOME/.gitbucket` from `HOME/gitbucket` to avoid problem like #243, but if data directory already exist at HOME/gitbucket, it continues being used.
|
||||
- Add LDAP display name attribute
|
||||
- Response performance improvement
|
||||
- Fix some bugs
|
||||
|
||||
### 1.9 - 28 Dec 2013
|
||||
- Display GITBUCKET_HOME on the system settings page
|
||||
- Fix some bugs
|
||||
|
||||
### 1.8 - 30 Nov 2013
|
||||
- Add user and group deletion
|
||||
- Improve pull request performance
|
||||
- Pull request synchronization (when source repository is updated after pull request, it's applied to the pull request)
|
||||
- LDAP StartTLS support
|
||||
- Enable hard wrapping in Markdown
|
||||
- Add new some options to specify the data directory. See details in [Wiki](https://github.com/takezoe/gitbucket/wiki/DirectoryStructure).
|
||||
- Fix some bugs
|
||||
|
||||
### 1.7 - 26 Oct 2013
|
||||
- Support working on Java6 in embedded Jetty mode
|
||||
- Add `--host` option to bind specified host name in embedded Jetty mode
|
||||
- Add `--https=true` option to force https scheme when using embedded Jetty mode at the back of https proxy
|
||||
- Add full name as user property
|
||||
- Change link color for absent Wiki pages
|
||||
- Add ZIP download button to the repository viewer tab
|
||||
- Improve ZIP exporting performance
|
||||
- Expand issue and comment textarea for long text automatically
|
||||
- Add conflict detection in Wiki
|
||||
- Add reverting wiki page from history
|
||||
- Match committer to user name by email address
|
||||
- Mail notification sender is customizable
|
||||
- Add link to changeset in refs comment for issues
|
||||
- Fix some bugs
|
||||
|
||||
### 1.6 - 1 Oct 2013
|
||||
- Web hook
|
||||
- Performance improvement for pull request
|
||||
- Executable war file
|
||||
- Specify suitable Content-Type for downloaded files in the repository viewer
|
||||
- Fix some bugs
|
||||
|
||||
### 1.5 - 4 Sep 2013
|
||||
- Fork and pull request
|
||||
- LDAP authentication
|
||||
- Mail notification
|
||||
- Add an option to turn off the gravatar support
|
||||
- Add the branch tab in the repository viewer
|
||||
- Encoding auto detection for the file content in the repository viewer
|
||||
- Add favicon, header logo and icons for the timeline
|
||||
- Specify data directory via environment variable GITBUCKET_HOME
|
||||
- Fix some bugs
|
||||
|
||||
### 1.4 - 31 Jul 2013
|
||||
- Group management
|
||||
- Repository search for code and issues
|
||||
- Display user related issues on the dashboard
|
||||
- Display participants avatar of issues on the issue page
|
||||
- Performance improvement for repository viewer
|
||||
- Alert by milestone due date
|
||||
- H2 database administration console
|
||||
- Fix some bugs
|
||||
|
||||
### 1.3 - 18 Jul 2013
|
||||
- Batch updating for issues
|
||||
- Display assigned user on issue list
|
||||
- User icon and Gravatar support
|
||||
- Convert @xxxx to link to the account page
|
||||
- Add copy to clipboard button for git clone URL
|
||||
- Allow multi-byte characters as wiki page name
|
||||
- Allow to create the empty repository
|
||||
- Fix some bugs
|
||||
|
||||
### 1.2 - 09 Jul 2013
|
||||
- Add activity timeline
|
||||
- Bugfix for Git 1.8.1.5 or later
|
||||
- Allow multi-byte characters as label
|
||||
- Fix some bugs
|
||||
|
||||
### 1.1 - 05 Jul 2013
|
||||
- Fix some bugs
|
||||
- Upgrade to JGit 3.0
|
||||
|
||||
### 1.0 - 04 Jul 2013
|
||||
|
||||
- This is a first public release.
|
||||
- This is a first public release
|
||||
|
||||
61
build.xml
Normal file
61
build.xml
Normal file
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<project name="gitbucket" default="all" basedir=".">
|
||||
|
||||
<property name="target.dir" value="target"/>
|
||||
<property name="embed.classes.dir" value="${target.dir}/embed-classes"/>
|
||||
<property name="jetty.dir" value="embed-jetty"/>
|
||||
<property name="scala.version" value="2.11"/>
|
||||
<property name="gitbucket.version" value="3.0.0"/>
|
||||
<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="all" depends="rename">
|
||||
</target>
|
||||
|
||||
|
||||
</project>
|
||||
13
contrib/README.md
Normal file
13
contrib/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Contrib Notes #
|
||||
|
||||
The configuration script adapts according to the OS.
|
||||
The `linux` directory contains scripts for Ubuntu and RedHat.
|
||||
The Mac scripts have been folded in as well.
|
||||
Common scripts are in this directory.
|
||||
|
||||
This version of scripts has so far only been tested on Ubuntu and Mac. Someone else will have to test on RedHat.
|
||||
|
||||
To run:
|
||||
|
||||
1. Edit `gitbucket.conf` to suit.
|
||||
2. Type: `install`
|
||||
62
contrib/gitbucket.conf
Normal file
62
contrib/gitbucket.conf
Normal file
@@ -0,0 +1,62 @@
|
||||
# Configuration section is below. Ignore this part
|
||||
|
||||
function isUbuntu {
|
||||
if [ -f /etc/lsb-release ]; then
|
||||
grep -i ubuntu /etc/lsb-release | head -n 1 | cut -d \ -f 1 | cut -d = -f 2
|
||||
fi
|
||||
}
|
||||
|
||||
function isRedHat {
|
||||
if [ -d "/etc/rc.d/init.d" ]; then echo yes; fi
|
||||
}
|
||||
|
||||
function isMac {
|
||||
if [[ "$(uname -a | cut -d \ -f 1 )" == "Darwin" ]]; then echo yes; fi
|
||||
}
|
||||
|
||||
#
|
||||
# Configuration section start
|
||||
#
|
||||
|
||||
# Bind host
|
||||
GITBUCKET_HOST=0.0.0.0
|
||||
|
||||
# Other Java option
|
||||
GITBUCKET_JVM_OPTS=-Dmail.smtp.starttls.enable=true
|
||||
|
||||
# Data directory, holds repositories
|
||||
GITBUCKET_HOME=/var/lib/gitbucket
|
||||
|
||||
GITBUCKET_LOG_DIR=/var/log/gitbucket
|
||||
|
||||
# Server port
|
||||
GITBUCKET_PORT=8080
|
||||
|
||||
# URL prefix for the GitBucket page (http://<host>:<port>/<prefix>/)
|
||||
GITBUCKET_PREFIX=
|
||||
|
||||
# Directory where GitBucket is installed
|
||||
# Configuration is stored here:
|
||||
GITBUCKET_DIR=/usr/share/gitbucket
|
||||
GITBUCKET_WAR_DIR=$GITBUCKET_DIR/lib
|
||||
|
||||
# Path to the WAR file
|
||||
GITBUCKET_WAR_FILE=$GITBUCKET_WAR_DIR/gitbucket.war
|
||||
|
||||
# GitBucket version to fetch when installing
|
||||
GITBUCKET_VERSION=2.1
|
||||
|
||||
#
|
||||
# End of configuration section. Ignore this part
|
||||
#
|
||||
if [ `isUbuntu` ]; then
|
||||
GITBUCKET_SERVICE=/etc/init.d/gitbucket
|
||||
elif [ `isRedHat` ]; then
|
||||
GITBUCKET_SERVICE=/etc/rc.d/init.d
|
||||
elif [ `isMac` ]; then
|
||||
GITBUCKET_SERVICE=/Library/StartupItems/GitBucket/GitBucket
|
||||
else
|
||||
echo "Don't know how to install onto this OS"
|
||||
exit -2
|
||||
fi
|
||||
|
||||
138
contrib/gitbucket.init
Normal file
138
contrib/gitbucket.init
Normal file
@@ -0,0 +1,138 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# RedHat: /etc/rc.d/init.d/gitbucket
|
||||
# Ubuntu: /etc/init.d/gitbucket
|
||||
# Mac OS/X: /Library/StartupItems/GitBucket
|
||||
#
|
||||
# Starts the GitBucket server
|
||||
#
|
||||
# chkconfig: 345 60 40
|
||||
# description: Run GitBucket server
|
||||
# processname: java
|
||||
|
||||
set -e
|
||||
|
||||
[ -f /etc/rc.d/init.d/functions ] && source /etc/rc.d/init.d/functions # RedHat
|
||||
[ -f /etc/rc.common ] && source /etc/rc.common # Mac OS/X
|
||||
|
||||
# Default values
|
||||
GITBUCKET_HOME=/var/lib/gitbucket
|
||||
GITBUCKET_WAR_FILE=/usr/share/gitbucket/lib/gitbucket.war
|
||||
|
||||
# Pull in cq settings
|
||||
[ -f /etc/sysconfig/gitbucket ] && source /etc/sysconfig/gitbucket # RedHat
|
||||
[ -f gitbucket.conf ] && source gitbucket.conf # For all systems
|
||||
|
||||
# Location of the log and PID file
|
||||
LOG_FILE=$GITBUCKET_LOG_DIR/run.log
|
||||
PID_FILE=/var/run/gitbucket.pid
|
||||
|
||||
RED='\033[1m\E[37;41m'
|
||||
GREEN='\033[1m\E[37;42m'
|
||||
OFF='\E[0m'
|
||||
|
||||
if [ -z "$(which success)" ]; then
|
||||
function success {
|
||||
printf "%b\n" "$GREEN $* $OFF"
|
||||
}
|
||||
fi
|
||||
if [ -z "$(which failure)" ]; then
|
||||
function failure {
|
||||
printf "%b\n" "$RED $* $OFF"
|
||||
}
|
||||
fi
|
||||
|
||||
RETVAL=0
|
||||
|
||||
start() {
|
||||
echo -n $"Starting GitBucket server: "
|
||||
|
||||
START_OPTS=
|
||||
if [ $GITBUCKET_PORT ]; then
|
||||
START_OPTS="${START_OPTS} --port=${GITBUCKET_PORT}"
|
||||
fi
|
||||
if [ $GITBUCKET_PREFIX ]; then
|
||||
START_OPTS="${START_OPTS} --prefix=${GITBUCKET_PREFIX}"
|
||||
fi
|
||||
if [ $GITBUCKET_HOST ]; then
|
||||
START_OPTS="${START_OPTS} --host=${GITBUCKET_HOST}"
|
||||
fi
|
||||
|
||||
GITBUCKET_HOME="${GITBUCKET_HOME}" java $GITBUCKET_JVM_OPTS -jar $GITBUCKET_WAR_FILE $START_OPTS >>$LOG_FILE 2>&1 &
|
||||
RETVAL=$?
|
||||
|
||||
echo $! > $PID_FILE
|
||||
|
||||
if [ $RETVAL -eq 0 ] ; then
|
||||
success "Success"
|
||||
else
|
||||
failure "Exit code $RETVAL"
|
||||
fi
|
||||
|
||||
echo
|
||||
return $RETVAL
|
||||
}
|
||||
|
||||
|
||||
stop() {
|
||||
echo -n $"Stopping GitBucket server: "
|
||||
|
||||
# Run the Java process
|
||||
kill $(cat $PID_FILE 2>/dev/null) >>$LOG_FILE 2>&1
|
||||
RETVAL=$?
|
||||
|
||||
if [ $RETVAL -eq 0 ] ; then
|
||||
rm -f $PID_FILE
|
||||
success "GitBucket stopping"
|
||||
else
|
||||
failure "GitBucket stopping"
|
||||
fi
|
||||
|
||||
echo
|
||||
return $RETVAL
|
||||
}
|
||||
|
||||
|
||||
restart() {
|
||||
stop
|
||||
start
|
||||
}
|
||||
|
||||
## MacOS proxies for System V service hooks:
|
||||
StartService() {
|
||||
start
|
||||
}
|
||||
|
||||
StopService() {
|
||||
stop
|
||||
}
|
||||
|
||||
RestartService() {
|
||||
restart
|
||||
}
|
||||
|
||||
|
||||
if [ `isMac` ]; then
|
||||
RunService "$1"
|
||||
else
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
restart)
|
||||
restart
|
||||
;;
|
||||
status)
|
||||
status -p $PID_FILE java
|
||||
RETVAL=$?
|
||||
;;
|
||||
*)
|
||||
echo $"Usage: $0 [start|stop|restart|status]"
|
||||
RETVAL=2
|
||||
esac
|
||||
exit $RETVAL
|
||||
fi
|
||||
|
||||
69
contrib/install
Executable file
69
contrib/install
Executable file
@@ -0,0 +1,69 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Only tested on Ubuntu 14.04
|
||||
|
||||
# Uses information stored in GitBucket git repo on GitHub as defaults.
|
||||
# Edit gitbucket.conf before running this
|
||||
|
||||
set -e
|
||||
|
||||
GITBUCKET_VERSION=2.1
|
||||
|
||||
if [ ! -f gitbucket.conf ]; then
|
||||
echo "gitbucket.conf not found, aborting"
|
||||
exit -3
|
||||
fi
|
||||
source gitbucket.conf
|
||||
|
||||
function createDir {
|
||||
if [ ! -d "$1" ]; then
|
||||
echo "Making $1 directory."
|
||||
sudo mkdir -p "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "$(which iptables)" ]; then
|
||||
echo "Opening port $GITBUCKET_PORT in firewall."
|
||||
sudo iptables -A INPUT -p tcp --dport $GITBUCKET_PORT -j ACCEPT
|
||||
echo "Please use iptables-persistent:"
|
||||
echo " sudo apt-get install iptables-persistent"
|
||||
echo "After installed, you can save/reload iptables rules anytime:"
|
||||
echo " sudo /etc/init.d/iptables-persistent save"
|
||||
echo " sudo /etc/init.d/iptables-persistent reload"
|
||||
fi
|
||||
|
||||
createDir "$GITBUCKET_HOME"
|
||||
createDir "$GITBUCKET_WAR_DIR"
|
||||
createDir "$GITBUCKET_DIR"
|
||||
createDir "$GITBUCKET_LOG_DIR"
|
||||
|
||||
echo "Fetching GitBucket v$GITBUCKET_VERSION and saving as $GITBUCKET_WAR_FILE"
|
||||
sudo wget -qO "$GITBUCKET_WAR_FILE" https://github.com/takezoe/gitbucket/releases/download/$GITBUCKET_VERSION/gitbucket.war
|
||||
|
||||
sudo rm -f "$GITBUCKET_LOG_DIR/run.log"
|
||||
|
||||
echo "Copying gitbucket.conf to $GITBUCKET_DIR"
|
||||
sudo cp gitbucket.conf $GITBUCKET_DIR
|
||||
if [ `isUbuntu` ] || [ `isRedHat` ]; then
|
||||
sudo cp gitbucket.init "$GITBUCKET_SERVICE"
|
||||
# Install gitbucket as a service that starts when system boots
|
||||
sudo chown root:root $GITBUCKET_SERVICE
|
||||
sudo chmod 755 $GITBUCKET_SERVICE
|
||||
sudo update-rc.d "$(basename $GITBUCKET_SERVICE)" defaults 98 02
|
||||
echo "Starting GitBucket service"
|
||||
sudo $GITBUCKET_SERVICE start
|
||||
elif [ `isMac` ]; then
|
||||
sudo macosx/makePlist
|
||||
echo "Starting GitBucket service"
|
||||
sudo cp gitbucket.conf "$GITBUCKET_SERVICE"
|
||||
sudo cp gitbucket.init "$GITBUCKET_SERVICE"
|
||||
sudo chmod a+x "$GITBUCKET_SERVICE"
|
||||
sudo "$GITBUCKET_SERVICE" start
|
||||
else
|
||||
echo "Don't know how to install this OS"
|
||||
exit -2
|
||||
fi
|
||||
|
||||
if [ $? != 0 ]; then
|
||||
less "$GITBUCKET_LOG_DIR/run.log"
|
||||
fi
|
||||
15
contrib/linux/redhat/README.md
Normal file
15
contrib/linux/redhat/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Contrib Notes #
|
||||
|
||||
RPM spec file and init script for Red Hat Enterprise Linux 6.x.
|
||||
|
||||
To create RPM:
|
||||
1. Edit `../../gitbucket.conf` to suit.
|
||||
2. Edit `gitbucket.init` to suit.
|
||||
3. Edit `gitbucket.spec` to suit.
|
||||
4. Place `gitbucket.spec` to rpm/SPECS/.
|
||||
5. Place `gitbucket.init` and `gitbucket.war` to rpm/SOURCES/.
|
||||
6. Execute `rpmbuild -ba rpm/SPECS/gitbucket.spec`
|
||||
|
||||
This rpm runs gitbucket not as root user but as gitbucket user.
|
||||
This rpm creates user and group named `gitbucket` at installation.
|
||||
This rpm make chkconfig of gitbucket to be on.
|
||||
108
contrib/linux/redhat/gitbucket.init
Normal file
108
contrib/linux/redhat/gitbucket.init
Normal file
@@ -0,0 +1,108 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# RedHat: /etc/rc.d/init.d/gitbucket
|
||||
#
|
||||
# Starts the GitBucket server
|
||||
#
|
||||
# chkconfig: 345 60 40
|
||||
# description: Run GitBucket server
|
||||
# processname: java
|
||||
|
||||
[ -f /etc/rc.d/init.d/functions ] && source /etc/rc.d/init.d/functions # RedHat
|
||||
|
||||
# Default values
|
||||
GITBUCKET_HOME=/var/lib/gitbucket
|
||||
GITBUCKET_WAR_FILE=/usr/share/gitbucket/lib/gitbucket.war
|
||||
|
||||
# Pull in cq settings
|
||||
[ -f /etc/sysconfig/gitbucket ] && source /etc/sysconfig/gitbucket # RedHat
|
||||
[ -f gitbucket.conf ] && source gitbucket.conf # For all systems
|
||||
|
||||
# Location of the log and PID file
|
||||
LOG_FILE=$GITBUCKET_LOG_DIR/run.log
|
||||
|
||||
RED='\033[1m\E[37;41m'
|
||||
GREEN='\033[1m\E[37;42m'
|
||||
OFF='\E[0m'
|
||||
|
||||
RETVAL=0
|
||||
|
||||
start() {
|
||||
echo -n $"Starting GitBucket server: "
|
||||
|
||||
START_OPTS=
|
||||
if [ $GITBUCKET_PORT ]; then
|
||||
START_OPTS="${START_OPTS} --port=${GITBUCKET_PORT}"
|
||||
fi
|
||||
if [ $GITBUCKET_PREFIX ]; then
|
||||
START_OPTS="${START_OPTS} --prefix=${GITBUCKET_PREFIX}"
|
||||
fi
|
||||
if [ $GITBUCKET_HOST ]; then
|
||||
START_OPTS="${START_OPTS} --host=${GITBUCKET_HOST}"
|
||||
fi
|
||||
|
||||
GITBUCKET_HOME="${GITBUCKET_HOME}" daemon --user=gitbucket java $GITBUCKET_JVM_OPTS -jar $GITBUCKET_WAR_FILE $START_OPTS >>$LOG_FILE 2>&1 &
|
||||
sleep 3
|
||||
pgrep -f $GITBUCKET_WAR_FILE >> $LOG_FILE 2>&1
|
||||
RETVAL=$?
|
||||
|
||||
if [ $RETVAL -eq 0 ] ; then
|
||||
success "Success"
|
||||
else
|
||||
failure "Exit code $RETVAL"
|
||||
fi
|
||||
|
||||
echo
|
||||
return $RETVAL
|
||||
}
|
||||
|
||||
|
||||
stop() {
|
||||
echo -n $"Stopping GitBucket server: "
|
||||
|
||||
# Run the Java process
|
||||
pkill -f $GITBUCKET_WAR_FILE >>$LOG_FILE 2>&1
|
||||
RETVAL=$?
|
||||
|
||||
if [ $RETVAL -eq 0 ] ; then
|
||||
success "GitBucket stopping"
|
||||
else
|
||||
failure "GitBucket stopping"
|
||||
fi
|
||||
|
||||
echo
|
||||
return $RETVAL
|
||||
}
|
||||
|
||||
|
||||
restart() {
|
||||
stop
|
||||
start
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
restart)
|
||||
restart
|
||||
;;
|
||||
status)
|
||||
pgrep -f $GITBUCKET_WAR_FILE >> $LOG_FILE 2>&1
|
||||
RETVAL=$?
|
||||
if [ $RETVAL -eq 0 ]; then
|
||||
echo $"GitBucket is running...."
|
||||
else
|
||||
echo $"GitBucket is stopped"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo $"Usage: $0 [start|stop|restart|status]"
|
||||
RETVAL=2
|
||||
esac
|
||||
|
||||
exit $RETVAL
|
||||
|
||||
82
contrib/linux/redhat/gitbucket.spec
Normal file
82
contrib/linux/redhat/gitbucket.spec
Normal file
@@ -0,0 +1,82 @@
|
||||
Name: gitbucket
|
||||
Summary: GitHub clone written with Scala.
|
||||
Version: 2.6
|
||||
Release: 1%{?dist}
|
||||
License: Apache
|
||||
URL: https://github.com/takezoe/gitbucket
|
||||
Group: System/Servers
|
||||
Source0: %{name}.war
|
||||
Source1: %{name}.init
|
||||
Source2: %{name}.conf
|
||||
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
|
||||
BuildArch: noarch
|
||||
Requires: java >= 1.7
|
||||
|
||||
|
||||
%description
|
||||
|
||||
GitBucket is the easily installable GitHub clone written with Scala.
|
||||
|
||||
|
||||
%install
|
||||
[ "%{buildroot}" != / ] && %{__rm} -rf "%{buildroot}"
|
||||
%{__mkdir_p} %{buildroot}{%{_sysconfdir}/{init.d,sysconfig},%{_datarootdir}/%{name}/lib,%{_sharedstatedir}/%{name},%{_localstatedir}/log/%{name}}
|
||||
%{__install} -m 0644 %{SOURCE0} %{buildroot}%{_datarootdir}/%{name}/lib
|
||||
%{__install} -m 0755 %{SOURCE1} %{buildroot}%{_sysconfdir}/init.d/%{name}
|
||||
%{__install} -m 0644 %{SOURCE2} %{buildroot}%{_sysconfdir}/sysconfig/%{name}
|
||||
touch %{buildroot}%{_localstatedir}/log/%{name}/run.log
|
||||
|
||||
%pre
|
||||
/usr/sbin/groupadd -r gitbucket &> /dev/null || :
|
||||
/usr/sbin/useradd -g gitbucket -s /bin/false -r -c "GitBucket GitHub clone" -d %{_sharedstatedir}/%{name} gitbucket &> /dev/null || :
|
||||
|
||||
%post
|
||||
/sbin/chkconfig --add gitbucket
|
||||
|
||||
%preun
|
||||
if [ "$1" = 0 ]; then
|
||||
/sbin/service gitbucket stop > /dev/null 2>&1
|
||||
/sbin/chkconfig --del gitbucket
|
||||
fi
|
||||
exit 0
|
||||
|
||||
%postun
|
||||
if [ "$1" -ge 1 ]; then
|
||||
/sbin/service gitbucket restart > /dev/null 2>&1
|
||||
fi
|
||||
exit 0
|
||||
|
||||
%clean
|
||||
[ "%{buildroot}" != / ] && %{__rm} -rf "%{buildroot}"
|
||||
|
||||
|
||||
%files
|
||||
%defattr(-,root,root,-)
|
||||
%{_datarootdir}/%{name}/lib/%{name}.war
|
||||
%config %{_sysconfdir}/init.d/%{name}
|
||||
%config(noreplace) %{_sysconfdir}/sysconfig/%{name}
|
||||
%attr(0755,gitbucket,gitbucket) %{_sharedstatedir}/%{name}
|
||||
%attr(0750,gitbucket,gitbucket) %{_localstatedir}/log/%{name}
|
||||
|
||||
|
||||
%changelog
|
||||
* Mon Nov 24 2014 Toru Takahashi <torutk at gmail.com>
|
||||
- Version bump to v2.6
|
||||
|
||||
* Sun Nov 09 2014 Toru Takahashi <torutk at gmail.com>
|
||||
- Version bump to v2.5
|
||||
|
||||
* Sun Oct 26 2014 Toru Takahashi <torutk at gmail.com>
|
||||
- Version bump to v2.4.1
|
||||
|
||||
* Mon Jul 21 2014 Toru Takahashi <torutk at gmail.com>
|
||||
- execute as gitbucket user
|
||||
|
||||
* Sun Jul 20 2014 Toru Takahashi <torutk at gmail.com>
|
||||
- Version bump to v2.1.
|
||||
|
||||
* Mon Oct 28 2013 Jiri Tyr <jiri_DOT_tyr at gmail.com>
|
||||
- Version bump to v1.7.
|
||||
|
||||
* Thu Oct 17 2013 Jiri Tyr <jiri_DOT_tyr at gmail.com>
|
||||
- First build.
|
||||
28
contrib/macosx/makePlist
Executable file
28
contrib/macosx/makePlist
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
|
||||
# From http://docstore.mik.ua/orelly/unix3/mac/ch02_02.htm
|
||||
source gitbucket.conf
|
||||
GITBUCKET_SERVICE_DIR=`dirname "$GITBUCKET_SERVICE"`
|
||||
mkdir -p "$GITBUCKET_SERVICE_DIR"
|
||||
cat << EOF > "$GITBUCKET_SERVICE_DIR/gitbucket.plist"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>gitbucket</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/bin/java</string>
|
||||
<string>$GITBUCKET_JVM_OPTS</string>
|
||||
<string>-jar</string>
|
||||
<string>gitbucket.war</string>
|
||||
<string>--host=$GITBUCKET_HOST</string>
|
||||
<string>--port=$GITBUCKET_PORT</string>
|
||||
<string>--https=true</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
22
doc/activity.md
Normal file
22
doc/activity.md
Normal file
@@ -0,0 +1,22 @@
|
||||
Activity Timeline
|
||||
========
|
||||
GitBucket records several types of user activity to ```ACTIVITY``` table. Activity types are shown below:
|
||||
|
||||
type | message | additional information
|
||||
------------------|------------------------------------------------------|------------------------
|
||||
create_repository |$user created $owner/$repo |-
|
||||
open_issue |$user opened issue $owner/$repo#$issueId |-
|
||||
close_issue |$user closed issue $owner/$repo#$issueId |-
|
||||
close_issue |$user closed pull request $owner/$repo#$issueId |-
|
||||
reopen_issue |$user reopened issue $owner/$repo#$issueId |-
|
||||
comment_issue |$user commented on issue $owner/$repo#$issueId |-
|
||||
comment_issue |$user commented on pull request $owner/$repo#$issueId |-
|
||||
create_wiki |$user created the $owner/$repo wiki |$page
|
||||
edit_wiki |$user edited the $owner/$repo wiki |$page<br>$page:$commitId(since 1.5)
|
||||
push |$user pushed to $owner/$repo#$branch to $owner/$repo |$commitId:$shortMessage\n*
|
||||
create_tag |$user created tag $tag at $owner/$repo |-
|
||||
create_branch |$user created branch $branch at $owner/$repo |-
|
||||
delete_branch |$user deleted branch $branch at $owner/$repo |-
|
||||
fork |$user forked $owner/$repo to $owner/$repo |-
|
||||
open_pullreq |$user opened pull request $owner/$repo#issueId |-
|
||||
merge_pullreq |$user merge pull request $owner/$repo#issueId |-
|
||||
37
doc/auto_update.md
Normal file
37
doc/auto_update.md
Normal file
@@ -0,0 +1,37 @@
|
||||
Automatic Schema Updating
|
||||
========
|
||||
GitBucket uses H2 database to manage project and account data. GitBucket updates database schema automatically in the first run after the upgrading.
|
||||
|
||||
To release a new version of GitBucket, add the version definition to the [servlet.AutoUpdate](https://github.com/takezoe/gitbucket/blob/master/src/main/scala/servlet/AutoUpdateListener.scala) at first.
|
||||
|
||||
```scala
|
||||
object AutoUpdate {
|
||||
...
|
||||
/**
|
||||
* The history of versions. A head of this sequence is the current BitBucket version.
|
||||
*/
|
||||
val versions = Seq(
|
||||
Version(1, 0)
|
||||
)
|
||||
...
|
||||
```
|
||||
|
||||
Next, add a SQL file which updates database schema into [/src/main/resources/update/](https://github.com/takezoe/gitbucket/tree/master/src/main/resources/update) as ```MAJOR_MINOR.sql```.
|
||||
|
||||
GitBucket stores the current version to ```GITBUCKET_HOME/version``` and checks it at start-up. If the stored version differs from the actual version, it executes differences of SQL files between the stored version and the actual version. And ```GITBUCKET_HOME/version``` is updated by the actual version.
|
||||
|
||||
We can also add any Scala code for upgrade GitBucket which modifies resources other than database. Override ```Version.update``` like below:
|
||||
|
||||
```scala
|
||||
val versions = Seq(
|
||||
new Version(1, 3){
|
||||
override def update(conn: Connection): Unit = {
|
||||
super.update(conn)
|
||||
// Add any code here!
|
||||
}
|
||||
},
|
||||
Version(1, 2),
|
||||
Version(1, 1),
|
||||
Version(1, 0)
|
||||
)
|
||||
```
|
||||
48
doc/comment_action.md
Normal file
48
doc/comment_action.md
Normal file
@@ -0,0 +1,48 @@
|
||||
About Action in Issue Comment
|
||||
========
|
||||
After the issue creation at GitBucket, users can add comments or close it.
|
||||
The details are saved at ```ISSUE_COMMENT``` table.
|
||||
|
||||
To determine if it was any operation, you see the ```ACTION``` column.
|
||||
|
||||
|ACTION|
|
||||
|--------|
|
||||
|comment|
|
||||
|close_comment|
|
||||
|reopen_comment|
|
||||
|close|
|
||||
|reopen|
|
||||
|commit|
|
||||
|merge|
|
||||
|delete_branch|
|
||||
|refer|
|
||||
|
||||
#####comment
|
||||
This value is saved when users have made a normal comment.
|
||||
|
||||
#####close_comment, reopen_comment
|
||||
These values are saved when users have reopened or closed the issue with comments.
|
||||
|
||||
#####close, reopen
|
||||
These values are saved when users have reopened or closed the issue.
|
||||
At the same time, store the fixed value(i.e. "Close" or "Reopen") to the ```CONTENT``` column.
|
||||
Therefore, this comment is not displayed, and not counted as a comment.
|
||||
|
||||
#####commit
|
||||
This value is saved when users have pushed including the ```#issueId``` to the commit message.
|
||||
At the same time, store it to the ```CONTENT``` column with its commit id.
|
||||
This comment is displayed. But it can not be edited by all users, and also not counted as a comment.
|
||||
|
||||
#####merge
|
||||
This value is saved when users have merged the pull request.
|
||||
At the same time, store the message to the ```CONTENT``` column.
|
||||
This comment is displayed. But it can not be edited by all users, and also not counted as a comment.
|
||||
|
||||
#####delete_branch
|
||||
This value is saved when users have deleted the branch. Users can delete branch after merging pull request which is requested from the same repository.
|
||||
At the same time, store it to the ```CONTENT``` column with the deleted branch name.
|
||||
Therefore, this comment is not displayed, and not counted as a comment.
|
||||
|
||||
#####refer
|
||||
This value is saved when other issue or issue comment contains reference to the issue like ```#issueId```.
|
||||
At the same time, store id and title of the referrer issue as ```id:title```.
|
||||
44
doc/directory.md
Normal file
44
doc/directory.md
Normal file
@@ -0,0 +1,44 @@
|
||||
Directory Structure
|
||||
========
|
||||
GitBucket persists all data into __HOME/.gitbucket__ in default (In 1.9 or before, HOME/gitbucket is default).
|
||||
|
||||
This directory has following structure:
|
||||
|
||||
```
|
||||
* /HOME/gitbucket
|
||||
* /repositoties
|
||||
* /USER_NAME
|
||||
* / REPO_NAME.git (substance of repository. GitServlet sees this directory)
|
||||
* / REPO_NAME
|
||||
* /issues (files which are attached to issue)
|
||||
* / REPO_NAME.wiki.git (wiki repository)
|
||||
* /data
|
||||
* /USER_NAME
|
||||
* /files
|
||||
* avatar.xxx (image file of user avatar)
|
||||
* /plugins
|
||||
* /PLUGIN_NAME
|
||||
* plugin.js
|
||||
* /tmp
|
||||
* /_upload
|
||||
* /SESSION_ID (removed at session timeout)
|
||||
* current time millis + random 10 alphanumeric chars (temporary file for file uploading)
|
||||
* /USER_NAME
|
||||
* /init-REPO_NAME (used in repository creation and removed after it) ... unused since 1.8
|
||||
* /REPO_NAME.wiki (working directory for wiki repository) ... unused since 1.8
|
||||
* /REPO_NAME
|
||||
* /download (temporary directories are created under this directory)
|
||||
```
|
||||
|
||||
There are some ways to specify the data directory instead of the default location.
|
||||
|
||||
1. Environment variable __GITBUCKET_HOME__
|
||||
2. System property __gitbucket.home__ (e.g. ```-Dgitbucket.home=PATH_TO_DATADIR```)
|
||||
3. Command line option for embedded Jetty (e.g. ```java -jar gitbucket.war --data=PATH_TO_DATADIR```)
|
||||
4. Context parameter __gitbucket.home__ in web.xml like below:
|
||||
```xml
|
||||
<context-param>
|
||||
<param-name>gitbucket.home</param-name>
|
||||
<param-value>PATH_TO_DATADIR</param-value>
|
||||
</context-param>
|
||||
```
|
||||
38
doc/how_to_run.md
Normal file
38
doc/how_to_run.md
Normal file
@@ -0,0 +1,38 @@
|
||||
How to run from the source tree
|
||||
========
|
||||
|
||||
for Testers
|
||||
--------
|
||||
|
||||
If you want to test GitBucket, input following command at the root directory of the source tree.
|
||||
|
||||
```
|
||||
C:\gitbucket> sbt ~container:start
|
||||
```
|
||||
|
||||
Then access to `http://localhost:8080/` by your browser. The default administrator account is `root` and password is `root`.
|
||||
|
||||
for Developers
|
||||
--------
|
||||
If you want to modify source code and confirm it, you can run GitBucket in auto reloading mode as following:
|
||||
|
||||
```
|
||||
C:\gitbucket> sbt
|
||||
...
|
||||
> container:start
|
||||
...
|
||||
> ~ ;copy-resources;aux-compile
|
||||
```
|
||||
|
||||
Build war file
|
||||
--------
|
||||
|
||||
To build war file, run the following command:
|
||||
|
||||
```
|
||||
C:\gitbucket> sbt package
|
||||
```
|
||||
|
||||
`gitbucket_2.11-x.x.x.war` is generated into `target/scala-2.11`.
|
||||
|
||||
To build executable war file, run Ant 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. Please note the current build.xml works on Windows only. Replace `sbt.bat` with `sbt.sh` in build.xml if you want to run it on Linux.
|
||||
23
doc/notification.md
Normal file
23
doc/notification.md
Normal file
@@ -0,0 +1,23 @@
|
||||
Notification Email
|
||||
========
|
||||
|
||||
GitBucket sends email to target users by enabling the notification email by an administrator.
|
||||
|
||||
The timing of the notification are as follows:
|
||||
|
||||
##### at the issue registration (new issue, new pull request)
|
||||
When a record is saved into the ```ISSUE``` table, GitBucket does the notification.
|
||||
|
||||
##### at the comment registration
|
||||
Among the records in the ```ISSUE_COMMENT``` table, them to be counted as a comment (i.e. the record ```ACTION``` column value is "comment" or "close_comment" or "reopen_comment") are saved, GitBucket does the notification.
|
||||
|
||||
##### at the status update (close, reopen, merge)
|
||||
When the ```CLOSED``` column value is updated, GitBucket does the notification.
|
||||
|
||||
Notified users are as follows:
|
||||
|
||||
* individual repository's owner
|
||||
* collaborators
|
||||
* participants
|
||||
|
||||
However, the operation in person is excluded from the target.
|
||||
10
doc/readme.md
Normal file
10
doc/readme.md
Normal file
@@ -0,0 +1,10 @@
|
||||
Developer's Guide
|
||||
========
|
||||
* [How to run from source tree](how_to_run.md)
|
||||
* [Directory Structure](directory.md)
|
||||
* [Mapping and Validation](validation.md)
|
||||
* Authentication in Controller (not yet)
|
||||
* [About Action in Issue Comment](comment_action.md)
|
||||
* [Activity Types](activity.md)
|
||||
* [Notification Email](notification.md)
|
||||
* [Automatic Schema Updating](auto_update.md)
|
||||
71
doc/validation.md
Normal file
71
doc/validation.md
Normal file
@@ -0,0 +1,71 @@
|
||||
Mapping and Validation
|
||||
========
|
||||
GitBucket uses [scalatra-forms](https://github.com/takezoe/scalatra-forms) to validate request parameters and map them to the scala object. This is inspired by Play2 form mapping / validation.
|
||||
|
||||
At first, define the mapping as following:
|
||||
|
||||
```scala
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
case class RegisterForm(name: String, description: String)
|
||||
|
||||
val form = mapping(
|
||||
"name" -> text(required, maxlength(40)),
|
||||
"description" -> text()
|
||||
)(RegisterForm.apply)
|
||||
```
|
||||
|
||||
The servlet have to mixed in ```jp.sf.amateras.scalatra.forms.ClientSideValidationFormSupport``` to validate request parameters and take mapped object. It validates request parameters before action. If any errors are detected, it throws an exception.
|
||||
|
||||
```scala
|
||||
class RegisterServlet extends ScalatraServlet with ClientSideValidationFormSupport {
|
||||
post("/register", form) { form: RegisterForm =>
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In the view template, you can add client-side validation by adding ```validate="true"``` to your form. Error messages are set to ```span#error-<fieldname>```.
|
||||
|
||||
```html
|
||||
<form method="POST" action="/register" validate="true">
|
||||
Name: <input type="name" type="text">
|
||||
<span class="error" id="error-name"></span>
|
||||
<br/>
|
||||
Description: <input type="description" type="text">
|
||||
<span class="error" id="error-description"></span>
|
||||
<br/>
|
||||
<input type="submit" value="Register"/>
|
||||
</form>
|
||||
```
|
||||
|
||||
Client-side validation calls ```<form-action>/validate``` to validate form contents. It returns a validation result as JSON. In this case, form action is ```/register```, so ```/register/validate``` is called before submitting a form. ```ClientSideValidationFormSupport``` adds this JSON API automatically.
|
||||
|
||||
For Ajax request, you have to use '''ajaxGet''' or '''ajaxPost''' to define action. It almost same as '''get''' or '''post'''. You can implement actions which handle Ajax request as same as normal actions.
|
||||
Small difference is they return validation errors as JSON.
|
||||
|
||||
```scala
|
||||
ajaxPost("/register", form){ form =>
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
You can call these actions using jQuery as below:
|
||||
|
||||
```javascript
|
||||
$('#register').click(function(e){
|
||||
$.ajax($(this).attr('action'), {
|
||||
type: 'POST',
|
||||
data: {
|
||||
name: $('#name').val(),
|
||||
mail: $('#mail').val()
|
||||
}
|
||||
})
|
||||
.done(function(data){
|
||||
$('#result').text('Registered!');
|
||||
})
|
||||
.fail(function(data, status){
|
||||
displayErrors($.parseJSON(data.responseText));
|
||||
});
|
||||
});
|
||||
```
|
||||
BIN
embed-jetty/javax.servlet-3.0.0.v201112011016.jar
Normal file
BIN
embed-jetty/javax.servlet-3.0.0.v201112011016.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-continuation-8.1.16.v20140903.jar
Normal file
BIN
embed-jetty/jetty-continuation-8.1.16.v20140903.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-http-8.1.16.v20140903.jar
Normal file
BIN
embed-jetty/jetty-http-8.1.16.v20140903.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-io-8.1.16.v20140903.jar
Normal file
BIN
embed-jetty/jetty-io-8.1.16.v20140903.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-security-8.1.16.v20140903.jar
Normal file
BIN
embed-jetty/jetty-security-8.1.16.v20140903.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-server-8.1.16.v20140903.jar
Normal file
BIN
embed-jetty/jetty-server-8.1.16.v20140903.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-servlet-8.1.16.v20140903.jar
Normal file
BIN
embed-jetty/jetty-servlet-8.1.16.v20140903.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-util-8.1.16.v20140903.jar
Normal file
BIN
embed-jetty/jetty-util-8.1.16.v20140903.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-webapp-8.1.16.v20140903.jar
Normal file
BIN
embed-jetty/jetty-webapp-8.1.16.v20140903.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-xml-8.1.16.v20140903.jar
Normal file
BIN
embed-jetty/jetty-xml-8.1.16.v20140903.jar
Normal file
Binary file not shown.
11
embed-jetty/update.sh
Executable file
11
embed-jetty/update.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/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
|
||||
9
etc/deploy-assemby-jar.sh
Executable file
9
etc/deploy-assemby-jar.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
mvn deploy:deploy-file \
|
||||
-DgroupId=gitbucket\
|
||||
-DartifactId=gitbucket-assembly\
|
||||
-Dversion=3.0.0\
|
||||
-Dpackaging=jar\
|
||||
-Dfile=../target/scala-2.11/gitbucket-assembly-3.0.0.jar\
|
||||
-DrepositoryId=sourceforge.jp\
|
||||
-Durl=scp://shell.sourceforge.jp/home/groups/a/am/amateras/htdocs/mvn/
|
||||
@@ -23,8 +23,8 @@
|
||||
<constraint>
|
||||
<height>-1</height>
|
||||
<width>-1</width>
|
||||
<x>37</x>
|
||||
<y>36</y>
|
||||
<x>33</x>
|
||||
<y>18</y>
|
||||
</constraint>
|
||||
<sourceConnections/>
|
||||
<targetConnections>
|
||||
@@ -51,8 +51,8 @@
|
||||
<constraint>
|
||||
<height>-1</height>
|
||||
<width>-1</width>
|
||||
<x>751</x>
|
||||
<y>47</y>
|
||||
<x>723</x>
|
||||
<y>138</y>
|
||||
</constraint>
|
||||
<sourceConnections>
|
||||
<net.java.amateras.db.visual.model.ForeignKeyModel>
|
||||
@@ -79,8 +79,8 @@
|
||||
<constraint>
|
||||
<height>-1</height>
|
||||
<width>-1</width>
|
||||
<x>882</x>
|
||||
<y>239</y>
|
||||
<x>1182</x>
|
||||
<y>339</y>
|
||||
</constraint>
|
||||
<sourceConnections/>
|
||||
<targetConnections>
|
||||
@@ -108,8 +108,8 @@
|
||||
<constraint>
|
||||
<height>-1</height>
|
||||
<width>-1</width>
|
||||
<x>940</x>
|
||||
<y>615</y>
|
||||
<x>1301</x>
|
||||
<y>836</y>
|
||||
</constraint>
|
||||
<sourceConnections>
|
||||
<net.java.amateras.db.visual.model.ForeignKeyModel reference="../../.."/>
|
||||
@@ -138,8 +138,8 @@
|
||||
<constraint>
|
||||
<height>-1</height>
|
||||
<width>-1</width>
|
||||
<x>420</x>
|
||||
<y>758</y>
|
||||
<x>684</x>
|
||||
<y>858</y>
|
||||
</constraint>
|
||||
<sourceConnections>
|
||||
<net.java.amateras.db.visual.model.ForeignKeyModel reference="../../.."/>
|
||||
@@ -167,8 +167,8 @@
|
||||
<constraint>
|
||||
<height>-1</height>
|
||||
<width>-1</width>
|
||||
<x>307</x>
|
||||
<y>356</y>
|
||||
<x>293</x>
|
||||
<y>478</y>
|
||||
</constraint>
|
||||
<sourceConnections>
|
||||
<net.java.amateras.db.visual.model.ForeignKeyModel>
|
||||
@@ -210,8 +210,8 @@
|
||||
<constraint>
|
||||
<height>-1</height>
|
||||
<width>-1</width>
|
||||
<x>641</x>
|
||||
<y>569</y>
|
||||
<x>875</x>
|
||||
<y>677</y>
|
||||
</constraint>
|
||||
<sourceConnections>
|
||||
<net.java.amateras.db.visual.model.ForeignKeyModel>
|
||||
@@ -283,9 +283,14 @@
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>MILESTONE_NAME</columnName>
|
||||
<logicalName>Milestone Name</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../net.java.amateras.db.visual.model.ColumnModel/columnType"/>
|
||||
<columnName>TITLE</columnName>
|
||||
<logicalName>Title</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType">
|
||||
<name>VARCHAR</name>
|
||||
<logicalName>文字列</logicalName>
|
||||
<supportSize>true</supportSize>
|
||||
<type>12</type>
|
||||
</columnType>
|
||||
<size>100</size>
|
||||
<notNull>true</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
@@ -293,6 +298,49 @@
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>DESCRIPTION</columnName>
|
||||
<logicalName>Description</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType">
|
||||
<name>TEXT</name>
|
||||
<logicalName>文字列</logicalName>
|
||||
<supportSize>true</supportSize>
|
||||
<type>2005</type>
|
||||
</columnType>
|
||||
<size></size>
|
||||
<notNull>false</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
<description></description>
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>DUE_DATE</columnName>
|
||||
<logicalName>Due Date</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType">
|
||||
<name>TIMESTAMP</name>
|
||||
<logicalName>日時</logicalName>
|
||||
<supportSize>false</supportSize>
|
||||
<type>93</type>
|
||||
</columnType>
|
||||
<size>10</size>
|
||||
<notNull>false</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
<description></description>
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>CLOSED_DATE</columnName>
|
||||
<logicalName>Closed Date</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../net.java.amateras.db.visual.model.ColumnModel[6]/columnType"/>
|
||||
<size>10</size>
|
||||
<notNull>false</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
<description></description>
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
</columns>
|
||||
<indices/>
|
||||
<backgroundColor>
|
||||
@@ -350,6 +398,36 @@
|
||||
</entry>
|
||||
</references>
|
||||
</net.java.amateras.db.visual.model.ForeignKeyModel>
|
||||
<net.java.amateras.db.visual.model.ForeignKeyModel>
|
||||
<listeners serialization="custom">
|
||||
<java.beans.PropertyChangeSupport>
|
||||
<default>
|
||||
<source class="net.java.amateras.db.visual.model.ForeignKeyModel" reference="../../../.."/>
|
||||
<propertyChangeSupportSerializedDataVersion>2</propertyChangeSupportSerializedDataVersion>
|
||||
</default>
|
||||
<null/>
|
||||
</java.beans.PropertyChangeSupport>
|
||||
</listeners>
|
||||
<source class="net.java.amateras.db.visual.model.TableModel" reference="../../.."/>
|
||||
<target class="net.java.amateras.db.visual.model.TableModel" reference="../../../../../../../../../../../../../../../../../.."/>
|
||||
<foreignKeyName>ISSUE_FK_2</foreignKeyName>
|
||||
<references>
|
||||
<entry>
|
||||
<net.java.amateras.db.visual.model.ColumnModel reference="../../../../net.java.amateras.db.visual.model.ForeignKeyModel[3]/references/entry/net.java.amateras.db.visual.model.ColumnModel"/>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>ASSIGNED_USER_NAME</columnName>
|
||||
<logicalName>Assinged User Name</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../../../../net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/columns/net.java.amateras.db.visual.model.ColumnModel[4]/columnType"/>
|
||||
<size>100</size>
|
||||
<notNull>false</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
<description></description>
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
</entry>
|
||||
</references>
|
||||
</net.java.amateras.db.visual.model.ForeignKeyModel>
|
||||
</sourceConnections>
|
||||
<targetConnections>
|
||||
<net.java.amateras.db.visual.model.ForeignKeyModel>
|
||||
@@ -375,8 +453,8 @@
|
||||
<constraint>
|
||||
<height>-1</height>
|
||||
<width>-1</width>
|
||||
<x>26</x>
|
||||
<y>660</y>
|
||||
<x>18</x>
|
||||
<y>776</y>
|
||||
</constraint>
|
||||
<sourceConnections>
|
||||
<net.java.amateras.db.visual.model.ForeignKeyModel reference="../../.."/>
|
||||
@@ -462,6 +540,22 @@
|
||||
<autoIncrement>true</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>ACTION</columnName>
|
||||
<logicalName>Action</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType">
|
||||
<name>VARCHAR</name>
|
||||
<logicalName>文字列</logicalName>
|
||||
<supportSize>true</supportSize>
|
||||
<type>12</type>
|
||||
</columnType>
|
||||
<size>20</size>
|
||||
<notNull>true</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
<description>Expand to VARCHAR(20) from VARCHAR(10) in 1.3</description>
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
<net.java.amateras.db.visual.model.ColumnModel reference="../../sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/references/entry/net.java.amateras.db.visual.model.ColumnModel[2]"/>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>CONTENT</columnName>
|
||||
@@ -498,7 +592,7 @@
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>UPDATED_DATE</columnName>
|
||||
<logicalName>Updated Date</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../net.java.amateras.db.visual.model.ColumnModel[7]/columnType"/>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../net.java.amateras.db.visual.model.ColumnModel[8]/columnType"/>
|
||||
<size>10</size>
|
||||
<notNull>true</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
@@ -572,10 +666,11 @@
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
<net.java.amateras.db.visual.model.ColumnModel reference="../../sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[4]/references/entry/net.java.amateras.db.visual.model.ColumnModel[2]"/>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>TITLE</columnName>
|
||||
<logicalName>Title</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../../targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/columns/net.java.amateras.db.visual.model.ColumnModel[6]/columnType"/>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../../targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/columns/net.java.amateras.db.visual.model.ColumnModel[7]/columnType"/>
|
||||
<size></size>
|
||||
<notNull>true</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
@@ -586,7 +681,7 @@
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>CONTENT</columnName>
|
||||
<logicalName>Content</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../../targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/columns/net.java.amateras.db.visual.model.ColumnModel[6]/columnType"/>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../../targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/columns/net.java.amateras.db.visual.model.ColumnModel[7]/columnType"/>
|
||||
<size></size>
|
||||
<notNull>true</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
@@ -597,7 +692,7 @@
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>REGISTERED_DATE</columnName>
|
||||
<logicalName>Registered Date</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../../targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/columns/net.java.amateras.db.visual.model.ColumnModel[7]/columnType"/>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../../targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/columns/net.java.amateras.db.visual.model.ColumnModel[8]/columnType"/>
|
||||
<size>10</size>
|
||||
<notNull>true</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
@@ -608,7 +703,7 @@
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>UPDATED_DATE</columnName>
|
||||
<logicalName>Updated Date</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../../targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/columns/net.java.amateras.db.visual.model.ColumnModel[7]/columnType"/>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../../targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/columns/net.java.amateras.db.visual.model.ColumnModel[8]/columnType"/>
|
||||
<size>10</size>
|
||||
<notNull>true</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
@@ -801,8 +896,8 @@
|
||||
<constraint>
|
||||
<height>-1</height>
|
||||
<width>-1</width>
|
||||
<x>388</x>
|
||||
<y>166</y>
|
||||
<x>481</x>
|
||||
<y>361</y>
|
||||
</constraint>
|
||||
<sourceConnections>
|
||||
<net.java.amateras.db.visual.model.ForeignKeyModel reference="../../.."/>
|
||||
@@ -862,6 +957,250 @@
|
||||
</net.java.amateras.db.visual.model.ForeignKeyModel>
|
||||
<net.java.amateras.db.visual.model.ForeignKeyModel reference="../net.java.amateras.db.visual.model.ForeignKeyModel[2]/source/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel"/>
|
||||
<net.java.amateras.db.visual.model.ForeignKeyModel reference="../net.java.amateras.db.visual.model.ForeignKeyModel[2]/source/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel"/>
|
||||
<net.java.amateras.db.visual.model.ForeignKeyModel>
|
||||
<listeners serialization="custom">
|
||||
<java.beans.PropertyChangeSupport>
|
||||
<default>
|
||||
<source class="net.java.amateras.db.visual.model.ForeignKeyModel" reference="../../../.."/>
|
||||
<propertyChangeSupportSerializedDataVersion>2</propertyChangeSupportSerializedDataVersion>
|
||||
</default>
|
||||
<null/>
|
||||
</java.beans.PropertyChangeSupport>
|
||||
</listeners>
|
||||
<source class="net.java.amateras.db.visual.model.TableModel">
|
||||
<listeners serialization="custom">
|
||||
<java.beans.PropertyChangeSupport>
|
||||
<default>
|
||||
<source class="net.java.amateras.db.visual.model.TableModel" reference="../../../.."/>
|
||||
<propertyChangeSupportSerializedDataVersion>2</propertyChangeSupportSerializedDataVersion>
|
||||
</default>
|
||||
<null/>
|
||||
</java.beans.PropertyChangeSupport>
|
||||
</listeners>
|
||||
<constraint>
|
||||
<height>-1</height>
|
||||
<width>-1</width>
|
||||
<x>1199</x>
|
||||
<y>25</y>
|
||||
</constraint>
|
||||
<sourceConnections>
|
||||
<net.java.amateras.db.visual.model.ForeignKeyModel reference="../../.."/>
|
||||
<net.java.amateras.db.visual.model.ForeignKeyModel>
|
||||
<listeners serialization="custom">
|
||||
<java.beans.PropertyChangeSupport>
|
||||
<default>
|
||||
<source class="net.java.amateras.db.visual.model.ForeignKeyModel" reference="../../../.."/>
|
||||
<propertyChangeSupportSerializedDataVersion>2</propertyChangeSupportSerializedDataVersion>
|
||||
</default>
|
||||
<null/>
|
||||
</java.beans.PropertyChangeSupport>
|
||||
</listeners>
|
||||
<source class="net.java.amateras.db.visual.model.TableModel" reference="../../.."/>
|
||||
<target class="net.java.amateras.db.visual.model.TableModel" reference="../../../../../../../../../../../.."/>
|
||||
<foreignKeyName>ACTIVITY_FK_2</foreignKeyName>
|
||||
<references>
|
||||
<entry>
|
||||
<net.java.amateras.db.visual.model.ColumnModel reference="../../../../../../../net.java.amateras.db.visual.model.ForeignKeyModel[2]/source/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[3]/references/entry/net.java.amateras.db.visual.model.ColumnModel"/>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>ACTIVITY_USER_NAME</columnName>
|
||||
<logicalName>Activity User Name</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../../../../../../../net.java.amateras.db.visual.model.ForeignKeyModel[2]/source/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/columns/net.java.amateras.db.visual.model.ColumnModel[4]/columnType"/>
|
||||
<size>100</size>
|
||||
<notNull>true</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
<description></description>
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
</entry>
|
||||
</references>
|
||||
</net.java.amateras.db.visual.model.ForeignKeyModel>
|
||||
</sourceConnections>
|
||||
<targetConnections/>
|
||||
<error></error>
|
||||
<linkedPath></linkedPath>
|
||||
<tableName>ACTIVITY</tableName>
|
||||
<logicalName>Activity</logicalName>
|
||||
<description>Since 1.2</description>
|
||||
<columns>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>ACTIVITY_ID</columnName>
|
||||
<logicalName>Activity ID</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType">
|
||||
<name>INT</name>
|
||||
<logicalName>整数</logicalName>
|
||||
<supportSize>false</supportSize>
|
||||
<type>4</type>
|
||||
</columnType>
|
||||
<size>10</size>
|
||||
<notNull>true</notNull>
|
||||
<primaryKey>true</primaryKey>
|
||||
<description></description>
|
||||
<autoIncrement>true</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>USER_NAME</columnName>
|
||||
<logicalName>User Name</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../../../../net.java.amateras.db.visual.model.ForeignKeyModel[2]/source/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/columns/net.java.amateras.db.visual.model.ColumnModel[4]/columnType"/>
|
||||
<size>100</size>
|
||||
<notNull>true</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
<description></description>
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>REPOSITORY_NAME</columnName>
|
||||
<logicalName>Repository Name</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../../../../net.java.amateras.db.visual.model.ForeignKeyModel[2]/source/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/columns/net.java.amateras.db.visual.model.ColumnModel[4]/columnType"/>
|
||||
<size>100</size>
|
||||
<notNull>true</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
<description></description>
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
<net.java.amateras.db.visual.model.ColumnModel reference="../../sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/references/entry/net.java.amateras.db.visual.model.ColumnModel[2]"/>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>ACTIVITY_TYPE</columnName>
|
||||
<logicalName>Activity Type</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../../../../net.java.amateras.db.visual.model.ForeignKeyModel[2]/source/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/columns/net.java.amateras.db.visual.model.ColumnModel[4]/columnType"/>
|
||||
<size>100</size>
|
||||
<notNull>true</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
<description></description>
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>MESSAGE</columnName>
|
||||
<logicalName>Message</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../../../../net.java.amateras.db.visual.model.ForeignKeyModel[2]/source/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/columns/net.java.amateras.db.visual.model.ColumnModel[5]/columnType"/>
|
||||
<size></size>
|
||||
<notNull>true</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
<description></description>
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>ADDITIONAL_INFO</columnName>
|
||||
<logicalName>Additional Information</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../../../../net.java.amateras.db.visual.model.ForeignKeyModel[2]/source/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/columns/net.java.amateras.db.visual.model.ColumnModel[5]/columnType"/>
|
||||
<size></size>
|
||||
<notNull>false</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
<description></description>
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>ACTIVITY_DATE</columnName>
|
||||
<logicalName>Activity Date</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../../../../net.java.amateras.db.visual.model.ForeignKeyModel[2]/source/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/columns/net.java.amateras.db.visual.model.ColumnModel[6]/columnType"/>
|
||||
<size>10</size>
|
||||
<notNull>true</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
<description></description>
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
</columns>
|
||||
<indices/>
|
||||
<backgroundColor>
|
||||
<red>255</red>
|
||||
<green>255</green>
|
||||
<blue>206</blue>
|
||||
</backgroundColor>
|
||||
<sql></sql>
|
||||
</source>
|
||||
<target class="net.java.amateras.db.visual.model.TableModel" reference="../../.."/>
|
||||
<foreignKeyName>ACTIVITY_FK_1</foreignKeyName>
|
||||
<references/>
|
||||
</net.java.amateras.db.visual.model.ForeignKeyModel>
|
||||
<net.java.amateras.db.visual.model.ForeignKeyModel>
|
||||
<listeners serialization="custom">
|
||||
<java.beans.PropertyChangeSupport>
|
||||
<default>
|
||||
<source class="net.java.amateras.db.visual.model.ForeignKeyModel" reference="../../../.."/>
|
||||
<propertyChangeSupportSerializedDataVersion>2</propertyChangeSupportSerializedDataVersion>
|
||||
</default>
|
||||
<null/>
|
||||
</java.beans.PropertyChangeSupport>
|
||||
</listeners>
|
||||
<source class="net.java.amateras.db.visual.model.TableModel">
|
||||
<listeners serialization="custom">
|
||||
<java.beans.PropertyChangeSupport>
|
||||
<default>
|
||||
<source class="net.java.amateras.db.visual.model.TableModel" reference="../../../.."/>
|
||||
<propertyChangeSupportSerializedDataVersion>2</propertyChangeSupportSerializedDataVersion>
|
||||
</default>
|
||||
<null/>
|
||||
</java.beans.PropertyChangeSupport>
|
||||
</listeners>
|
||||
<constraint>
|
||||
<height>-1</height>
|
||||
<width>-1</width>
|
||||
<x>1451</x>
|
||||
<y>577</y>
|
||||
</constraint>
|
||||
<sourceConnections>
|
||||
<net.java.amateras.db.visual.model.ForeignKeyModel reference="../../.."/>
|
||||
</sourceConnections>
|
||||
<targetConnections/>
|
||||
<error></error>
|
||||
<linkedPath></linkedPath>
|
||||
<tableName>COMMIT_LOG</tableName>
|
||||
<logicalName>Commit Log</logicalName>
|
||||
<description>Since 1.2</description>
|
||||
<columns>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>USER_NAME</columnName>
|
||||
<logicalName>User Name</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../../../../net.java.amateras.db.visual.model.ForeignKeyModel[2]/source/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/columns/net.java.amateras.db.visual.model.ColumnModel[4]/columnType"/>
|
||||
<size>100</size>
|
||||
<notNull>true</notNull>
|
||||
<primaryKey>true</primaryKey>
|
||||
<description></description>
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>REPOSITORY_NAME</columnName>
|
||||
<logicalName>Repository Name</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../../../../net.java.amateras.db.visual.model.ForeignKeyModel[2]/source/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/columns/net.java.amateras.db.visual.model.ColumnModel[4]/columnType"/>
|
||||
<size>100</size>
|
||||
<notNull>true</notNull>
|
||||
<primaryKey>true</primaryKey>
|
||||
<description></description>
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>COMMIT_ID</columnName>
|
||||
<logicalName>Commit ID</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../../../../net.java.amateras.db.visual.model.ForeignKeyModel[2]/source/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/columns/net.java.amateras.db.visual.model.ColumnModel[4]/columnType"/>
|
||||
<size>40</size>
|
||||
<notNull>true</notNull>
|
||||
<primaryKey>true</primaryKey>
|
||||
<description></description>
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
</columns>
|
||||
<indices/>
|
||||
<backgroundColor>
|
||||
<red>255</red>
|
||||
<green>255</green>
|
||||
<blue>206</blue>
|
||||
</backgroundColor>
|
||||
<sql></sql>
|
||||
</source>
|
||||
<target class="net.java.amateras.db.visual.model.TableModel" reference="../../.."/>
|
||||
<foreignKeyName>COMMIT_LOG_FK_1</foreignKeyName>
|
||||
<references/>
|
||||
</net.java.amateras.db.visual.model.ForeignKeyModel>
|
||||
</targetConnections>
|
||||
<error></error>
|
||||
<linkedPath></linkedPath>
|
||||
@@ -1062,6 +1401,100 @@
|
||||
</net.java.amateras.db.visual.model.ForeignKeyModel>
|
||||
<net.java.amateras.db.visual.model.ForeignKeyModel reference="../net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel/target/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/source/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[3]"/>
|
||||
<net.java.amateras.db.visual.model.ForeignKeyModel reference="../net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel/target/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/source/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]"/>
|
||||
<net.java.amateras.db.visual.model.ForeignKeyModel reference="../net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel/target/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel[6]/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]"/>
|
||||
<net.java.amateras.db.visual.model.ForeignKeyModel reference="../net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel/target/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/source/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[4]"/>
|
||||
<net.java.amateras.db.visual.model.ForeignKeyModel>
|
||||
<listeners serialization="custom">
|
||||
<java.beans.PropertyChangeSupport>
|
||||
<default>
|
||||
<source class="net.java.amateras.db.visual.model.ForeignKeyModel" reference="../../../.."/>
|
||||
<propertyChangeSupportSerializedDataVersion>2</propertyChangeSupportSerializedDataVersion>
|
||||
</default>
|
||||
<null/>
|
||||
</java.beans.PropertyChangeSupport>
|
||||
</listeners>
|
||||
<source class="net.java.amateras.db.visual.model.TableModel">
|
||||
<listeners serialization="custom">
|
||||
<java.beans.PropertyChangeSupport>
|
||||
<default>
|
||||
<source class="net.java.amateras.db.visual.model.TableModel" reference="../../../.."/>
|
||||
<propertyChangeSupportSerializedDataVersion>2</propertyChangeSupportSerializedDataVersion>
|
||||
</default>
|
||||
<null/>
|
||||
</java.beans.PropertyChangeSupport>
|
||||
</listeners>
|
||||
<constraint>
|
||||
<height>-1</height>
|
||||
<width>-1</width>
|
||||
<x>432</x>
|
||||
<y>240</y>
|
||||
</constraint>
|
||||
<sourceConnections>
|
||||
<net.java.amateras.db.visual.model.ForeignKeyModel reference="../../.."/>
|
||||
<net.java.amateras.db.visual.model.ForeignKeyModel>
|
||||
<listeners serialization="custom">
|
||||
<java.beans.PropertyChangeSupport>
|
||||
<default>
|
||||
<source class="net.java.amateras.db.visual.model.ForeignKeyModel" reference="../../../.."/>
|
||||
<propertyChangeSupportSerializedDataVersion>2</propertyChangeSupportSerializedDataVersion>
|
||||
</default>
|
||||
<null/>
|
||||
</java.beans.PropertyChangeSupport>
|
||||
</listeners>
|
||||
<source class="net.java.amateras.db.visual.model.TableModel" reference="../../.."/>
|
||||
<target class="net.java.amateras.db.visual.model.TableModel" reference="../../../../../.."/>
|
||||
<foreignKeyName>GROUP_MEMBER_FK_2</foreignKeyName>
|
||||
<references/>
|
||||
</net.java.amateras.db.visual.model.ForeignKeyModel>
|
||||
</sourceConnections>
|
||||
<targetConnections/>
|
||||
<error></error>
|
||||
<linkedPath></linkedPath>
|
||||
<tableName>GROUP_MEMBER</tableName>
|
||||
<logicalName>Group Member</logicalName>
|
||||
<description>Since 1.4</description>
|
||||
<columns>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>GROUP_NAME</columnName>
|
||||
<logicalName>Group Name</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../../../../net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel/target/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/source/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/columns/net.java.amateras.db.visual.model.ColumnModel[4]/columnType"/>
|
||||
<size>100</size>
|
||||
<notNull>true</notNull>
|
||||
<primaryKey>true</primaryKey>
|
||||
<description></description>
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>USER_NAME</columnName>
|
||||
<logicalName>User Name</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../../../../net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel/target/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/source/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/columns/net.java.amateras.db.visual.model.ColumnModel[4]/columnType"/>
|
||||
<size>100</size>
|
||||
<notNull>true</notNull>
|
||||
<primaryKey>true</primaryKey>
|
||||
<description></description>
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
</columns>
|
||||
<indices/>
|
||||
<backgroundColor>
|
||||
<red>255</red>
|
||||
<green>255</green>
|
||||
<blue>206</blue>
|
||||
</backgroundColor>
|
||||
<sql></sql>
|
||||
</source>
|
||||
<target class="net.java.amateras.db.visual.model.TableModel" reference="../../.."/>
|
||||
<foreignKeyName>GROUP_MEMBER_FK_1</foreignKeyName>
|
||||
<references>
|
||||
<entry>
|
||||
<net.java.amateras.db.visual.model.ColumnModel reference="../../../../net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel/target/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/source/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[3]/references/entry/net.java.amateras.db.visual.model.ColumnModel"/>
|
||||
<net.java.amateras.db.visual.model.ColumnModel reference="../../../source/columns/net.java.amateras.db.visual.model.ColumnModel"/>
|
||||
</entry>
|
||||
</references>
|
||||
</net.java.amateras.db.visual.model.ForeignKeyModel>
|
||||
<net.java.amateras.db.visual.model.ForeignKeyModel reference="../net.java.amateras.db.visual.model.ForeignKeyModel[6]/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]"/>
|
||||
</targetConnections>
|
||||
<error></error>
|
||||
<linkedPath></linkedPath>
|
||||
@@ -1089,8 +1522,8 @@
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>PASSWORD</columnName>
|
||||
<logicalName>Password</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../net.java.amateras.db.visual.model.ColumnModel[2]/columnType"/>
|
||||
<size>20</size>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../../targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel/target/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/source/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/columns/net.java.amateras.db.visual.model.ColumnModel[4]/columnType"/>
|
||||
<size>40</size>
|
||||
<notNull>true</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
<description></description>
|
||||
@@ -1098,18 +1531,18 @@
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>USER_TYPE</columnName>
|
||||
<logicalName>User Type</logicalName>
|
||||
<columnName>ADMINISTRATOR</columnName>
|
||||
<logicalName>Administrator</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType">
|
||||
<name>INT</name>
|
||||
<logicalName>整数</logicalName>
|
||||
<name>BOOLEAN</name>
|
||||
<logicalName>真偽値</logicalName>
|
||||
<supportSize>false</supportSize>
|
||||
<type>4</type>
|
||||
<type>16</type>
|
||||
</columnType>
|
||||
<size>10</size>
|
||||
<notNull>true</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
<description>0:Normal 1:Administrator</description>
|
||||
<description></description>
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue>0</defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
@@ -1157,6 +1590,33 @@
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>IMAGE</columnName>
|
||||
<logicalName>Image</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../../targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel/target/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/source/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/columns/net.java.amateras.db.visual.model.ColumnModel[5]/columnType"/>
|
||||
<size>100</size>
|
||||
<notNull>false</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
<description>Since 1.3</description>
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>GROUP_ACCOUNT</columnName>
|
||||
<logicalName>Group Account</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType">
|
||||
<name>BOOLEAN</name>
|
||||
<logicalName>真偽値</logicalName>
|
||||
<supportSize>false</supportSize>
|
||||
<type>16</type>
|
||||
</columnType>
|
||||
<size>10</size>
|
||||
<notNull>true</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
<description>Since 1.4</description>
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue>FALSE</defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
</columns>
|
||||
<indices>
|
||||
<net.java.amateras.db.visual.model.IndexModel>
|
||||
@@ -1184,6 +1644,91 @@
|
||||
<net.java.amateras.db.visual.model.TableModel reference="../net.java.amateras.db.visual.model.TableModel/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel/target/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/source/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target"/>
|
||||
<net.java.amateras.db.visual.model.TableModel reference="../net.java.amateras.db.visual.model.TableModel/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel/target/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/source/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source"/>
|
||||
<net.java.amateras.db.visual.model.TableModel reference="../net.java.amateras.db.visual.model.TableModel/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel/target/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel[3]/source"/>
|
||||
<net.java.amateras.db.visual.model.TableModel reference="../net.java.amateras.db.visual.model.TableModel/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel/target/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel[6]/source"/>
|
||||
<net.java.amateras.db.visual.model.TableModel reference="../net.java.amateras.db.visual.model.TableModel/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel/target/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel[7]/source"/>
|
||||
<net.java.amateras.db.visual.model.TableModel reference="../net.java.amateras.db.visual.model.TableModel/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel[6]/source"/>
|
||||
<net.java.amateras.db.visual.model.TableModel>
|
||||
<listeners serialization="custom">
|
||||
<java.beans.PropertyChangeSupport>
|
||||
<default>
|
||||
<source class="net.java.amateras.db.visual.model.TableModel" reference="../../../.."/>
|
||||
<propertyChangeSupportSerializedDataVersion>2</propertyChangeSupportSerializedDataVersion>
|
||||
</default>
|
||||
<null/>
|
||||
</java.beans.PropertyChangeSupport>
|
||||
</listeners>
|
||||
<constraint>
|
||||
<height>-1</height>
|
||||
<width>-1</width>
|
||||
<x>410</x>
|
||||
<y>860</y>
|
||||
</constraint>
|
||||
<sourceConnections/>
|
||||
<targetConnections/>
|
||||
<error></error>
|
||||
<linkedPath></linkedPath>
|
||||
<tableName>ISSUE_OUTLINE_VIEW</tableName>
|
||||
<logicalName>Issue Outline View</logicalName>
|
||||
<description>Since 1.4</description>
|
||||
<columns>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>USER_NAME</columnName>
|
||||
<logicalName>User Name</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../../../net.java.amateras.db.visual.model.TableModel/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel/target/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/source/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/columns/net.java.amateras.db.visual.model.ColumnModel[5]/columnType"/>
|
||||
<size>100</size>
|
||||
<notNull>false</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
<description></description>
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>REPOSITORY_NAME</columnName>
|
||||
<logicalName>Repository Name</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../../../net.java.amateras.db.visual.model.TableModel/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel/target/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/source/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/sourceConnections/net.java.amateras.db.visual.model.ForeignKeyModel[2]/target/targetConnections/net.java.amateras.db.visual.model.ForeignKeyModel/source/columns/net.java.amateras.db.visual.model.ColumnModel[5]/columnType"/>
|
||||
<size>100</size>
|
||||
<notNull>false</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
<description></description>
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>ISSUE_ID</columnName>
|
||||
<logicalName>Issue ID</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType">
|
||||
<name>INT</name>
|
||||
<logicalName>整数</logicalName>
|
||||
<supportSize>false</supportSize>
|
||||
<type>4</type>
|
||||
</columnType>
|
||||
<size>10</size>
|
||||
<notNull>false</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
<description></description>
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
<net.java.amateras.db.visual.model.ColumnModel>
|
||||
<columnName>COMMENT_COUNT</columnName>
|
||||
<logicalName>Comment Count</logicalName>
|
||||
<columnType class="net.java.amateras.db.dialect.ColumnType" reference="../../net.java.amateras.db.visual.model.ColumnModel[3]/columnType"/>
|
||||
<size>10</size>
|
||||
<notNull>false</notNull>
|
||||
<primaryKey>false</primaryKey>
|
||||
<description></description>
|
||||
<autoIncrement>false</autoIncrement>
|
||||
<defaultValue></defaultValue>
|
||||
</net.java.amateras.db.visual.model.ColumnModel>
|
||||
</columns>
|
||||
<indices/>
|
||||
<backgroundColor>
|
||||
<red>210</red>
|
||||
<green>232</green>
|
||||
<blue>249</blue>
|
||||
</backgroundColor>
|
||||
<sql></sql>
|
||||
</net.java.amateras.db.visual.model.TableModel>
|
||||
</children>
|
||||
<dommains/>
|
||||
<dialectName>H2</dialectName>
|
||||
754
etc/icons.svg
Normal file
754
etc/icons.svg
Normal file
@@ -0,0 +1,754 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0" width="2000" height="2000" viewBox="0, 0, 2000, 2000">
|
||||
<g id="Layer_1">
|
||||
<g id="path4000">
|
||||
<path d="M583.868,482.54 C583.868,482.54 596.594,491.899 620.234,491.631 C643.873,491.363 650.538,482.54 650.538,482.54 L671.558,404.092 L563.665,404.092 z" fill="#B3B3B3"/>
|
||||
<path d="M583.868,482.54 C583.868,482.54 596.594,491.899 620.234,491.631 C643.873,491.363 650.538,482.54 650.538,482.54 L671.558,404.092 L563.665,404.092 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="1"/>
|
||||
</g>
|
||||
<path d="M215.018,822.461 C215.018,822.461 217.291,803.608 246.459,799.039 C256.428,797.478 278.667,793.574 278.667,770.933" fill-opacity="0" stroke="#B3B3B3" stroke-width="17.059" id="path3207"/>
|
||||
<path d="M62.863,746.321 C62.863,801.014 132.287,797.758 132.287,797.758" fill-opacity="0" stroke="#B3B3B3" stroke-width="17.56" id="path4318"/>
|
||||
<g id="rect3935">
|
||||
<path d="M9.359,600.772 L185.078,600.772 L185.078,623.958 L9.359,623.958 z" fill="#B3B3B3"/>
|
||||
<path d="M9.359,600.772 L185.078,600.772 L185.078,623.958 L9.359,623.958 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.781" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g id="path3894-1">
|
||||
<path d="M450.793,475.007 L401.329,532.789 L414.646,471.965 z" fill="#B3B3B3"/>
|
||||
<path d="M450.793,475.007 L401.329,532.789 L414.646,471.965 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="7.485"/>
|
||||
</g>
|
||||
<g id="rect3088-5-5">
|
||||
<path d="M373.916,407.856 L485.485,407.856 L485.485,477.7 L373.916,477.7 z" fill="#B3B3B3"/>
|
||||
<path d="M373.916,407.856 L485.485,407.856 L485.485,477.7 L373.916,477.7 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="19.036" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<path d="M306.191,155.761 L306.191,70.894 C306.191,70.894 307.419,60.791 295.143,60.791 C282.868,60.791 261.385,60.791 261.385,60.791" fill-opacity="0" stroke="#008000" stroke-width="15" id="path3850"/>
|
||||
<g id="path2991">
|
||||
<path d="M159.256,113.426 C159.256,151.442 128.438,182.26 90.422,182.26 C52.407,182.26 21.589,151.442 21.589,113.426 C21.589,75.411 52.407,44.593 90.422,44.593 C128.438,44.593 159.256,75.411 159.256,113.426 z" fill="#008000"/>
|
||||
<path d="M159.256,113.426 C159.256,151.442 128.438,182.26 90.422,182.26 C52.407,182.26 21.589,151.442 21.589,113.426 C21.589,75.411 52.407,44.593 90.422,44.593 C128.438,44.593 159.256,75.411 159.256,113.426 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.66"/>
|
||||
</g>
|
||||
<g id="path2993">
|
||||
<path d="M148.532,113.917 C148.532,145.702 122.765,171.469 90.98,171.469 C59.194,171.469 33.427,145.702 33.427,113.917 C33.427,82.131 59.194,56.364 90.98,56.364 C122.765,56.364 148.532,82.131 148.532,113.917 z" fill="#FFFFFF"/>
|
||||
<path d="M148.532,113.917 C148.532,145.702 122.765,171.469 90.98,171.469 C59.194,171.469 33.427,145.702 33.427,113.917 C33.427,82.131 59.194,56.364 90.98,56.364 C122.765,56.364 148.532,82.131 148.532,113.917 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.552"/>
|
||||
</g>
|
||||
<g id="rect2995">
|
||||
<path d="M81.339,65.348 L100.605,65.348 L100.605,130.839 L81.339,130.839 z" fill="#008000"/>
|
||||
<path d="M81.339,65.348 L100.605,65.348 L100.605,130.839 L81.339,130.839 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.733"/>
|
||||
</g>
|
||||
<g id="rect2997">
|
||||
<path d="M81.509,143.757 L101.128,143.757 L101.128,161.089 L81.509,161.089 z" fill="#008000"/>
|
||||
<path d="M81.509,143.757 L101.128,143.757 L101.128,161.089 L81.509,161.089 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.381"/>
|
||||
</g>
|
||||
<g id="rect3818">
|
||||
<path d="M230.385,74.531 L232.534,74.531 L232.534,143.355 L230.385,143.355 z" fill="#FFFFFF"/>
|
||||
<path d="M230.385,74.531 L232.534,74.531 L232.534,143.355 L230.385,143.355 z" fill-opacity="0" stroke="#008000" stroke-width="15"/>
|
||||
</g>
|
||||
<g id="path3795-4">
|
||||
<path d="M246.225,160.683 C246.225,168.561 239.776,174.947 231.82,174.947 C223.864,174.947 217.414,168.561 217.414,160.683 C217.414,152.805 223.864,146.419 231.82,146.419 C239.776,146.419 246.225,152.805 246.225,160.683 z" fill="#FFFFFF"/>
|
||||
<path d="M246.225,160.683 C246.225,168.561 239.776,174.947 231.82,174.947 C223.864,174.947 217.414,168.561 217.414,160.683 C217.414,152.805 223.864,146.419 231.82,146.419 C239.776,146.419 246.225,152.805 246.225,160.683 z" fill-opacity="0" stroke="#008000" stroke-width="7.989"/>
|
||||
</g>
|
||||
<g id="path3795">
|
||||
<path d="M245.212,61.462 C245.212,69.34 238.762,75.726 230.806,75.726 C222.85,75.726 216.4,69.34 216.4,61.462 C216.4,53.585 222.85,47.198 230.806,47.198 C238.762,47.198 245.212,53.585 245.212,61.462 z" fill="#FFFFFF"/>
|
||||
<path d="M245.212,61.462 C245.212,69.34 238.762,75.726 230.806,75.726 C222.85,75.726 216.4,69.34 216.4,61.462 C216.4,53.585 222.85,47.198 230.806,47.198 C238.762,47.198 245.212,53.585 245.212,61.462 z" fill-opacity="0" stroke="#008000" stroke-width="7.989"/>
|
||||
</g>
|
||||
<g id="path3795-4-0">
|
||||
<path d="M320.671,160.75 C320.671,168.628 314.221,175.014 306.265,175.014 C298.309,175.014 291.86,168.628 291.86,160.75 C291.86,152.872 298.309,146.486 306.265,146.486 C314.221,146.486 320.671,152.872 320.671,160.75 z" fill="#FFFFFF"/>
|
||||
<path d="M320.671,160.75 C320.671,168.628 314.221,175.014 306.265,175.014 C298.309,175.014 291.86,168.628 291.86,160.75 C291.86,152.872 298.309,146.486 306.265,146.486 C314.221,146.486 320.671,152.872 320.671,160.75 z" fill-opacity="0" stroke="#008000" stroke-width="7.989"/>
|
||||
</g>
|
||||
<g id="path3852">
|
||||
<path d="M279.602,36.54 L279.602,83.2 L249.511,61.99 z" fill="#008000"/>
|
||||
<path d="M279.602,36.54 L279.602,83.2 L249.511,61.99 z" fill-opacity="0" stroke="#008000" stroke-width="0.55"/>
|
||||
</g>
|
||||
<path d="M308.603,323.909 L308.603,239.042 C308.603,239.042 309.831,228.939 297.555,228.939 C285.279,228.939 263.797,228.939 263.797,228.939" fill-opacity="0" stroke="#800000" stroke-width="15" id="path3850-4"/>
|
||||
<g id="path2991-8">
|
||||
<path d="M161.667,281.574 C161.667,319.59 130.85,350.407 92.834,350.407 C54.819,350.407 24.001,319.59 24.001,281.574 C24.001,243.558 54.819,212.741 92.834,212.741 C130.85,212.741 161.667,243.558 161.667,281.574 z" fill="#800000"/>
|
||||
<path d="M161.667,281.574 C161.667,319.59 130.85,350.407 92.834,350.407 C54.819,350.407 24.001,319.59 24.001,281.574 C24.001,243.558 54.819,212.741 92.834,212.741 C130.85,212.741 161.667,243.558 161.667,281.574 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.66"/>
|
||||
</g>
|
||||
<g id="path2993-8">
|
||||
<path d="M150.944,282.064 C150.944,313.85 125.177,339.617 93.391,339.617 C61.606,339.617 35.839,313.85 35.839,282.064 C35.839,250.279 61.606,224.512 93.391,224.512 C125.177,224.512 150.944,250.279 150.944,282.064 z" fill="#FFFFFF"/>
|
||||
<path d="M150.944,282.064 C150.944,313.85 125.177,339.617 93.391,339.617 C61.606,339.617 35.839,313.85 35.839,282.064 C35.839,250.279 61.606,224.512 93.391,224.512 C125.177,224.512 150.944,250.279 150.944,282.064 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.552"/>
|
||||
</g>
|
||||
<g id="rect2995-2">
|
||||
<path d="M83.75,233.496 L103.017,233.496 L103.017,298.986 L83.75,298.986 z" fill="#800000"/>
|
||||
<path d="M83.75,233.496 L103.017,233.496 L103.017,298.986 L83.75,298.986 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.733"/>
|
||||
</g>
|
||||
<g id="rect2997-4">
|
||||
<path d="M83.921,311.905 L103.54,311.905 L103.54,329.237 L83.921,329.237 z" fill="#800000"/>
|
||||
<path d="M83.921,311.905 L103.54,311.905 L103.54,329.237 L83.921,329.237 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.381"/>
|
||||
</g>
|
||||
<g id="rect3818-5">
|
||||
<path d="M232.797,242.679 L234.946,242.679 L234.946,311.502 L232.797,311.502 z" fill="#FFFFFF"/>
|
||||
<path d="M232.797,242.679 L234.946,242.679 L234.946,311.502 L232.797,311.502 z" fill-opacity="0" stroke="#800000" stroke-width="15"/>
|
||||
</g>
|
||||
<g id="path3795-4-5">
|
||||
<path d="M248.637,328.831 C248.637,336.708 242.187,343.095 234.231,343.095 C226.275,343.095 219.826,336.708 219.826,328.831 C219.826,320.953 226.275,314.566 234.231,314.566 C242.187,314.566 248.637,320.953 248.637,328.831 z" fill="#FFFFFF"/>
|
||||
<path d="M248.637,328.831 C248.637,336.708 242.187,343.095 234.231,343.095 C226.275,343.095 219.826,336.708 219.826,328.831 C219.826,320.953 226.275,314.566 234.231,314.566 C242.187,314.566 248.637,320.953 248.637,328.831 z" fill-opacity="0" stroke="#800000" stroke-width="7.989"/>
|
||||
</g>
|
||||
<g id="path3795-1">
|
||||
<path d="M247.623,229.61 C247.623,237.488 241.174,243.874 233.218,243.874 C225.262,243.874 218.812,237.488 218.812,229.61 C218.812,221.732 225.262,215.346 233.218,215.346 C241.174,215.346 247.623,221.732 247.623,229.61 z" fill="#FFFFFF"/>
|
||||
<path d="M247.623,229.61 C247.623,237.488 241.174,243.874 233.218,243.874 C225.262,243.874 218.812,237.488 218.812,229.61 C218.812,221.732 225.262,215.346 233.218,215.346 C241.174,215.346 247.623,221.732 247.623,229.61 z" fill-opacity="0" stroke="#800000" stroke-width="7.989"/>
|
||||
</g>
|
||||
<g id="path3795-4-0-7">
|
||||
<path d="M323.083,328.898 C323.083,336.775 316.633,343.162 308.677,343.162 C300.721,343.162 294.271,336.775 294.271,328.898 C294.271,321.02 300.721,314.633 308.677,314.633 C316.633,314.633 323.083,321.02 323.083,328.898 z" fill="#FFFFFF"/>
|
||||
<path d="M323.083,328.898 C323.083,336.775 316.633,343.162 308.677,343.162 C300.721,343.162 294.271,336.775 294.271,328.898 C294.271,321.02 300.721,314.633 308.677,314.633 C316.633,314.633 323.083,321.02 323.083,328.898 z" fill-opacity="0" stroke="#800000" stroke-width="7.989"/>
|
||||
</g>
|
||||
<g id="path3852-1">
|
||||
<path d="M282.013,204.688 L282.013,251.348 L251.923,230.137 z" fill="#800000"/>
|
||||
<path d="M282.013,204.688 L282.013,251.348 L251.923,230.137 z" fill-opacity="0" stroke="#800000" stroke-width="0.55"/>
|
||||
</g>
|
||||
<path d="M439.891,42.342 L643.358,42.342 L643.358,245.81 L439.891,245.81 z" fill="#CCCCCC" id="rect2985"/>
|
||||
<path d="M610.846,124.458 C610.846,161.784 580.587,192.042 543.262,192.042 C505.936,192.042 475.678,161.784 475.678,124.458 C475.678,87.132 505.936,56.874 543.262,56.874 C580.587,56.874 610.846,87.132 610.846,124.458 z" fill="#FFFFFF" id="path2989"/>
|
||||
<g id="path2993-2">
|
||||
<path d="M484.685,245.601 L603.47,245.601 L544.162,112.49 z" fill="#FFFFFF"/>
|
||||
<path d="M484.685,245.601 L603.47,245.601 L544.162,112.49 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="1.054"/>
|
||||
</g>
|
||||
<path d="M307.593,489.153 L307.593,404.286 C307.593,404.286 308.82,394.183 296.545,394.183 C284.269,394.183 262.786,394.183 262.786,394.183" fill-opacity="0" stroke="#B3B3B3" stroke-width="15" id="path3850-1"/>
|
||||
<g id="path2991-7">
|
||||
<path d="M160.657,446.818 C160.657,484.833 129.84,515.651 91.824,515.651 C53.808,515.651 22.991,484.833 22.991,446.818 C22.991,408.802 53.808,377.984 91.824,377.984 C129.84,377.984 160.657,408.802 160.657,446.818 z" fill="#B3B3B3"/>
|
||||
<path d="M160.657,446.818 C160.657,484.833 129.84,515.651 91.824,515.651 C53.808,515.651 22.991,484.833 22.991,446.818 C22.991,408.802 53.808,377.984 91.824,377.984 C129.84,377.984 160.657,408.802 160.657,446.818 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.66"/>
|
||||
</g>
|
||||
<g id="path2993-4">
|
||||
<path d="M149.934,447.308 C149.934,479.094 124.167,504.861 92.381,504.861 C60.596,504.861 34.828,479.094 34.828,447.308 C34.828,415.523 60.596,389.756 92.381,389.756 C124.167,389.756 149.934,415.523 149.934,447.308 z" fill="#FFFFFF"/>
|
||||
<path d="M149.934,447.308 C149.934,479.094 124.167,504.861 92.381,504.861 C60.596,504.861 34.828,479.094 34.828,447.308 C34.828,415.523 60.596,389.756 92.381,389.756 C124.167,389.756 149.934,415.523 149.934,447.308 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.552"/>
|
||||
</g>
|
||||
<g id="rect2995-0">
|
||||
<path d="M82.74,398.739 L102.007,398.739 L102.007,464.23 L82.74,464.23 z" fill="#B3B3B3"/>
|
||||
<path d="M82.74,398.739 L102.007,398.739 L102.007,464.23 L82.74,464.23 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.733"/>
|
||||
</g>
|
||||
<g id="rect2997-9">
|
||||
<path d="M82.911,477.149 L102.53,477.149 L102.53,494.48 L82.911,494.48 z" fill="#B3B3B3"/>
|
||||
<path d="M82.911,477.149 L102.53,477.149 L102.53,494.48 L82.911,494.48 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.381"/>
|
||||
</g>
|
||||
<g id="rect3818-4">
|
||||
<path d="M231.787,407.923 L233.935,407.923 L233.935,476.746 L231.787,476.746 z" fill="#FFFFFF"/>
|
||||
<path d="M231.787,407.923 L233.935,407.923 L233.935,476.746 L231.787,476.746 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="15"/>
|
||||
</g>
|
||||
<g id="path3795-4-8">
|
||||
<path d="M247.627,494.074 C247.627,501.952 241.177,508.338 233.221,508.338 C225.265,508.338 218.816,501.952 218.816,494.074 C218.816,486.197 225.265,479.81 233.221,479.81 C241.177,479.81 247.627,486.197 247.627,494.074 z" fill="#FFFFFF"/>
|
||||
<path d="M247.627,494.074 C247.627,501.952 241.177,508.338 233.221,508.338 C225.265,508.338 218.816,501.952 218.816,494.074 C218.816,486.197 225.265,479.81 233.221,479.81 C241.177,479.81 247.627,486.197 247.627,494.074 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.989"/>
|
||||
</g>
|
||||
<g id="path3795-8">
|
||||
<path d="M246.613,394.854 C246.613,402.732 240.164,409.118 232.208,409.118 C224.252,409.118 217.802,402.732 217.802,394.854 C217.802,386.976 224.252,380.59 232.208,380.59 C240.164,380.59 246.613,386.976 246.613,394.854 z" fill="#FFFFFF"/>
|
||||
<path d="M246.613,394.854 C246.613,402.732 240.164,409.118 232.208,409.118 C224.252,409.118 217.802,402.732 217.802,394.854 C217.802,386.976 224.252,380.59 232.208,380.59 C240.164,380.59 246.613,386.976 246.613,394.854 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.989"/>
|
||||
</g>
|
||||
<g id="path3795-4-0-2">
|
||||
<path d="M322.072,494.141 C322.072,502.019 315.623,508.405 307.667,508.405 C299.711,508.405 293.261,502.019 293.261,494.141 C293.261,486.264 299.711,479.877 307.667,479.877 C315.623,479.877 322.072,486.264 322.072,494.141 z" fill="#FFFFFF"/>
|
||||
<path d="M322.072,494.141 C322.072,502.019 315.623,508.405 307.667,508.405 C299.711,508.405 293.261,502.019 293.261,494.141 C293.261,486.264 299.711,479.877 307.667,479.877 C315.623,479.877 322.072,486.264 322.072,494.141 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.989"/>
|
||||
</g>
|
||||
<g id="path3852-4">
|
||||
<path d="M281.003,369.932 L281.003,416.591 L250.912,395.381 z" fill="#B3B3B3"/>
|
||||
<path d="M281.003,369.932 L281.003,416.591 L250.912,395.381 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="0.55"/>
|
||||
</g>
|
||||
<g id="rect3088">
|
||||
<path d="M394.325,382.21 L518.574,382.21 L518.574,459.992 L394.325,459.992 z" fill="#FFFFFF"/>
|
||||
<path d="M394.325,382.21 L518.574,382.21 L518.574,459.992 L394.325,459.992 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="21.2" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g id="path3894">
|
||||
<path d="M452.812,456.961 L505.34,514.54 L491.198,453.931 z" fill="#B3B3B3"/>
|
||||
<path d="M452.812,456.961 L505.34,514.54 L491.198,453.931 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="7.7"/>
|
||||
</g>
|
||||
<g id="rect3088-5">
|
||||
<path d="M405.586,381.471 L517.155,381.471 L517.155,451.314 L405.586,451.314 z" fill="#B3B3B3"/>
|
||||
<path d="M405.586,381.471 L517.155,381.471 L517.155,451.314 L405.586,451.314 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="19.036" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g id="path2991-7-7">
|
||||
<path d="M152.8,612.829 C152.8,643.627 127.833,668.593 97.036,668.593 C66.238,668.593 41.272,643.627 41.272,612.829 C41.272,582.032 66.238,557.065 97.036,557.065 C127.833,557.065 152.8,582.032 152.8,612.829 z" fill="#B3B3B3"/>
|
||||
<path d="M152.8,612.829 C152.8,643.627 127.833,668.593 97.036,668.593 C66.238,668.593 41.272,643.627 41.272,612.829 C41.272,582.032 66.238,557.065 97.036,557.065 C127.833,557.065 152.8,582.032 152.8,612.829 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="0.535"/>
|
||||
</g>
|
||||
<g id="path2993-4-1">
|
||||
<path d="M135.769,612.856 C135.769,634.203 118.464,651.509 97.117,651.509 C75.769,651.509 58.464,634.203 58.464,612.856 C58.464,591.509 75.769,574.204 97.117,574.204 C118.464,574.204 135.769,591.509 135.769,612.856 z" fill="#FFFFFF"/>
|
||||
<path d="M135.769,612.856 C135.769,634.203 118.464,651.509 97.117,651.509 C75.769,651.509 58.464,634.203 58.464,612.856 C58.464,591.509 75.769,574.204 97.117,574.204 C118.464,574.204 135.769,591.509 135.769,612.856 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.371"/>
|
||||
</g>
|
||||
<g id="path2991-7-1">
|
||||
<path d="M360.813,616.87 C360.813,654.885 329.996,685.703 291.98,685.703 C253.964,685.703 223.147,654.885 223.147,616.87 C223.147,578.854 253.964,548.036 291.98,548.036 C329.996,548.036 360.813,578.854 360.813,616.87 z" fill="#B3B3B3"/>
|
||||
<path d="M360.813,616.87 C360.813,654.885 329.996,685.703 291.98,685.703 C253.964,685.703 223.147,654.885 223.147,616.87 C223.147,578.854 253.964,548.036 291.98,548.036 C329.996,548.036 360.813,578.854 360.813,616.87 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.66"/>
|
||||
</g>
|
||||
<g id="path2993-4-5">
|
||||
<path d="M350.09,617.36 C350.09,649.146 324.323,674.913 292.537,674.913 C260.752,674.913 234.984,649.146 234.984,617.36 C234.984,585.575 260.752,559.808 292.537,559.808 C324.323,559.808 350.09,585.575 350.09,617.36 z" fill="#FFFFFF"/>
|
||||
<path d="M350.09,617.36 C350.09,649.146 324.323,674.913 292.537,674.913 C260.752,674.913 234.984,649.146 234.984,617.36 C234.984,585.575 260.752,559.808 292.537,559.808 C324.323,559.808 350.09,585.575 350.09,617.36 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.552"/>
|
||||
</g>
|
||||
<g id="rect2995-0-2">
|
||||
<path d="M282.896,568.792 L302.163,568.792 L302.163,634.282 L282.896,634.282 z" fill="#B3B3B3"/>
|
||||
<path d="M282.896,568.792 L302.163,568.792 L302.163,634.282 L282.896,634.282 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.733"/>
|
||||
</g>
|
||||
<g id="rect2997-9-7">
|
||||
<path d="M283.067,647.2 L302.686,647.2 L302.686,664.532 L283.067,664.532 z" fill="#B3B3B3"/>
|
||||
<path d="M283.067,647.2 L302.686,647.2 L302.686,664.532 L283.067,664.532 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.381"/>
|
||||
</g>
|
||||
<g id="rect4046-3">
|
||||
<path d="M265.347,590.671 L222.652,566.769 L229.715,603.704 L265.347,590.671 z" fill="#FFFFFF"/>
|
||||
<path d="M265.347,590.671 L222.652,566.769 L229.715,603.704 L265.347,590.671 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="1.93" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect4046">
|
||||
<path d="M258.314,586.135 L229.499,570.007 L234.118,595.192 L258.314,586.135 z" fill="#B3B3B3"/>
|
||||
<path d="M258.314,586.135 L229.499,570.007 L234.118,595.192 L258.314,586.135 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="1.313" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect4046-3-2">
|
||||
<path d="M317.971,646.518 L360.666,670.419 L353.603,633.485 L317.971,646.518 z" fill="#FFFFFF"/>
|
||||
<path d="M317.971,646.518 L360.666,670.419 L353.603,633.485 L317.971,646.518 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="1.93" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect4046-1">
|
||||
<path d="M323.933,652.482 L352.748,668.61 L348.129,643.425 L323.933,652.482 z" fill="#B3B3B3"/>
|
||||
<path d="M323.933,652.482 L352.748,668.61 L348.129,643.425 L323.933,652.482 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="1.313" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="path2991-7-79">
|
||||
<path d="M540.417,614.85 C540.417,652.865 509.6,683.683 471.584,683.683 C433.568,683.683 402.751,652.865 402.751,614.85 C402.751,576.834 433.568,546.016 471.584,546.016 C509.6,546.016 540.417,576.834 540.417,614.85 z" fill="#B3B3B3"/>
|
||||
<path d="M540.417,614.85 C540.417,652.865 509.6,683.683 471.584,683.683 C433.568,683.683 402.751,652.865 402.751,614.85 C402.751,576.834 433.568,546.016 471.584,546.016 C509.6,546.016 540.417,576.834 540.417,614.85 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.66"/>
|
||||
</g>
|
||||
<g id="path2993-4-54">
|
||||
<path d="M529.694,615.34 C529.694,647.125 503.927,672.893 472.141,672.893 C440.356,672.893 414.589,647.125 414.589,615.34 C414.589,583.555 440.356,557.787 472.141,557.787 C503.927,557.787 529.694,583.555 529.694,615.34 z" fill="#FFFFFF"/>
|
||||
<path d="M529.694,615.34 C529.694,647.125 503.927,672.893 472.141,672.893 C440.356,672.893 414.589,647.125 414.589,615.34 C414.589,583.555 440.356,557.787 472.141,557.787 C503.927,557.787 529.694,583.555 529.694,615.34 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.552"/>
|
||||
</g>
|
||||
<g id="rect4271">
|
||||
<path d="M521.629,568.481 L546.81,594.751 L505.942,637.386 L480.762,611.116 z" fill="#FFFFFF"/>
|
||||
<path d="M521.629,568.481 L546.81,594.751 L505.942,637.386 L480.762,611.116 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="4.802" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect2995-0-3-3">
|
||||
<path d="M486.899,587.752 L498.348,576.303 L525.283,603.238 L513.834,614.687 z" fill="#B3B3B3"/>
|
||||
<path d="M486.899,587.752 L498.348,576.303 L525.283,603.238 L513.834,614.687 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="0.513"/>
|
||||
</g>
|
||||
<g id="rect2995-0-3-2">
|
||||
<path d="M540.917,564.622 L552.295,576 L513.62,614.676 L502.242,603.298 z" fill="#B3B3B3"/>
|
||||
<path d="M540.917,564.622 L552.295,576 L513.62,614.676 L502.242,603.298 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="0.613"/>
|
||||
</g>
|
||||
<g id="rect2995-0-3">
|
||||
<path d="M462.5,566.771 L481.767,566.771 L481.767,632.262 L462.5,632.262 z" fill="#B3B3B3"/>
|
||||
<path d="M462.5,566.771 L481.767,566.771 L481.767,632.262 L462.5,632.262 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.733"/>
|
||||
</g>
|
||||
<g id="rect2997-9-1">
|
||||
<path d="M462.955,644.956 L482.574,644.956 L482.574,662.287 L462.955,662.287 z" fill="#B3B3B3"/>
|
||||
<path d="M462.955,644.956 L482.574,644.956 L482.574,662.287 L462.955,662.287 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.381"/>
|
||||
</g>
|
||||
<g id="rect3818-4-8">
|
||||
<path d="M62.784,747.977 L64.932,747.977 L64.932,816.8 L62.784,816.8 z" fill="#FFFFFF"/>
|
||||
<path d="M62.784,747.977 L64.932,747.977 L64.932,816.8 L62.784,816.8 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="15"/>
|
||||
</g>
|
||||
<g id="path3795-4-8-7">
|
||||
<path d="M78.624,834.13 C78.624,842.008 72.174,848.394 64.218,848.394 C56.262,848.394 49.813,842.008 49.813,834.13 C49.813,826.252 56.262,819.866 64.218,819.866 C72.174,819.866 78.624,826.252 78.624,834.13 z" fill="#FFFFFF"/>
|
||||
<path d="M78.624,834.13 C78.624,842.008 72.174,848.394 64.218,848.394 C56.262,848.394 49.813,842.008 49.813,834.13 C49.813,826.252 56.262,819.866 64.218,819.866 C72.174,819.866 78.624,826.252 78.624,834.13 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.989"/>
|
||||
</g>
|
||||
<g id="path3795-8-4">
|
||||
<path d="M77.61,734.91 C77.61,742.788 71.161,749.174 63.205,749.174 C55.249,749.174 48.799,742.788 48.799,734.91 C48.799,727.032 55.249,720.646 63.205,720.646 C71.161,720.646 77.61,727.032 77.61,734.91 z" fill="#FFFFFF"/>
|
||||
<path d="M77.61,734.91 C77.61,742.788 71.161,749.174 63.205,749.174 C55.249,749.174 48.799,742.788 48.799,734.91 C48.799,727.032 55.249,720.646 63.205,720.646 C71.161,720.646 77.61,727.032 77.61,734.91 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.989"/>
|
||||
</g>
|
||||
<g id="path3795-4-8-7-7">
|
||||
<path d="M152.898,797.667 C152.898,805.544 146.448,811.931 138.492,811.931 C130.536,811.931 124.087,805.544 124.087,797.667 C124.087,789.789 130.536,783.402 138.492,783.402 C146.448,783.402 152.898,789.789 152.898,797.667 z" fill="#FFFFFF"/>
|
||||
<path d="M152.898,797.667 C152.898,805.544 146.448,811.931 138.492,811.931 C130.536,811.931 124.087,805.544 124.087,797.667 C124.087,789.789 130.536,783.402 138.492,783.402 C146.448,783.402 152.898,789.789 152.898,797.667 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.989"/>
|
||||
</g>
|
||||
<g id="rect3818-4-8-4">
|
||||
<path d="M213.61,747.063 L215.759,747.063 L215.759,815.886 L213.61,815.886 z" fill="#FFFFFF"/>
|
||||
<path d="M213.61,747.063 L215.759,747.063 L215.759,815.886 L213.61,815.886 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="15"/>
|
||||
</g>
|
||||
<g id="path3795-4-8-7-8">
|
||||
<path d="M229.451,833.216 C229.451,841.094 223.001,847.48 215.045,847.48 C207.089,847.48 200.639,841.094 200.639,833.216 C200.639,825.338 207.089,818.952 215.045,818.952 C223.001,818.952 229.451,825.338 229.451,833.216 z" fill="#FFFFFF"/>
|
||||
<path d="M229.451,833.216 C229.451,841.094 223.001,847.48 215.045,847.48 C207.089,847.48 200.639,841.094 200.639,833.216 C200.639,825.338 207.089,818.952 215.045,818.952 C223.001,818.952 229.451,825.338 229.451,833.216 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.989"/>
|
||||
</g>
|
||||
<g id="path3795-8-4-8">
|
||||
<path d="M228.437,733.996 C228.437,741.873 221.987,748.26 214.031,748.26 C206.075,748.26 199.626,741.873 199.626,733.996 C199.626,726.118 206.075,719.731 214.031,719.731 C221.987,719.731 228.437,726.118 228.437,733.996 z" fill="#FFFFFF"/>
|
||||
<path d="M228.437,733.996 C228.437,741.873 221.987,748.26 214.031,748.26 C206.075,748.26 199.626,741.873 199.626,733.996 C199.626,726.118 206.075,719.731 214.031,719.731 C221.987,719.731 228.437,726.118 228.437,733.996 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.989"/>
|
||||
</g>
|
||||
<g id="path3795-8-4-8-2">
|
||||
<path d="M295.005,765.037 C295.005,772.915 288.555,779.301 280.599,779.301 C272.643,779.301 266.193,772.915 266.193,765.037 C266.193,757.159 272.643,750.773 280.599,750.773 C288.555,750.773 295.005,757.159 295.005,765.037 z" fill="#FFFFFF"/>
|
||||
<path d="M295.005,765.037 C295.005,772.915 288.555,779.301 280.599,779.301 C272.643,779.301 266.193,772.915 266.193,765.037 C266.193,757.159 272.643,750.773 280.599,750.773 C288.555,750.773 295.005,757.159 295.005,765.037 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.989"/>
|
||||
</g>
|
||||
<g id="path3992-4">
|
||||
<path d="M677.589,395.106 C677.589,405.481 650.861,413.892 617.891,413.892 C584.921,413.892 558.193,405.481 558.193,395.106 C558.193,384.731 584.921,376.32 617.891,376.32 C650.861,376.32 677.589,384.731 677.589,395.106 z" fill="#B3B3B3"/>
|
||||
<path d="M677.589,395.106 C677.589,405.481 650.861,413.892 617.891,413.892 C584.921,413.892 558.193,405.481 558.193,395.106 C558.193,384.731 584.921,376.32 617.891,376.32 C650.861,376.32 677.589,384.731 677.589,395.106 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="10" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect2995-0-2-7">
|
||||
<path d="M651.087,376.107 L651.087,396.357 L632.275,396.357 L632.275,423.576 L651.087,423.576 L651.087,441.482 L678.275,441.482 L678.275,423.576 L697.65,423.576 L697.65,396.357 L678.275,396.357 L678.275,376.107 L651.087,376.107 z" fill="#B3B3B3"/>
|
||||
<path d="M651.087,376.107 L651.087,396.357 L632.275,396.357 L632.275,423.576 L651.087,423.576 L651.087,441.482 L678.275,441.482 L678.275,423.576 L697.65,423.576 L697.65,396.357 L678.275,396.357 L678.275,376.107 L651.087,376.107 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="8.571"/>
|
||||
</g>
|
||||
<g id="path2991-7-2">
|
||||
<path d="M723.493,615.976 C723.493,653.992 692.676,684.81 654.66,684.81 C616.644,684.81 585.827,653.992 585.827,615.976 C585.827,577.961 616.644,547.143 654.66,547.143 C692.676,547.143 723.493,577.961 723.493,615.976 z" fill="#B3B3B3"/>
|
||||
<path d="M723.493,615.976 C723.493,653.992 692.676,684.81 654.66,684.81 C616.644,684.81 585.827,653.992 585.827,615.976 C585.827,577.961 616.644,547.143 654.66,547.143 C692.676,547.143 723.493,577.961 723.493,615.976 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.66"/>
|
||||
</g>
|
||||
<g id="path2993-4-7">
|
||||
<path d="M707.365,616.038 C707.365,645.075 683.826,668.615 654.789,668.615 C625.751,668.615 602.212,645.075 602.212,616.038 C602.212,587.001 625.751,563.462 654.789,563.462 C683.826,563.462 707.365,587.001 707.365,616.038 z" fill="#FFFFFF"/>
|
||||
<path d="M707.365,616.038 C707.365,645.075 683.826,668.615 654.789,668.615 C625.751,668.615 602.212,645.075 602.212,616.038 C602.212,587.001 625.751,563.462 654.789,563.462 C683.826,563.462 707.365,587.001 707.365,616.038 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.504"/>
|
||||
</g>
|
||||
<g id="rect2995-0-6">
|
||||
<path d="M695.28,567.323 L708.018,581.411 L615.14,665.393 L602.401,651.305 z" fill="#B3B3B3"/>
|
||||
<path d="M695.28,567.323 L708.018,581.411 L615.14,665.393 L602.401,651.305 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="1.007"/>
|
||||
</g>
|
||||
<g id="g4284">
|
||||
<g id="rect4201">
|
||||
<path d="M336.17,737.103 C336.17,737.103 372.868,730.771 385.714,731.389 C398.56,732.007 412.402,737.103 412.402,737.103 L412.402,830.478 C412.402,830.478 398.56,825.382 385.714,824.764 C372.868,824.146 336.17,830.478 336.17,830.478 z" fill="#FFFFFF"/>
|
||||
<path d="M336.17,737.103 C336.17,737.103 372.868,730.771 385.714,731.389 C398.56,732.007 412.402,737.103 412.402,737.103 L412.402,830.478 C412.402,830.478 398.56,825.382 385.714,824.764 C372.868,824.146 336.17,830.478 336.17,830.478 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="9.482" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect4203">
|
||||
<path d="M355.332,755.123 L391.81,755.123 L391.81,758.744 L355.332,758.744 z" fill="#FFFFFF"/>
|
||||
<path d="M355.332,755.123 L391.81,755.123 L391.81,758.744 L355.332,758.744 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.807" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect4203-2">
|
||||
<path d="M356.047,777.981 L392.525,777.981 L392.525,781.602 L356.047,781.602 z" fill="#FFFFFF"/>
|
||||
<path d="M356.047,777.981 L392.525,777.981 L392.525,781.602 L356.047,781.602 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.807" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect4203-2-3">
|
||||
<path d="M356.047,799.981 L392.525,799.981 L392.525,803.602 L356.047,803.602 z" fill="#FFFFFF"/>
|
||||
<path d="M356.047,799.981 L392.525,799.981 L392.525,803.602 L356.047,803.602 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.807" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="path4245">
|
||||
<path d="M332.988,837.946 C332.988,837.946 372.01,827.288 386.458,828.206 C400.906,829.124 417.012,837.946 417.012,837.946" fill="#B3B3B3"/>
|
||||
<path d="M332.988,837.946 C332.988,837.946 372.01,827.288 386.458,828.206 C400.906,829.124 417.012,837.946 417.012,837.946" fill-opacity="0" stroke="#B3B3B3" stroke-width="12.961"/>
|
||||
</g>
|
||||
<g id="g4277">
|
||||
<g id="rect4201-2">
|
||||
<path d="M494.67,736.807 C494.67,736.807 457.853,730.474 444.965,731.092 C432.078,731.71 418.191,736.807 418.191,736.807 L418.191,830.181 C418.191,830.181 432.078,825.085 444.965,824.467 C457.853,823.849 494.67,830.181 494.67,830.181 z" fill="#FFFFFF"/>
|
||||
<path d="M494.67,736.807 C494.67,736.807 457.853,730.474 444.965,731.092 C432.078,731.71 418.191,736.807 418.191,736.807 L418.191,830.181 C418.191,830.181 432.078,825.085 444.965,824.467 C457.853,823.849 494.67,830.181 494.67,830.181 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="9.513" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect4203-21">
|
||||
<path d="M475.446,754.826 L438.849,754.826 L438.849,758.447 L475.446,758.447 z" fill="#FFFFFF"/>
|
||||
<path d="M475.446,754.826 L438.849,754.826 L438.849,758.447 L475.446,758.447 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.833" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect4203-2-6">
|
||||
<path d="M474.729,777.684 L438.133,777.684 L438.133,781.305 L474.729,781.305 z" fill="#FFFFFF"/>
|
||||
<path d="M474.729,777.684 L438.133,777.684 L438.133,781.305 L474.729,781.305 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.833" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect4203-2-3-8">
|
||||
<path d="M474.729,799.684 L438.133,799.684 L438.133,803.305 L474.729,803.305 z" fill="#FFFFFF"/>
|
||||
<path d="M474.729,799.684 L438.133,799.684 L438.133,803.305 L474.729,803.305 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.833" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="path4245-5">
|
||||
<path d="M497.863,837.649 C497.863,837.649 458.714,826.991 444.219,827.909 C429.724,828.827 413.566,837.649 413.566,837.649" fill="#B3B3B3"/>
|
||||
<path d="M497.863,837.649 C497.863,837.649 458.714,826.991 444.219,827.909 C429.724,828.827 413.566,837.649 413.566,837.649" fill-opacity="0" stroke="#B3B3B3" stroke-width="13.003"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="g3107">
|
||||
<g id="rect3075">
|
||||
<path d="M563.777,776.482 L610,730.259 L672.149,792.408 L625.926,838.631 z" fill="#B3B3B3"/>
|
||||
<path d="M563.777,776.482 L610,730.259 L672.149,792.408 L625.926,838.631 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="9.707" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect3075-1">
|
||||
<path d="M559.935,726.033 L607.36,726.566 L606.827,773.992 L559.402,773.459 z" fill="#B3B3B3"/>
|
||||
<path d="M559.935,726.033 L607.36,726.566 L606.827,773.992 L559.402,773.459 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="6.074" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="path3100">
|
||||
<path d="M592.33,748.581 C595.261,751.513 595.261,756.266 592.33,759.198 C589.398,762.13 584.644,762.13 581.712,759.198 C578.78,756.266 578.78,751.513 581.712,748.581 C584.644,745.649 589.398,745.649 592.33,748.581 z" fill="#FFFFFF"/>
|
||||
<path d="M592.33,748.581 C595.261,751.513 595.261,756.266 592.33,759.198 C589.398,762.13 584.644,762.13 581.712,759.198 C578.78,756.266 578.78,751.513 581.712,748.581 C584.644,745.649 589.398,745.649 592.33,748.581 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="9.535" stroke-linecap="round"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="rect2995-0-2-7-7">
|
||||
<path d="M631.179,718.824 L631.179,739.074 L612.367,739.074 L612.367,766.292 L631.179,766.292 L631.179,784.199 L658.367,784.199 L658.367,766.292 L677.742,766.292 L677.742,739.074 L658.367,739.074 L658.367,718.824 L631.179,718.824 z" fill="#B3B3B3"/>
|
||||
<path d="M631.179,718.824 L631.179,739.074 L612.367,739.074 L612.367,766.292 L631.179,766.292 L631.179,784.199 L658.367,784.199 L658.367,766.292 L677.742,766.292 L677.742,739.074 L658.367,739.074 L658.367,718.824 L631.179,718.824 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="8.571"/>
|
||||
</g>
|
||||
<path d="M37.874,887.379 L150.697,887.379 L150.697,1024.488 L37.874,1024.488 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="6.166" id="rect3083"/>
|
||||
<path d="M37.568,887.787 L151.004,887.787 L151.004,998.366 L37.568,998.366 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="5.553" id="rect3083-7"/>
|
||||
<path d="M40.533,888.354 L60.182,888.354 L60.182,996.37 L40.533,996.37 z" fill="#B3B3B3" id="rect2995-0-4"/>
|
||||
<path d="M69.033,901.926 L80.11,901.926 L80.11,913.513 L69.033,913.513 z" fill="#B3B3B3" id="rect2995-0-4-0"/>
|
||||
<path d="M69.033,924.949 L80.11,924.949 L80.11,936.537 L69.033,936.537 z" fill="#B3B3B3" id="rect2995-0-4-0-9"/>
|
||||
<path d="M69.033,947.973 L80.11,947.973 L80.11,959.561 L69.033,959.561 z" fill="#B3B3B3" id="rect2995-0-4-0-9-4"/>
|
||||
<path d="M69.033,970.997 L80.11,970.997 L80.11,982.585 L69.033,982.585 z" fill="#B3B3B3" id="rect2995-0-4-0-9-4-8"/>
|
||||
<path d="M58.747,1008.069 L86.967,1008.069 L86.967,1028.227 L58.747,1028.227 z" fill="#B3B3B3" id="rect2995-0-4-8"/>
|
||||
<path d="M74.13,1027.791 L66.438,1034.293 L58.747,1040.796 L58.747,1027.791 L58.747,1014.786 L66.438,1021.288 z" fill="#B3B3B3" id="path4002"/>
|
||||
<path d="M73.027,1027.791 L79.978,1034.293 L86.93,1040.796 L86.93,1027.791 L86.93,1014.786 L79.978,1021.288 z" fill="#B3B3B3" id="path4002-2"/>
|
||||
<path d="M197.589,886.909 L310.411,886.909 L310.411,1024.017 L197.589,1024.017 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="6.166" id="rect3083-4"/>
|
||||
<path d="M197.282,887.317 L310.718,887.317 L310.718,997.896 L197.282,997.896 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="5.553" id="rect3083-7-5"/>
|
||||
<path d="M200.247,887.884 L219.896,887.884 L219.896,995.9 L200.247,995.9 z" fill="#B3B3B3" id="rect2995-0-4-5"/>
|
||||
<path d="M218.461,1007.598 L246.682,1007.598 L246.682,1027.757 L218.461,1027.757 z" fill="#B3B3B3" id="rect2995-0-4-8-5"/>
|
||||
<path d="M233.844,1027.321 L226.153,1033.824 L218.461,1040.326 L218.461,1027.321 L218.461,1014.316 L226.153,1020.819 z" fill="#B3B3B3" id="path4002-27"/>
|
||||
<path d="M232.741,1027.321 L239.693,1033.824 L246.644,1040.326 L246.644,1027.321 L246.644,1014.316 L239.693,1020.819 z" fill="#B3B3B3" id="path4002-2-6"/>
|
||||
<path d="M253.805,948.352 L273.454,948.352 L273.454,986.667 L253.805,986.667 z" fill="#B3B3B3" id="rect2995-0-4-5-7"/>
|
||||
<path d="M228.066,900.37 L247.715,900.37 L247.715,933.129 L228.066,933.129 z" fill="#B3B3B3" id="rect2995-0-4-5-7-6"/>
|
||||
<path d="M227.906,932.653 L241.436,918.405 L269.22,944.789 L255.69,959.037 z" fill="#B3B3B3" id="rect2995-0-4-5-7-8"/>
|
||||
<path d="M278.632,900.37 L298.281,900.37 L298.281,933.129 L278.632,933.129 z" fill="#B3B3B3" id="rect2995-0-4-5-7-6-9"/>
|
||||
<path d="M298.392,932.121 L285.456,918.405 L258.894,943.805 L271.829,957.522 z" fill="#B3B3B3" id="rect2995-0-4-5-7-8-2"/>
|
||||
<g id="rect3083-7-5-7">
|
||||
<path d="M362.125,942.66 L476.301,942.66 L476.301,1025.189 L362.125,1025.189 z" fill="#FFFFFF"/>
|
||||
<path d="M362.125,942.66 L476.301,942.66 L476.301,1025.189 L362.125,1025.189 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="4.813"/>
|
||||
</g>
|
||||
<path d="M450.3,943.806 L475.505,943.806 L475.505,1025.558 L450.3,1025.558 z" fill="#B3B3B3" id="rect2995-0-4-5-9"/>
|
||||
<path d="M376.811,954.433 L454.039,954.433 L454.039,964.969 L376.811,964.969 z" fill="#B3B3B3" id="rect2995-0-4-5-9-5"/>
|
||||
<path d="M376.237,978.161 L453.464,978.161 L453.464,988.697 L376.237,988.697 z" fill="#B3B3B3" id="rect2995-0-4-5-9-5-4"/>
|
||||
<path d="M376.237,1002.161 L453.464,1002.161 L453.464,1012.697 L376.237,1012.697 z" fill="#B3B3B3" id="rect2995-0-4-5-9-5-4-3"/>
|
||||
<path d="M381.162,940.8 L381.162,910.941 C381.162,910.941 390.092,887.418 418.377,887.055 C445.32,886.708 454.992,909.954 454.992,909.954 L455.107,941.947" fill-opacity="0" stroke="#B3B3B3" stroke-width="15.226" id="path4310"/>
|
||||
<g id="path2991-7-1-4">
|
||||
<path d="M691.408,957.408 C691.408,995.423 660.59,1026.241 622.575,1026.241 C584.559,1026.241 553.742,995.423 553.742,957.408 C553.742,919.392 584.559,888.575 622.575,888.575 C660.59,888.575 691.408,919.392 691.408,957.408 z" fill="#B3B3B3"/>
|
||||
<path d="M691.408,957.408 C691.408,995.423 660.59,1026.241 622.575,1026.241 C584.559,1026.241 553.742,995.423 553.742,957.408 C553.742,919.392 584.559,888.575 622.575,888.575 C660.59,888.575 691.408,919.392 691.408,957.408 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.66"/>
|
||||
</g>
|
||||
<g id="path2993-4-5-8">
|
||||
<path d="M680.685,957.898 C680.685,989.684 654.917,1015.451 623.132,1015.451 C591.346,1015.451 565.579,989.684 565.579,957.898 C565.579,926.113 591.346,900.346 623.132,900.346 C654.917,900.346 680.685,926.113 680.685,957.898 z" fill="#FFFFFF"/>
|
||||
<path d="M680.685,957.898 C680.685,989.684 654.917,1015.451 623.132,1015.451 C591.346,1015.451 565.579,989.684 565.579,957.898 C565.579,926.113 591.346,900.346 623.132,900.346 C654.917,900.346 680.685,926.113 680.685,957.898 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.552"/>
|
||||
</g>
|
||||
<path d="M617.433,939.273 L631.101,939.273 L631.101,1004.878 L617.433,1004.878 z" fill="#B3B3B3" id="rect2995-0-2-8"/>
|
||||
<g id="rect4046-3-4">
|
||||
<path d="M595.942,931.209 L553.247,907.307 L560.31,944.242 L595.942,931.209 z" fill="#FFFFFF"/>
|
||||
<path d="M595.942,931.209 L553.247,907.307 L560.31,944.242 L595.942,931.209 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="1.93" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect4046-5">
|
||||
<path d="M588.909,926.673 L560.094,910.545 L564.713,935.73 L588.909,926.673 z" fill="#B3B3B3"/>
|
||||
<path d="M588.909,926.673 L560.094,910.545 L564.713,935.73 L588.909,926.673 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="1.313" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect3075-11">
|
||||
<path d="M779.562,898.094 C779.396,912.75 779.229,927.406 779.062,942.062 C781.26,942.084 783.458,942.104 785.656,942.125 C784.104,943.688 782.552,945.25 781,946.813 C801.708,967.521 822.417,988.229 843.125,1008.938 C858.531,993.531 873.938,978.125 889.344,962.719 C868.635,942 847.927,921.281 827.219,900.563 C825.49,902.302 823.76,904.042 822.031,905.781 C822.052,903.386 822.073,900.99 822.094,898.594 C807.917,898.427 793.74,898.261 779.563,898.094 z" fill="#FFFFFF"/>
|
||||
<path d="M779.562,898.094 C779.396,912.75 779.229,927.406 779.062,942.062 C781.26,942.084 783.458,942.104 785.656,942.125 C784.104,943.688 782.552,945.25 781,946.813 C801.708,967.521 822.417,988.229 843.125,1008.938 C858.531,993.531 873.938,978.125 889.344,962.719 C868.635,942 847.927,921.281 827.219,900.563 C825.49,902.302 823.76,904.042 822.031,905.781 C822.052,903.386 822.073,900.99 822.094,898.594 C807.917,898.427 793.74,898.261 779.563,898.094 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="9" stroke-linecap="round"/>
|
||||
</g>
|
||||
<path d="M606.483,964.91 L606.483,951.243 L672.089,951.243 L672.089,964.91 z" fill="#B3B3B3" id="rect2995-0-2-8-6"/>
|
||||
<g id="rect3075-11-7">
|
||||
<path d="M786.383,905.075 C786.256,916.229 786.129,927.383 786.003,938.537 C787.675,938.553 789.348,938.568 791.021,938.584 C789.839,939.773 788.658,940.963 787.477,942.152 C803.237,957.911 818.997,973.671 834.756,989.431 C846.481,977.706 858.206,965.982 869.93,954.257 C854.171,938.489 838.411,922.722 822.651,906.954 C821.335,908.278 820.019,909.602 818.703,910.926 C818.719,909.102 818.734,907.279 818.751,905.456 C807.961,905.329 797.172,905.202 786.383,905.075 z" fill="#FFFFFF"/>
|
||||
<path d="M786.383,905.075 C786.256,916.229 786.129,927.383 786.003,938.537 C787.675,938.553 789.348,938.568 791.021,938.584 C789.839,939.773 788.658,940.963 787.477,942.152 C803.237,957.911 818.997,973.671 834.756,989.431 C846.481,977.706 858.206,965.982 869.93,954.257 C854.171,938.489 838.411,922.722 822.651,906.954 C821.335,908.278 820.019,909.602 818.703,910.926 C818.719,909.102 818.734,907.279 818.751,905.456 C807.961,905.329 797.172,905.202 786.383,905.075 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="6.849" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="path3100-2">
|
||||
<path d="M813.748,916.688 C818.255,921.195 818.255,928.501 813.748,933.008 C809.242,937.514 801.935,937.514 797.429,933.008 C792.922,928.501 792.922,921.195 797.429,916.688 C801.935,912.182 809.242,912.182 813.748,916.688 z" fill="#FFFFFF"/>
|
||||
<path d="M813.748,916.688 C818.255,921.195 818.255,928.501 813.748,933.008 C809.242,937.514 801.935,937.514 797.429,933.008 C792.922,928.501 792.922,921.195 797.429,916.688 C801.935,912.182 809.242,912.182 813.748,916.688 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.585" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect4114">
|
||||
<path d="M813.845,955.107 L834.888,934.064 L864.012,963.188 L842.969,984.231 z" fill="#FFFFFF"/>
|
||||
<path d="M813.845,955.107 L834.888,934.064 L864.012,963.188 L842.969,984.231 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.133"/>
|
||||
</g>
|
||||
<g id="path2991-7-6">
|
||||
<path d="M969.889,84.636 C969.889,122.652 939.072,153.469 901.056,153.469 C863.04,153.469 832.223,122.652 832.223,84.636 C832.223,46.62 863.04,15.803 901.056,15.803 C939.072,15.803 969.889,46.62 969.889,84.636 z" fill="#A0A0A0"/>
|
||||
<path d="M969.889,84.636 C969.889,122.652 939.072,153.469 901.056,153.469 C863.04,153.469 832.223,122.652 832.223,84.636 C832.223,46.62 863.04,15.803 901.056,15.803 C939.072,15.803 969.889,46.62 969.889,84.636 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.66"/>
|
||||
</g>
|
||||
<g id="path2993-4-8">
|
||||
<path d="M959.166,85.127 C959.166,116.912 933.399,142.679 901.613,142.679 C869.828,142.679 844.061,116.912 844.061,85.127 C844.061,53.341 869.828,27.574 901.613,27.574 C933.399,27.574 959.166,53.341 959.166,85.127 z" fill="#FFFFFF"/>
|
||||
<path d="M959.166,85.127 C959.166,116.912 933.399,142.679 901.613,142.679 C869.828,142.679 844.061,116.912 844.061,85.127 C844.061,53.341 869.828,27.574 901.613,27.574 C933.399,27.574 959.166,53.341 959.166,85.127 z" fill-opacity="0" stroke="#808080" stroke-width="0.552"/>
|
||||
</g>
|
||||
<g id="rect2995-0-8">
|
||||
<path d="M891.972,36.558 L911.239,36.558 L911.239,102.049 L891.972,102.049 z" fill="#A0A0A0"/>
|
||||
<path d="M891.972,36.558 L911.239,36.558 L911.239,102.049 L891.972,102.049 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.733"/>
|
||||
</g>
|
||||
<g id="rect2997-9-2">
|
||||
<path d="M892.143,114.967 L911.762,114.967 L911.762,132.299 L892.143,132.299 z" fill="#A0A0A0"/>
|
||||
<path d="M892.143,114.967 L911.762,114.967 L911.762,132.299 L892.143,132.299 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.381"/>
|
||||
</g>
|
||||
<g id="g4284-1">
|
||||
<g id="rect4201-26">
|
||||
<path d="M829.877,204.061 C829.877,204.061 866.575,197.729 879.421,198.347 C892.267,198.964 906.109,204.061 906.109,204.061 L906.109,297.436 C906.109,297.436 892.267,292.339 879.421,291.721 C866.575,291.104 829.877,297.436 829.877,297.436 z" fill="#FFFFFF"/>
|
||||
<path d="M829.877,204.061 C829.877,204.061 866.575,197.729 879.421,198.347 C892.267,198.964 906.109,204.061 906.109,204.061 L906.109,297.436 C906.109,297.436 892.267,292.339 879.421,291.721 C866.575,291.104 829.877,297.436 829.877,297.436 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="9.482" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect4203-0">
|
||||
<path d="M849.039,222.081 L885.517,222.081 L885.517,225.701 L849.039,225.701 z" fill="#FFFFFF"/>
|
||||
<path d="M849.039,222.081 L885.517,222.081 L885.517,225.701 L849.039,225.701 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="7.807" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect4203-2-4">
|
||||
<path d="M849.754,244.938 L886.232,244.938 L886.232,248.559 L849.754,248.559 z" fill="#FFFFFF"/>
|
||||
<path d="M849.754,244.938 L886.232,244.938 L886.232,248.559 L849.754,248.559 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="7.807" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect4203-2-3-9">
|
||||
<path d="M849.754,266.939 L886.232,266.939 L886.232,270.559 L849.754,270.559 z" fill="#FFFFFF"/>
|
||||
<path d="M849.754,266.939 L886.232,266.939 L886.232,270.559 L849.754,270.559 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="7.807" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="path4245-4">
|
||||
<path d="M826.695,304.903 C826.695,304.903 865.716,294.245 880.165,295.163 C894.613,296.081 910.719,304.903 910.719,304.903" fill="#B3B3B3"/>
|
||||
<path d="M826.695,304.903 C826.695,304.903 865.716,294.245 880.165,295.163 C894.613,296.081 910.719,304.903 910.719,304.903" fill-opacity="0" stroke="#A0A0A0" stroke-width="12.961"/>
|
||||
</g>
|
||||
<g id="g4277-6">
|
||||
<g id="rect4201-2-0">
|
||||
<path d="M988.377,203.764 C988.377,203.764 951.56,197.432 938.672,198.05 C925.784,198.668 911.898,203.764 911.898,203.764 L911.898,297.139 C911.898,297.139 925.784,292.042 938.672,291.425 C951.56,290.807 988.377,297.139 988.377,297.139 z" fill="#FFFFFF"/>
|
||||
<path d="M988.377,203.764 C988.377,203.764 951.56,197.432 938.672,198.05 C925.784,198.668 911.898,203.764 911.898,203.764 L911.898,297.139 C911.898,297.139 925.784,292.042 938.672,291.425 C951.56,290.807 988.377,297.139 988.377,297.139 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="9.513" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect4203-21-3">
|
||||
<path d="M969.152,221.784 L932.556,221.784 L932.556,225.405 L969.152,225.405 z" fill="#FFFFFF"/>
|
||||
<path d="M969.152,221.784 L932.556,221.784 L932.556,225.405 L969.152,225.405 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="7.833" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect4203-2-6-6">
|
||||
<path d="M968.436,244.641 L931.84,244.641 L931.84,248.262 L968.436,248.262 z" fill="#FFFFFF"/>
|
||||
<path d="M968.436,244.641 L931.84,244.641 L931.84,248.262 L968.436,248.262 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="7.833" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect4203-2-3-8-2">
|
||||
<path d="M968.436,266.642 L931.84,266.642 L931.84,270.263 L968.436,270.263 z" fill="#FFFFFF"/>
|
||||
<path d="M968.436,266.642 L931.84,266.642 L931.84,270.263 L968.436,270.263 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="7.833" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="path4245-5-4">
|
||||
<path d="M991.569,304.606 C991.569,304.606 952.421,293.949 937.926,294.866 C923.431,295.784 907.273,304.606 907.273,304.606" fill="#B3B3B3"/>
|
||||
<path d="M991.569,304.606 C991.569,304.606 952.421,293.949 937.926,294.866 C923.431,295.784 907.273,304.606 907.273,304.606" fill-opacity="0" stroke="#A0A0A0" stroke-width="13.003"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<path d="M942.845,472.215 L942.845,387.348 C942.845,387.348 944.073,377.245 931.797,377.245 C919.521,377.245 898.039,377.245 898.039,377.245" fill-opacity="0" stroke="#A0A0A0" stroke-width="15" id="path3850-1-1"/>
|
||||
<g id="rect3818-4-7">
|
||||
<path d="M867.039,390.985 L869.187,390.985 L869.187,459.808 L867.039,459.808 z" fill="#FFFFFF"/>
|
||||
<path d="M867.039,390.985 L869.187,390.985 L869.187,459.808 L867.039,459.808 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="15"/>
|
||||
</g>
|
||||
<g id="path3795-4-8-4">
|
||||
<path d="M882.879,477.136 C882.879,485.014 876.429,491.4 868.473,491.4 C860.517,491.4 854.068,485.014 854.068,477.136 C854.068,469.259 860.517,462.872 868.473,462.872 C876.429,462.872 882.879,469.259 882.879,477.136 z" fill="#FFFFFF"/>
|
||||
<path d="M882.879,477.136 C882.879,485.014 876.429,491.4 868.473,491.4 C860.517,491.4 854.068,485.014 854.068,477.136 C854.068,469.259 860.517,462.872 868.473,462.872 C876.429,462.872 882.879,469.259 882.879,477.136 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="7.989"/>
|
||||
</g>
|
||||
<g id="path3795-8-0">
|
||||
<path d="M881.865,377.916 C881.865,385.794 875.416,392.18 867.46,392.18 C859.504,392.18 853.054,385.794 853.054,377.916 C853.054,370.038 859.504,363.652 867.46,363.652 C875.416,363.652 881.865,370.038 881.865,377.916 z" fill="#FFFFFF"/>
|
||||
<path d="M881.865,377.916 C881.865,385.794 875.416,392.18 867.46,392.18 C859.504,392.18 853.054,385.794 853.054,377.916 C853.054,370.038 859.504,363.652 867.46,363.652 C875.416,363.652 881.865,370.038 881.865,377.916 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="7.989"/>
|
||||
</g>
|
||||
<g id="path3795-4-0-2-9">
|
||||
<path d="M957.324,477.203 C957.324,485.081 950.875,491.467 942.919,491.467 C934.963,491.467 928.513,485.081 928.513,477.203 C928.513,469.326 934.963,462.939 942.919,462.939 C950.875,462.939 957.324,469.326 957.324,477.203 z" fill="#FFFFFF"/>
|
||||
<path d="M957.324,477.203 C957.324,485.081 950.875,491.467 942.919,491.467 C934.963,491.467 928.513,485.081 928.513,477.203 C928.513,469.326 934.963,462.939 942.919,462.939 C950.875,462.939 957.324,469.326 957.324,477.203 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="7.989"/>
|
||||
</g>
|
||||
<g id="path3852-4-4">
|
||||
<path d="M916.255,352.994 L916.255,399.653 L886.165,378.443 z" fill="#A0A0A0"/>
|
||||
<path d="M916.255,352.994 L916.255,399.653 L886.165,378.443 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="0.55"/>
|
||||
</g>
|
||||
<g id="rect3953">
|
||||
<path d="M871.854,546.111 L878.997,553.254 L833.283,598.968 L826.14,591.825 z" fill="#A0A0A0"/>
|
||||
<path d="M871.854,546.111 L878.997,553.254 L833.283,598.968 L826.14,591.825 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="7.148"/>
|
||||
</g>
|
||||
<g id="rect3953-8">
|
||||
<path d="M877.987,632.232 L870.844,639.374 L825.13,593.66 L832.273,586.517 z" fill="#A0A0A0"/>
|
||||
<path d="M877.987,632.232 L870.844,639.374 L825.13,593.66 L832.273,586.517 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="7.148"/>
|
||||
</g>
|
||||
<g id="rect3953-82">
|
||||
<path d="M959.87,591.826 L952.728,598.968 L907.013,553.254 L914.156,546.111 z" fill="#A0A0A0"/>
|
||||
<path d="M959.87,591.826 L952.728,598.968 L907.013,553.254 L914.156,546.111 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="7.148"/>
|
||||
</g>
|
||||
<g id="rect3953-82-4">
|
||||
<path d="M910.116,641.395 L902.973,634.252 L948.687,588.538 L955.83,595.68 z" fill="#A0A0A0"/>
|
||||
<path d="M910.116,641.395 L902.973,634.252 L948.687,588.538 L955.83,595.68 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="7.148"/>
|
||||
</g>
|
||||
<g id="g4016">
|
||||
<g id="rect3953-82-4-1-4">
|
||||
<path d="M901.66,795.087 L917.47,779.277 L966.852,828.659 L951.042,844.469 z" fill="#A0A0A0"/>
|
||||
<path d="M901.66,795.087 L917.47,779.277 L966.852,828.659 L951.042,844.469 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="11.053"/>
|
||||
</g>
|
||||
<g id="rect3953-82-4-1-7-0">
|
||||
<path d="M866.314,748.302 L870.684,743.931 L923.627,796.873 L919.256,801.244 z" fill="#A0A0A0"/>
|
||||
<path d="M866.314,748.302 L870.684,743.931 L923.627,796.873 L919.256,801.244 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="6.017"/>
|
||||
</g>
|
||||
<g id="path3226">
|
||||
<path d="M834.333,751.795 C824.112,741.574 824.591,724.521 835.405,713.708 C846.218,702.894 863.271,702.414 873.493,712.636 C883.714,722.858 883.235,739.911 872.421,750.724 C861.608,761.538 844.555,762.017 834.333,751.795 z" fill="#A0A0A0"/>
|
||||
<path d="M834.333,751.795 C824.112,741.574 824.591,724.521 835.405,713.708 C846.218,702.894 863.271,702.414 873.493,712.636 C883.714,722.858 883.235,739.911 872.421,750.724 C861.608,761.538 844.555,762.017 834.333,751.795 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="7.536"/>
|
||||
</g>
|
||||
<path d="M830.328,738.658 C819.882,728.213 817.963,713.195 826.042,705.117 C834.121,697.038 849.138,698.957 859.584,709.402 C870.029,719.848 871.948,734.865 863.869,742.944 C855.791,751.023 840.774,749.104 830.328,738.658 z" fill="#FFFFFF" id="path3226-9"/>
|
||||
</g>
|
||||
<path d="M912.995,736.468 L947.28,770.754 L897.28,820.754 L862.995,786.468 z" fill="#FFFFFF" id="rect4027"/>
|
||||
<g id="g4022">
|
||||
<g id="rect3953-82-4-1">
|
||||
<path d="M914.66,784.426 L898.85,768.616 L948.231,719.234 L964.042,735.045 z" fill="#A0A0A0"/>
|
||||
<path d="M914.66,784.426 L898.85,768.616 L948.231,719.234 L964.042,735.045 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="11.053"/>
|
||||
</g>
|
||||
<g id="rect3953-82-4-1-7">
|
||||
<path d="M862.174,826.216 L857.803,821.845 L910.746,768.902 L915.117,773.273 z" fill="#A0A0A0"/>
|
||||
<path d="M862.174,826.216 L857.803,821.845 L910.746,768.902 L915.117,773.273 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="6.017"/>
|
||||
</g>
|
||||
<g id="rect3182">
|
||||
<path d="M851.456,814.022 L836.452,848.251 L870.703,833.269 L872.227,829.8 L854.925,812.498 L851.456,814.022 z" fill="#A0A0A0"/>
|
||||
<path d="M851.456,814.022 L836.452,848.251 L870.703,833.269 L872.227,829.8 L854.925,812.498 L851.456,814.022 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="4.636"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="path2991-7-6-1">
|
||||
<path d="M1228.488,89.805 C1228.488,127.82 1197.671,158.638 1159.655,158.638 C1121.64,158.638 1090.822,127.82 1090.822,89.805 C1090.822,51.789 1121.64,20.971 1159.655,20.971 C1197.671,20.971 1228.488,51.789 1228.488,89.805 z" fill="#3C3C3C"/>
|
||||
<path d="M1228.488,89.805 C1228.488,127.82 1197.671,158.638 1159.655,158.638 C1121.64,158.638 1090.822,127.82 1090.822,89.805 C1090.822,51.789 1121.64,20.971 1159.655,20.971 C1197.671,20.971 1228.488,51.789 1228.488,89.805 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.66"/>
|
||||
</g>
|
||||
<g id="path2993-4-8-7">
|
||||
<path d="M1217.765,90.295 C1217.765,122.081 1191.998,147.848 1160.212,147.848 C1128.427,147.848 1102.66,122.081 1102.66,90.295 C1102.66,58.51 1128.427,32.742 1160.212,32.742 C1191.998,32.742 1217.765,58.51 1217.765,90.295 z" fill="#FFFFFF"/>
|
||||
<path d="M1217.765,90.295 C1217.765,122.081 1191.998,147.848 1160.212,147.848 C1128.427,147.848 1102.66,122.081 1102.66,90.295 C1102.66,58.51 1128.427,32.742 1160.212,32.742 C1191.998,32.742 1217.765,58.51 1217.765,90.295 z" fill-opacity="0" stroke="#3C3C80" stroke-width="0.552"/>
|
||||
</g>
|
||||
<g id="rect2995-0-8-4">
|
||||
<path d="M1150.571,41.726 L1169.838,41.726 L1169.838,107.217 L1150.571,107.217 z" fill="#3C3C3C"/>
|
||||
<path d="M1150.571,41.726 L1169.838,41.726 L1169.838,107.217 L1150.571,107.217 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.733"/>
|
||||
</g>
|
||||
<g id="rect2997-9-2-0">
|
||||
<path d="M1150.742,120.136 L1170.361,120.136 L1170.361,137.467 L1150.742,137.467 z" fill="#3C3C3C"/>
|
||||
<path d="M1150.742,120.136 L1170.361,120.136 L1170.361,137.467 L1150.742,137.467 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.381"/>
|
||||
</g>
|
||||
<g id="g4284-1-9">
|
||||
<g id="rect4201-26-4">
|
||||
<path d="M1088.476,209.229 C1088.476,209.229 1125.174,202.897 1138.02,203.515 C1150.866,204.133 1164.708,209.229 1164.708,209.229 L1164.708,302.604 C1164.708,302.604 1150.866,297.508 1138.02,296.89 C1125.174,296.272 1088.476,302.604 1088.476,302.604 z" fill="#FFFFFF"/>
|
||||
<path d="M1088.476,209.229 C1088.476,209.229 1125.174,202.897 1138.02,203.515 C1150.866,204.133 1164.708,209.229 1164.708,209.229 L1164.708,302.604 C1164.708,302.604 1150.866,297.508 1138.02,296.89 C1125.174,296.272 1088.476,302.604 1088.476,302.604 z" fill-opacity="0" stroke="#3C3C3C" stroke-width="9.482" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect4203-0-8">
|
||||
<path d="M1107.638,227.249 L1144.116,227.249 L1144.116,230.87 L1107.638,230.87 z" fill="#FFFFFF"/>
|
||||
<path d="M1107.638,227.249 L1144.116,227.249 L1144.116,230.87 L1107.638,230.87 z" fill-opacity="0" stroke="#3C3C3C" stroke-width="7.807" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect4203-2-4-8">
|
||||
<path d="M1108.353,250.107 L1144.831,250.107 L1144.831,253.728 L1108.353,253.728 z" fill="#FFFFFF"/>
|
||||
<path d="M1108.353,250.107 L1144.831,250.107 L1144.831,253.728 L1108.353,253.728 z" fill-opacity="0" stroke="#3C3C3C" stroke-width="7.807" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect4203-2-3-9-2">
|
||||
<path d="M1108.353,272.107 L1144.831,272.107 L1144.831,275.728 L1108.353,275.728 z" fill="#FFFFFF"/>
|
||||
<path d="M1108.353,272.107 L1144.831,272.107 L1144.831,275.728 L1108.353,275.728 z" fill-opacity="0" stroke="#3C3C3C" stroke-width="7.807" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="path4245-4-4">
|
||||
<path d="M1085.294,310.072 C1085.294,310.072 1124.315,299.414 1138.764,300.332 C1153.212,301.25 1169.318,310.072 1169.318,310.072" fill="#B3B3B3"/>
|
||||
<path d="M1085.294,310.072 C1085.294,310.072 1124.315,299.414 1138.764,300.332 C1153.212,301.25 1169.318,310.072 1169.318,310.072" fill-opacity="0" stroke="#3C3C3C" stroke-width="12.961"/>
|
||||
</g>
|
||||
<g id="g4277-6-5">
|
||||
<g id="rect4201-2-0-5">
|
||||
<path d="M1246.976,208.933 C1246.976,208.933 1210.159,202.601 1197.271,203.218 C1184.384,203.836 1170.497,208.933 1170.497,208.933 L1170.497,302.308 C1170.497,302.308 1184.384,297.211 1197.271,296.593 C1210.159,295.976 1246.976,302.308 1246.976,302.308 z" fill="#FFFFFF"/>
|
||||
<path d="M1246.976,208.933 C1246.976,208.933 1210.159,202.601 1197.271,203.218 C1184.384,203.836 1170.497,208.933 1170.497,208.933 L1170.497,302.308 C1170.497,302.308 1184.384,297.211 1197.271,296.593 C1210.159,295.976 1246.976,302.308 1246.976,302.308 z" fill-opacity="0" stroke="#3C3C3C" stroke-width="9.513" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect4203-21-3-1">
|
||||
<path d="M1227.751,226.953 L1191.155,226.953 L1191.155,230.573 L1227.751,230.573 z" fill="#FFFFFF"/>
|
||||
<path d="M1227.751,226.953 L1191.155,226.953 L1191.155,230.573 L1227.751,230.573 z" fill-opacity="0" stroke="#3C3C3C" stroke-width="7.833" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect4203-2-6-6-7">
|
||||
<path d="M1227.035,249.81 L1190.439,249.81 L1190.439,253.431 L1227.035,253.431 z" fill="#FFFFFF"/>
|
||||
<path d="M1227.035,249.81 L1190.439,249.81 L1190.439,253.431 L1227.035,253.431 z" fill-opacity="0" stroke="#3C3C3C" stroke-width="7.833" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect4203-2-3-8-2-1">
|
||||
<path d="M1227.035,271.811 L1190.439,271.811 L1190.439,275.431 L1227.035,275.431 z" fill="#FFFFFF"/>
|
||||
<path d="M1227.035,271.811 L1190.439,271.811 L1190.439,275.431 L1227.035,275.431 z" fill-opacity="0" stroke="#3C3C3C" stroke-width="7.833" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="path4245-5-4-1">
|
||||
<path d="M1250.169,309.775 C1250.169,309.775 1211.02,299.117 1196.525,300.035 C1182.03,300.953 1165.872,309.775 1165.872,309.775" fill="#B3B3B3"/>
|
||||
<path d="M1250.169,309.775 C1250.169,309.775 1211.02,299.117 1196.525,300.035 C1182.03,300.953 1165.872,309.775 1165.872,309.775" fill-opacity="0" stroke="#3C3C3C" stroke-width="13.003"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<path d="M1201.444,477.383 L1201.444,392.516 C1201.444,392.516 1202.672,382.413 1190.396,382.413 C1178.12,382.413 1156.638,382.413 1156.638,382.413" fill-opacity="0" stroke="#3C3C3C" stroke-width="15" id="path3850-1-1-5"/>
|
||||
<g id="rect3818-4-7-2">
|
||||
<path d="M1125.638,396.153 L1127.786,396.153 L1127.786,464.977 L1125.638,464.977 z" fill="#FFFFFF"/>
|
||||
<path d="M1125.638,396.153 L1127.786,396.153 L1127.786,464.977 L1125.638,464.977 z" fill-opacity="0" stroke="#3C3C3C" stroke-width="15"/>
|
||||
</g>
|
||||
<g id="path3795-4-8-4-7">
|
||||
<path d="M1141.478,482.305 C1141.478,490.183 1135.028,496.569 1127.072,496.569 C1119.116,496.569 1112.667,490.183 1112.667,482.305 C1112.667,474.427 1119.116,468.041 1127.072,468.041 C1135.028,468.041 1141.478,474.427 1141.478,482.305 z" fill="#FFFFFF"/>
|
||||
<path d="M1141.478,482.305 C1141.478,490.183 1135.028,496.569 1127.072,496.569 C1119.116,496.569 1112.667,490.183 1112.667,482.305 C1112.667,474.427 1119.116,468.041 1127.072,468.041 C1135.028,468.041 1141.478,474.427 1141.478,482.305 z" fill-opacity="0" stroke="#3C3C3C" stroke-width="7.989"/>
|
||||
</g>
|
||||
<g id="path3795-8-0-6">
|
||||
<path d="M1140.464,383.085 C1140.464,390.962 1134.015,397.349 1126.059,397.349 C1118.103,397.349 1111.653,390.962 1111.653,383.085 C1111.653,375.207 1118.103,368.82 1126.059,368.82 C1134.015,368.82 1140.464,375.207 1140.464,383.085 z" fill="#FFFFFF"/>
|
||||
<path d="M1140.464,383.085 C1140.464,390.962 1134.015,397.349 1126.059,397.349 C1118.103,397.349 1111.653,390.962 1111.653,383.085 C1111.653,375.207 1118.103,368.82 1126.059,368.82 C1134.015,368.82 1140.464,375.207 1140.464,383.085 z" fill-opacity="0" stroke="#3C3C3C" stroke-width="7.989"/>
|
||||
</g>
|
||||
<g id="path3795-4-0-2-9-1">
|
||||
<path d="M1215.923,482.372 C1215.923,490.25 1209.474,496.636 1201.518,496.636 C1193.562,496.636 1187.112,490.25 1187.112,482.372 C1187.112,474.494 1193.562,468.108 1201.518,468.108 C1209.474,468.108 1215.923,474.494 1215.923,482.372 z" fill="#FFFFFF"/>
|
||||
<path d="M1215.923,482.372 C1215.923,490.25 1209.474,496.636 1201.518,496.636 C1193.562,496.636 1187.112,490.25 1187.112,482.372 C1187.112,474.494 1193.562,468.108 1201.518,468.108 C1209.474,468.108 1215.923,474.494 1215.923,482.372 z" fill-opacity="0" stroke="#3C3C3C" stroke-width="7.989"/>
|
||||
</g>
|
||||
<g id="path3852-4-4-4">
|
||||
<path d="M1174.854,358.162 L1174.854,404.822 L1144.764,383.612 z" fill="#3C3C3C"/>
|
||||
<path d="M1174.854,358.162 L1174.854,404.822 L1144.764,383.612 z" fill-opacity="0" stroke="#3C3C3C" stroke-width="0.55"/>
|
||||
</g>
|
||||
<g id="rect3953-2">
|
||||
<path d="M1130.453,551.28 L1137.596,558.423 L1091.882,604.137 L1084.739,596.994 z" fill="#3C3C3C"/>
|
||||
<path d="M1130.453,551.28 L1137.596,558.423 L1091.882,604.137 L1084.739,596.994 z" fill-opacity="0" stroke="#3C3C3C" stroke-width="7.148"/>
|
||||
</g>
|
||||
<g id="rect3953-8-3">
|
||||
<path d="M1136.586,637.4 L1129.443,644.543 L1083.729,598.829 L1090.872,591.686 z" fill="#3C3C3C"/>
|
||||
<path d="M1136.586,637.4 L1129.443,644.543 L1083.729,598.829 L1090.872,591.686 z" fill-opacity="0" stroke="#3C3C3C" stroke-width="7.148"/>
|
||||
</g>
|
||||
<g id="rect3953-82-2">
|
||||
<path d="M1218.469,596.994 L1211.327,604.137 L1165.612,558.423 L1172.755,551.28 z" fill="#3C3C3C"/>
|
||||
<path d="M1218.469,596.994 L1211.327,604.137 L1165.612,558.423 L1172.755,551.28 z" fill-opacity="0" stroke="#3C3C3C" stroke-width="7.148"/>
|
||||
</g>
|
||||
<g id="rect3953-82-4-2">
|
||||
<path d="M1168.715,646.563 L1161.572,639.42 L1207.286,593.706 L1214.429,600.849 z" fill="#3C3C3C"/>
|
||||
<path d="M1168.715,646.563 L1161.572,639.42 L1207.286,593.706 L1214.429,600.849 z" fill-opacity="0" stroke="#3C3C3C" stroke-width="7.148"/>
|
||||
</g>
|
||||
<g id="g4138">
|
||||
<g id="rect3953-82-4-1-4-6">
|
||||
<path d="M1160.259,800.256 L1176.069,784.446 L1225.451,833.827 L1209.641,849.638 z" fill="#3C3C3C"/>
|
||||
<path d="M1160.259,800.256 L1176.069,784.446 L1225.451,833.827 L1209.641,849.638 z" fill-opacity="0" stroke="#3C3C3C" stroke-width="11.053"/>
|
||||
</g>
|
||||
<g id="rect3953-82-4-1-7-0-8">
|
||||
<path d="M1124.913,753.47 L1129.283,749.099 L1182.226,802.042 L1177.855,806.413 z" fill="#3C3C3C"/>
|
||||
<path d="M1124.913,753.47 L1129.283,749.099 L1182.226,802.042 L1177.855,806.413 z" fill-opacity="0" stroke="#3C3C3C" stroke-width="6.017"/>
|
||||
</g>
|
||||
<g id="path3226-5">
|
||||
<path d="M1092.932,756.964 C1082.711,746.742 1083.19,729.69 1094.004,718.876 C1104.817,708.063 1121.87,707.583 1132.092,717.805 C1142.313,728.027 1141.834,745.079 1131.02,755.893 C1120.207,766.706 1103.154,767.186 1092.932,756.964 z" fill="#3C3C3C"/>
|
||||
<path d="M1092.932,756.964 C1082.711,746.742 1083.19,729.69 1094.004,718.876 C1104.817,708.063 1121.87,707.583 1132.092,717.805 C1142.313,728.027 1141.834,745.079 1131.02,755.893 C1120.207,766.706 1103.154,767.186 1092.932,756.964 z" fill-opacity="0" stroke="#3C3C3C" stroke-width="7.536"/>
|
||||
</g>
|
||||
<path d="M1088.927,743.827 C1078.481,733.381 1076.562,718.364 1084.641,710.285 C1092.72,702.206 1107.737,704.125 1118.183,714.571 C1128.628,725.017 1130.547,740.034 1122.469,748.113 C1114.39,756.191 1099.373,754.273 1088.927,743.827 z" fill="#FFFFFF" id="path3226-9-7"/>
|
||||
<path d="M1170.308,742.065 L1204.594,776.351 L1154.594,826.351 L1120.308,792.065 z" fill="#FFFFFF" id="rect4027-6"/>
|
||||
<g id="rect3953-82-4-1-8">
|
||||
<path d="M1173.259,789.595 L1157.449,773.785 L1206.83,724.403 L1222.641,740.213 z" fill="#3C3C3C"/>
|
||||
<path d="M1173.259,789.595 L1157.449,773.785 L1206.83,724.403 L1222.641,740.213 z" fill-opacity="0" stroke="#3C3C3C" stroke-width="11.053"/>
|
||||
</g>
|
||||
<g id="rect3953-82-4-1-7-9">
|
||||
<path d="M1120.773,831.384 L1116.402,827.013 L1169.345,774.071 L1173.716,778.442 z" fill="#3C3C3C"/>
|
||||
<path d="M1120.773,831.384 L1116.402,827.013 L1169.345,774.071 L1173.716,778.442 z" fill-opacity="0" stroke="#3C3C3C" stroke-width="6.017"/>
|
||||
</g>
|
||||
<g id="rect3182-2">
|
||||
<path d="M1110.055,819.191 L1095.051,853.419 L1129.302,838.438 L1130.827,834.968 L1113.525,817.666 L1110.055,819.191 z" fill="#3C3C3C"/>
|
||||
<path d="M1110.055,819.191 L1095.051,853.419 L1129.302,838.438 L1130.827,834.968 L1113.525,817.666 L1110.055,819.191 z" fill-opacity="0" stroke="#3C3C3C" stroke-width="4.636"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="path2991-7-1-4-1">
|
||||
<path d="M148.345,1166.648 C148.345,1204.663 117.528,1235.481 79.512,1235.481 C41.496,1235.481 10.679,1204.663 10.679,1166.648 C10.679,1128.632 41.496,1097.815 79.512,1097.815 C117.528,1097.815 148.345,1128.632 148.345,1166.648 z" fill="#BEBEFF"/>
|
||||
<path d="M148.345,1166.648 C148.345,1204.663 117.528,1235.481 79.512,1235.481 C41.496,1235.481 10.679,1204.663 10.679,1166.648 C10.679,1128.632 41.496,1097.815 79.512,1097.815 C117.528,1097.815 148.345,1128.632 148.345,1166.648 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.66"/>
|
||||
</g>
|
||||
<g id="path2993-4-5-8-7">
|
||||
<path d="M137.622,1167.138 C137.622,1198.924 111.855,1224.691 80.069,1224.691 C48.284,1224.691 22.516,1198.924 22.516,1167.138 C22.516,1135.353 48.284,1109.586 80.069,1109.586 C111.855,1109.586 137.622,1135.353 137.622,1167.138 z" fill="#FFFFFF"/>
|
||||
<path d="M137.622,1167.138 C137.622,1198.924 111.855,1224.691 80.069,1224.691 C48.284,1224.691 22.516,1198.924 22.516,1167.138 C22.516,1135.353 48.284,1109.586 80.069,1109.586 C111.855,1109.586 137.622,1135.353 137.622,1167.138 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="0.552"/>
|
||||
</g>
|
||||
<path d="M74.371,1148.512 L88.038,1148.512 L88.038,1214.118 L74.371,1214.118 z" fill="#BEBEFA" id="rect2995-0-2-8-4"/>
|
||||
<g id="rect4046-3-4-0">
|
||||
<path d="M52.879,1140.449 L10.184,1116.547 L17.247,1153.482 L52.879,1140.449 z" fill="#FFFFFF"/>
|
||||
<path d="M52.879,1140.449 L10.184,1116.547 L17.247,1153.482 L52.879,1140.449 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="1.93" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect4046-5-9">
|
||||
<path d="M45.846,1135.913 L17.031,1119.785 L21.65,1144.97 L45.846,1135.913 z" fill="#BEBEFF"/>
|
||||
<path d="M45.846,1135.913 L17.031,1119.785 L21.65,1144.97 L45.846,1135.913 z" fill-opacity="0" stroke="#BEBEFF" stroke-width="1.313" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect3075-11-4">
|
||||
<path d="M236.5,1107.334 C236.333,1121.99 236.166,1136.646 236,1151.302 C238.198,1151.324 240.395,1151.344 242.593,1151.365 C241.041,1152.928 239.489,1154.49 237.937,1156.053 C258.646,1176.761 279.354,1197.469 300.062,1218.178 C315.468,1202.771 330.875,1187.365 346.281,1171.959 C325.573,1151.24 304.864,1130.521 284.156,1109.803 C282.427,1111.542 280.698,1113.282 278.968,1115.021 C278.99,1112.626 279.01,1110.23 279.031,1107.834 C264.854,1107.667 250.677,1107.501 236.5,1107.334 z" fill="#FFFFFF"/>
|
||||
<path d="M236.5,1107.334 C236.333,1121.99 236.166,1136.646 236,1151.302 C238.198,1151.324 240.395,1151.344 242.593,1151.365 C241.041,1152.928 239.489,1154.49 237.937,1156.053 C258.646,1176.761 279.354,1197.469 300.062,1218.178 C315.468,1202.771 330.875,1187.365 346.281,1171.959 C325.573,1151.24 304.864,1130.521 284.156,1109.803 C282.427,1111.542 280.698,1113.282 278.968,1115.021 C278.99,1112.626 279.01,1110.23 279.031,1107.834 C264.854,1107.667 250.677,1107.501 236.5,1107.334 z" fill-opacity="0" stroke="#BEBEFA" stroke-width="9" stroke-linecap="round"/>
|
||||
</g>
|
||||
<path d="M63.42,1174.15 L63.42,1160.483 L129.026,1160.483 L129.026,1174.15 z" fill="#BEBEFA" id="rect2995-0-2-8-6-8"/>
|
||||
<g id="rect3075-11-7-8">
|
||||
<path d="M243.32,1114.315 C243.193,1125.469 243.067,1136.623 242.94,1147.777 C244.613,1147.793 246.285,1147.808 247.958,1147.824 C246.777,1149.013 245.595,1150.203 244.414,1151.392 C260.174,1167.151 275.934,1182.911 291.693,1198.671 C303.418,1186.946 315.143,1175.222 326.867,1163.497 C311.108,1147.729 295.348,1131.962 279.588,1116.194 C278.272,1117.518 276.956,1118.842 275.64,1120.166 C275.656,1118.342 275.671,1116.519 275.688,1114.696 C264.899,1114.569 254.109,1114.442 243.32,1114.315 z" fill="#FFFFFF"/>
|
||||
<path d="M243.32,1114.315 C243.193,1125.469 243.067,1136.623 242.94,1147.777 C244.613,1147.793 246.285,1147.808 247.958,1147.824 C246.777,1149.013 245.595,1150.203 244.414,1151.392 C260.174,1167.151 275.934,1182.911 291.693,1198.671 C303.418,1186.946 315.143,1175.222 326.867,1163.497 C311.108,1147.729 295.348,1131.962 279.588,1116.194 C278.272,1117.518 276.956,1118.842 275.64,1120.166 C275.656,1118.342 275.671,1116.519 275.688,1114.696 C264.899,1114.569 254.109,1114.442 243.32,1114.315 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="6.849" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="path3100-2-2">
|
||||
<path d="M270.685,1125.928 C275.192,1130.435 275.192,1137.741 270.685,1142.248 C266.179,1146.754 258.872,1146.754 254.366,1142.248 C249.859,1137.741 249.859,1130.435 254.366,1125.928 C258.872,1121.422 266.179,1121.422 270.685,1125.928 z" fill="#FFFFFF"/>
|
||||
<path d="M270.685,1125.928 C275.192,1130.435 275.192,1137.741 270.685,1142.248 C266.179,1146.754 258.872,1146.754 254.366,1142.248 C249.859,1137.741 249.859,1130.435 254.366,1125.928 C258.872,1121.422 266.179,1121.422 270.685,1125.928 z" fill-opacity="0" stroke="#BEBEFA" stroke-width="7.585" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g id="rect4114-4">
|
||||
<path d="M270.782,1164.347 L291.825,1143.305 L320.949,1172.429 L299.906,1193.471 z" fill="#FFFFFF"/>
|
||||
<path d="M270.782,1164.347 L291.825,1143.305 L320.949,1172.429 L299.906,1193.471 z" fill-opacity="0" stroke="#BEBEFF" stroke-width="7.133"/>
|
||||
</g>
|
||||
<path d="M444.846,1211.217 C444.846,1211.217 447.118,1192.364 476.287,1187.796 C486.256,1186.234 508.495,1182.331 508.495,1159.69" fill-opacity="0" stroke="#BEBEFF" stroke-width="17.059" id="path3207-5"/>
|
||||
<g id="rect3818-4-8-4-5">
|
||||
<path d="M443.438,1135.819 L445.587,1135.819 L445.587,1204.643 L443.438,1204.643 z" fill="#FFFFFF"/>
|
||||
<path d="M443.438,1135.819 L445.587,1135.819 L445.587,1204.643 L443.438,1204.643 z" fill-opacity="0" stroke="#BEBEFA" stroke-width="15"/>
|
||||
</g>
|
||||
<g id="path3795-4-8-7-8-1">
|
||||
<path d="M459.278,1221.972 C459.278,1229.85 452.829,1236.236 444.873,1236.236 C436.917,1236.236 430.467,1229.85 430.467,1221.972 C430.467,1214.094 436.917,1207.708 444.873,1207.708 C452.829,1207.708 459.278,1214.094 459.278,1221.972 z" fill="#FFFFFF"/>
|
||||
<path d="M459.278,1221.972 C459.278,1229.85 452.829,1236.236 444.873,1236.236 C436.917,1236.236 430.467,1229.85 430.467,1221.972 C430.467,1214.094 436.917,1207.708 444.873,1207.708 C452.829,1207.708 459.278,1214.094 459.278,1221.972 z" fill-opacity="0" stroke="#BEBEFF" stroke-width="7.989"/>
|
||||
</g>
|
||||
<g id="path3795-8-4-8-7">
|
||||
<path d="M458.265,1122.752 C458.265,1130.63 451.815,1137.016 443.859,1137.016 C435.903,1137.016 429.453,1130.63 429.453,1122.752 C429.453,1114.874 435.903,1108.488 443.859,1108.488 C451.815,1108.488 458.265,1114.874 458.265,1122.752 z" fill="#FFFFFF"/>
|
||||
<path d="M458.265,1122.752 C458.265,1130.63 451.815,1137.016 443.859,1137.016 C435.903,1137.016 429.453,1130.63 429.453,1122.752 C429.453,1114.874 435.903,1108.488 443.859,1108.488 C451.815,1108.488 458.265,1114.874 458.265,1122.752 z" fill-opacity="0" stroke="#BEBEFF" stroke-width="7.989"/>
|
||||
</g>
|
||||
<g id="path3795-8-4-8-2-1">
|
||||
<path d="M524.832,1153.794 C524.832,1161.672 518.383,1168.058 510.427,1168.058 C502.471,1168.058 496.021,1161.672 496.021,1153.794 C496.021,1145.916 502.471,1139.53 510.427,1139.53 C518.383,1139.53 524.832,1145.916 524.832,1153.794 z" fill="#FFFFFF"/>
|
||||
<path d="M524.832,1153.794 C524.832,1161.672 518.383,1168.058 510.427,1168.058 C502.471,1168.058 496.021,1161.672 496.021,1153.794 C496.021,1145.916 502.471,1139.53 510.427,1139.53 C518.383,1139.53 524.832,1145.916 524.832,1153.794 z" fill-opacity="0" stroke="#BEBEFF" stroke-width="7.989"/>
|
||||
</g>
|
||||
<g id="g3992">
|
||||
<path d="M1704.368,51.875 L1720.533,68.041 L1646.34,142.233 L1630.175,126.068 z" fill="#3C3C3C" id="rect2995-0-8-4-1"/>
|
||||
<path d="M1599.987,96.742 L1615.64,81.088 L1660.886,126.335 L1645.233,141.988 z" fill="#3C3C3C" id="rect2995-0-8-4-1-4"/>
|
||||
</g>
|
||||
<g id="g4112">
|
||||
<path d="M1468.322,48.548 L1484.487,64.713 L1410.294,138.906 L1394.129,122.741 z" fill="#A0A0A0" id="rect2995-0-8-4-1-5"/>
|
||||
<path d="M1363.94,93.415 L1379.593,77.761 L1424.84,123.008 L1409.187,138.661 z" fill="#A0A0A0" id="rect2995-0-8-4-1-4-5"/>
|
||||
</g>
|
||||
<path d="M1454.823,275.861 L1382.568,332.264 L1402.021,272.893 z" fill="#B3B3B3" id="path3894-1-1"/>
|
||||
<g id="rect3088-5-5-7">
|
||||
<path d="M1372.165,222.803 L1487.252,222.803 L1487.252,281.66 L1372.165,281.66 z" fill="#B3B3B3"/>
|
||||
<path d="M1372.165,222.803 L1487.252,222.803 L1487.252,281.66 L1372.165,281.66 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="32.985" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g id="rect4170">
|
||||
<path d="M1405.281,364.617 L1428.986,364.617 L1428.986,500.829 L1405.281,500.829 z" fill="#DCDCDC"/>
|
||||
<path d="M1405.281,364.617 L1428.986,364.617 L1428.986,500.829 L1405.281,500.829 z" fill-opacity="0" stroke="#DCDCDC" stroke-width="1.392" stroke-miterlimit="4.3"/>
|
||||
</g>
|
||||
<g id="rect4166">
|
||||
<path d="M1355.731,391.55 L1470.966,391.55 L1470.966,442.009 L1355.731,442.009 z" fill="#DCDCDC"/>
|
||||
<path d="M1355.731,391.55 L1470.966,391.55 L1470.966,442.009 L1355.731,442.009 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="9.98" stroke-miterlimit="4.3"/>
|
||||
</g>
|
||||
<g id="rect4174">
|
||||
<path d="M1465.955,397.292 L1486.652,417.137 L1465.955,436.982 L1445.258,417.137 z" fill="#DCDCDC"/>
|
||||
<path d="M1465.955,397.292 L1486.652,417.137 L1465.955,436.982 L1445.258,417.137 z" fill-opacity="0" stroke="#DCDCDC" stroke-width="0.942" stroke-miterlimit="4.3"/>
|
||||
</g>
|
||||
<g id="path4364">
|
||||
<path d="M1426.457,416.302 C1426.457,421.48 1422.26,425.677 1417.082,425.677 C1411.904,425.677 1407.706,421.48 1407.706,416.302 C1407.706,411.124 1411.904,406.926 1417.082,406.926 C1422.26,406.926 1426.457,411.124 1426.457,416.302 z" fill="#FFFFE6"/>
|
||||
<path d="M1426.457,416.302 C1426.457,421.48 1422.26,425.677 1417.082,425.677 C1411.904,425.677 1407.706,421.48 1407.706,416.302 C1407.706,411.124 1411.904,406.926 1417.082,406.926 C1422.26,406.926 1426.457,411.124 1426.457,416.302 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="8.603" stroke-miterlimit="4.3"/>
|
||||
</g>
|
||||
<path d="M1684.796,276.068 L1612.54,332.47 L1631.994,273.099 z" fill="#DCDCDC" id="path3894-1-1-1"/>
|
||||
<g id="rect3088-5-5-7-7">
|
||||
<path d="M1602.137,223.01 L1717.224,223.01 L1717.224,281.866 L1602.137,281.866 z" fill="#DCDCDC"/>
|
||||
<path d="M1602.137,223.01 L1717.224,223.01 L1717.224,281.866 L1602.137,281.866 z" fill-opacity="0" stroke="#DCDCDC" stroke-width="32.985" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g id="rect3220">
|
||||
<path d="M42.774,1300.197 L111.779,1300.197 L111.779,1369.202 L42.774,1369.202 z" fill="#3C3C3C"/>
|
||||
<path d="M42.774,1300.197 L111.779,1300.197 L111.779,1369.202 L42.774,1369.202 z" fill-opacity="0" stroke="#888888" stroke-width="48.237" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<path d="M112.129,1315.562 L57.129,1370.562 L42.843,1356.276 L97.843,1301.276 z" fill="#FFFFFF" id="rect3998-1"/>
|
||||
<path d="M56.4,1300.576 L111.4,1355.576 L97.114,1369.862 L42.114,1314.862 z" fill="#FFFFFF" id="rect3998-1-7"/>
|
||||
<g id="rect3220-4">
|
||||
<path d="M235.208,1299.692 L304.213,1299.692 L304.213,1368.697 L235.208,1368.697 z" fill="#0088CC"/>
|
||||
<path d="M235.208,1299.692 L304.213,1299.692 L304.213,1368.697 L235.208,1368.697 z" fill-opacity="0" stroke="#0088CC" stroke-width="48.237" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<path d="M304.563,1315.057 L249.563,1370.057 L235.277,1355.771 L290.277,1300.771 z" fill="#FFFFFF" id="rect3998-1-0"/>
|
||||
<path d="M248.834,1300.071 L303.834,1355.071 L289.549,1369.357 L234.549,1314.357 z" fill="#FFFFFF" id="rect3998-1-7-9"/>
|
||||
<g id="path3795-4-8-4">
|
||||
<path d="M1412.392,641.966 C1412.392,650.212 1405.641,656.896 1397.313,656.896 C1388.985,656.896 1382.235,650.212 1382.235,641.966 C1382.235,633.72 1388.985,627.035 1397.313,627.035 C1405.641,627.035 1412.392,633.72 1412.392,641.966 z" fill="#A0A0A0"/>
|
||||
<path d="M1412.392,641.966 C1412.392,650.212 1405.641,656.896 1397.313,656.896 C1388.985,656.896 1382.235,650.212 1382.235,641.966 C1382.235,633.72 1388.985,627.035 1397.313,627.035 C1405.641,627.035 1412.392,633.72 1412.392,641.966 z" fill="#A0A0A0"/>
|
||||
</g>
|
||||
<path d="M1396.792,592.168 C1426.908,592.168 1450.613,610.989 1450.613,639.54" fill-opacity="0" stroke="#A0A0A0" stroke-width="20" stroke-linecap="round"/>
|
||||
<path d="M1397.792,545.653 C1453.613,544.493 1499.627,588.735 1499.627,636.54" fill-opacity="0" stroke="#A0A0A0" stroke-width="20" stroke-linecap="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 78 KiB |
17
etc/pom.xml
Normal file
17
etc/pom.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>jp.sf.amateras</groupId>
|
||||
<artifactId>gitbucket-assembly</artifactId>
|
||||
<version>0.0.1</version>
|
||||
<build>
|
||||
<extensions>
|
||||
<extension>
|
||||
<groupId>org.apache.maven.wagon</groupId>
|
||||
<artifactId>wagon-ssh</artifactId>
|
||||
<version>1.0-beta-6</version>
|
||||
</extension>
|
||||
</extensions>
|
||||
</build>
|
||||
</project>
|
||||
Binary file not shown.
1
project/build.properties
Normal file
1
project/build.properties
Normal file
@@ -0,0 +1 @@
|
||||
sbt.version=0.13.8
|
||||
@@ -1,43 +1,78 @@
|
||||
import sbt._
|
||||
import Keys._
|
||||
import org.scalatra.sbt._
|
||||
import org.scalatra.sbt.PluginKeys._
|
||||
import twirl.sbt.TwirlPlugin._
|
||||
import com.typesafe.sbteclipse.plugin.EclipsePlugin.EclipseKeys
|
||||
import play.twirl.sbt.SbtTwirl
|
||||
import play.twirl.sbt.Import.TwirlKeys._
|
||||
import sbtassembly._
|
||||
import sbtassembly.AssemblyKeys._
|
||||
|
||||
object MyBuild extends Build {
|
||||
val Organization = "jp.sf.amateras"
|
||||
val Organization = "gitbucket"
|
||||
val Name = "gitbucket"
|
||||
val Version = "0.0.1"
|
||||
val ScalaVersion = "2.10.1"
|
||||
val ScalatraVersion = "2.2.0"
|
||||
val Version = "3.0.0"
|
||||
val ScalaVersion = "2.11.6"
|
||||
val ScalatraVersion = "2.3.1"
|
||||
|
||||
lazy val project = Project (
|
||||
"gitbucket",
|
||||
file("."),
|
||||
settings = Defaults.defaultSettings ++ ScalatraPlugin.scalatraWithJRebel ++ Seq(
|
||||
organization := Organization,
|
||||
name := Name,
|
||||
version := Version,
|
||||
scalaVersion := ScalaVersion,
|
||||
resolvers += Classpaths.typesafeReleases,
|
||||
libraryDependencies ++= Seq(
|
||||
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "3.0.0.201306101825-r",
|
||||
"org.apache.commons" % "commons-io" % "1.3.2",
|
||||
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
||||
"org.scalatra" %% "scalatra-specs2" % ScalatraVersion % "test",
|
||||
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
||||
"org.json4s" %% "json4s-jackson" % "3.2.4",
|
||||
"commons-io" % "commons-io" % "2.4",
|
||||
"org.pegdown" % "pegdown" % "1.3.0",
|
||||
"org.apache.commons" % "commons-compress" % "1.5",
|
||||
"com.typesafe.slick" %% "slick" % "1.0.1",
|
||||
"com.h2database" % "h2" % "1.3.171",
|
||||
"ch.qos.logback" % "logback-classic" % "1.0.6" % "runtime",
|
||||
"org.eclipse.jetty" % "jetty-webapp" % "8.1.8.v20121106" % "container",
|
||||
"org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container;provided;test" artifacts (Artifact("javax.servlet", "jar", "jar"))
|
||||
),
|
||||
EclipseKeys.withSource := true
|
||||
) ++ seq(Twirl.settings: _*)
|
||||
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/"
|
||||
),
|
||||
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.1.0",
|
||||
"commons-io" % "commons-io" % "2.4",
|
||||
"org.pegdown" % "pegdown" % "1.4.1", // 1.4.2 has incompatible APi changes
|
||||
"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" % "0.11.0",
|
||||
"com.typesafe.slick" %% "slick" % "2.1.0",
|
||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||
"com.h2database" % "h2" % "1.4.186",
|
||||
// "ch.qos.logback" % "logback-classic" % "1.0.13" % "runtime",
|
||||
"org.eclipse.jetty" % "jetty-webapp" % "8.1.16.v20140903" % "container;provided",
|
||||
"org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container;provided;test" artifacts Artifact("javax.servlet", "jar", "jar"),
|
||||
"junit" % "junit" % "4.12" % "test",
|
||||
"com.mchange" % "c3p0" % "0.9.5",
|
||||
"com.typesafe" % "config" % "1.2.1",
|
||||
"com.typesafe.play" %% "twirl-compiler" % "1.0.4"
|
||||
),
|
||||
play.twirl.sbt.Import.TwirlKeys.templateImports += "gitbucket.core._",
|
||||
EclipseKeys.withSource := true,
|
||||
javacOptions in compile ++= Seq("-target", "7", "-source", "7"),
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.1.2")
|
||||
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
||||
|
||||
addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.2.0")
|
||||
|
||||
addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.2.0")
|
||||
|
||||
addSbtPlugin("io.spray" % "sbt-twirl" % "0.6.1")
|
||||
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.4.0")
|
||||
addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.6.0")
|
||||
addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.3.5")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.4")
|
||||
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.8")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.12.0")
|
||||
|
||||
Binary file not shown.
BIN
sbt-launch-0.13.8.jar
Normal file
BIN
sbt-launch-0.13.8.jar
Normal file
Binary file not shown.
2
sbt.bat
2
sbt.bat
@@ -1,2 +1,2 @@
|
||||
set SCRIPT_DIR=%~dp0
|
||||
java -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -jar "%SCRIPT_DIR%\sbt-launch-0.12.3.jar" %*
|
||||
java -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" %*
|
||||
|
||||
2
sbt.sh
Executable file
2
sbt.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
java -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 "$@"
|
||||
91
src/main/java/JettyLauncher.java
Normal file
91
src/main/java/JettyLauncher.java
Normal file
@@ -0,0 +1,91 @@
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.nio.SelectChannelConnector;
|
||||
import org.eclipse.jetty.webapp.WebAppContext;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.security.ProtectionDomain;
|
||||
|
||||
public class JettyLauncher {
|
||||
public static void main(String[] args) throws Exception {
|
||||
String host = null;
|
||||
int port = 8080;
|
||||
String contextPath = "/";
|
||||
boolean forceHttps = false;
|
||||
|
||||
for(String arg: args) {
|
||||
if(arg.startsWith("--") && arg.contains("=")) {
|
||||
String[] dim = arg.split("=");
|
||||
if(dim.length >= 2) {
|
||||
if(dim[0].equals("--host")) {
|
||||
host = dim[1];
|
||||
} else if(dim[0].equals("--port")) {
|
||||
port = Integer.parseInt(dim[1]);
|
||||
} else if(dim[0].equals("--prefix")) {
|
||||
contextPath = dim[1];
|
||||
} else if(dim[0].equals("--gitbucket.home")){
|
||||
System.setProperty("gitbucket.home", dim[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Server server = new Server();
|
||||
|
||||
SelectChannelConnector connector = new SelectChannelConnector();
|
||||
if(host != null) {
|
||||
connector.setHost(host);
|
||||
}
|
||||
connector.setMaxIdleTime(1000 * 60 * 60);
|
||||
connector.setSoLingerTime(-1);
|
||||
connector.setPort(port);
|
||||
server.addConnector(connector);
|
||||
|
||||
WebAppContext context = new WebAppContext();
|
||||
|
||||
File tmpDir = new File(getGitBucketHome(), "tmp");
|
||||
if(tmpDir.exists()){
|
||||
deleteDirectory(tmpDir);
|
||||
}
|
||||
tmpDir.mkdirs();
|
||||
context.setTempDirectory(tmpDir);
|
||||
|
||||
ProtectionDomain domain = JettyLauncher.class.getProtectionDomain();
|
||||
URL location = domain.getCodeSource().getLocation();
|
||||
|
||||
context.setContextPath(contextPath);
|
||||
context.setDescriptor(location.toExternalForm() + "/WEB-INF/web.xml");
|
||||
context.setServer(server);
|
||||
context.setWar(location.toExternalForm());
|
||||
if (forceHttps) {
|
||||
context.setInitParameter("org.scalatra.ForceHttps", "true");
|
||||
}
|
||||
|
||||
server.setHandler(context);
|
||||
server.start();
|
||||
server.join();
|
||||
}
|
||||
|
||||
private static File getGitBucketHome(){
|
||||
String home = System.getProperty("gitbucket.home");
|
||||
if(home != null && home.length() > 0){
|
||||
return new File(home);
|
||||
}
|
||||
home = System.getenv("GITBUCKET_HOME");
|
||||
if(home != null && home.length() > 0){
|
||||
return new File(home);
|
||||
}
|
||||
return new File(System.getProperty("user.home"), ".gitbucket");
|
||||
}
|
||||
|
||||
private static void deleteDirectory(File dir){
|
||||
for(File file: dir.listFiles()){
|
||||
if(file.isFile()){
|
||||
file.delete();
|
||||
} else if(file.isDirectory()){
|
||||
deleteDirectory(file);
|
||||
}
|
||||
}
|
||||
dir.delete();
|
||||
}
|
||||
}
|
||||
93
src/main/java/gitbucket/core/util/PatchUtil.java
Normal file
93
src/main/java/gitbucket/core/util/PatchUtil.java
Normal file
@@ -0,0 +1,93 @@
|
||||
package gitbucket.core.util;
|
||||
|
||||
import org.eclipse.jgit.api.errors.PatchApplyException;
|
||||
import org.eclipse.jgit.diff.RawText;
|
||||
import org.eclipse.jgit.internal.JGitText;
|
||||
import org.eclipse.jgit.patch.FileHeader;
|
||||
import org.eclipse.jgit.patch.HunkHeader;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class helps to apply patch. Most of these code came from {@link org.eclipse.jgit.api.ApplyCommand}.
|
||||
*/
|
||||
public class PatchUtil {
|
||||
|
||||
public static String apply(String source, String patch, FileHeader fh)
|
||||
throws IOException, PatchApplyException {
|
||||
RawText rt = new RawText(source.getBytes("UTF-8"));
|
||||
List<String> oldLines = new ArrayList<String>(rt.size());
|
||||
for (int i = 0; i < rt.size(); i++)
|
||||
oldLines.add(rt.getString(i));
|
||||
List<String> newLines = new ArrayList<String>(oldLines);
|
||||
for (HunkHeader hh : fh.getHunks()) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(patch.getBytes("UTF-8"), hh.getStartOffset(), hh.getEndOffset() - hh.getStartOffset());
|
||||
RawText hrt = new RawText(out.toByteArray());
|
||||
List<String> hunkLines = new ArrayList<String>(hrt.size());
|
||||
for (int i = 0; i < hrt.size(); i++)
|
||||
hunkLines.add(hrt.getString(i));
|
||||
int pos = 0;
|
||||
for (int j = 1; j < hunkLines.size(); j++) {
|
||||
String hunkLine = hunkLines.get(j);
|
||||
switch (hunkLine.charAt(0)) {
|
||||
case ' ':
|
||||
if (!newLines.get(hh.getNewStartLine() - 1 + pos).equals(
|
||||
hunkLine.substring(1))) {
|
||||
throw new PatchApplyException(MessageFormat.format(
|
||||
JGitText.get().patchApplyException, hh));
|
||||
}
|
||||
pos++;
|
||||
break;
|
||||
case '-':
|
||||
if (!newLines.get(hh.getNewStartLine() - 1 + pos).equals(
|
||||
hunkLine.substring(1))) {
|
||||
throw new PatchApplyException(MessageFormat.format(
|
||||
JGitText.get().patchApplyException, hh));
|
||||
}
|
||||
newLines.remove(hh.getNewStartLine() - 1 + pos);
|
||||
break;
|
||||
case '+':
|
||||
newLines.add(hh.getNewStartLine() - 1 + pos,
|
||||
hunkLine.substring(1));
|
||||
pos++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isNoNewlineAtEndOfFile(fh))
|
||||
newLines.add(""); //$NON-NLS-1$
|
||||
if (!rt.isMissingNewlineAtEnd())
|
||||
oldLines.add(""); //$NON-NLS-1$
|
||||
if (!isChanged(oldLines, newLines))
|
||||
return null; // don't touch the file
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String l : newLines) {
|
||||
// don't bother handling line endings - if it was windows, the \r is
|
||||
// still there!
|
||||
sb.append(l).append('\n');
|
||||
}
|
||||
sb.deleteCharAt(sb.length() - 1);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static boolean isChanged(List<String> ol, List<String> nl) {
|
||||
if (ol.size() != nl.size())
|
||||
return true;
|
||||
for (int i = 0; i < ol.size(); i++)
|
||||
if (!ol.get(i).equals(nl.get(i)))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isNoNewlineAtEndOfFile(FileHeader fh) {
|
||||
HunkHeader lastHunk = fh.getHunks().get(fh.getHunks().size() - 1);
|
||||
RawText lhrt = new RawText(lastHunk.getBuffer());
|
||||
return lhrt.getString(lhrt.size() - 1).equals(
|
||||
"\\ No newline at end of file"); //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
6
src/main/resources/database.conf
Normal file
6
src/main/resources/database.conf
Normal file
@@ -0,0 +1,6 @@
|
||||
db {
|
||||
driver = "org.h2.Driver"
|
||||
url = "jdbc:h2:${DatabaseHome};MVCC=true"
|
||||
user = "sa"
|
||||
password = "sa"
|
||||
}
|
||||
@@ -1,4 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<logger name="scala.slick" level="INFO" />
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
|
||||
<!--
|
||||
<logger name="service.WebHookService" level="DEBUG" />
|
||||
<logger name="servlet" level="DEBUG" />
|
||||
-->
|
||||
</configuration>
|
||||
BIN
src/main/resources/noimage.png
Normal file
BIN
src/main/resources/noimage.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
@@ -1,135 +1,135 @@
|
||||
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/takezoe/gitbucket',
|
||||
SYSDATE,
|
||||
SYSDATE,
|
||||
NULL
|
||||
);
|
||||
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/takezoe/gitbucket',
|
||||
SYSDATE,
|
||||
SYSDATE,
|
||||
NULL
|
||||
);
|
||||
|
||||
11
src/main/resources/update/1_12.sql
Normal file
11
src/main/resources/update/1_12.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
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
src/main/resources/update/1_13.sql
Normal file
1
src/main/resources/update/1_13.sql
Normal file
@@ -0,0 +1 @@
|
||||
DROP TABLE COMMIT_LOG;
|
||||
24
src/main/resources/update/1_2.sql
Normal file
24
src/main/resources/update/1_2.sql
Normal file
@@ -0,0 +1,24 @@
|
||||
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);
|
||||
|
||||
8
src/main/resources/update/1_3.sql
Normal file
8
src/main/resources/update/1_3.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
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';
|
||||
24
src/main/resources/update/1_4.sql
Normal file
24
src/main/resources/update/1_4.sql
Normal file
@@ -0,0 +1,24 @@
|
||||
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);
|
||||
21
src/main/resources/update/1_5.sql
Normal file
21
src/main/resources/update/1_5.sql
Normal file
@@ -0,0 +1,21 @@
|
||||
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;
|
||||
8
src/main/resources/update/1_6.sql
Normal file
8
src/main/resources/update/1_6.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
CREATE TABLE WEB_HOOK (
|
||||
USER_NAME VARCHAR(100) NOT NULL,
|
||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
||||
URL VARCHAR(200) NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE WEB_HOOK ADD CONSTRAINT IDX_WEB_HOOK_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, URL);
|
||||
ALTER TABLE WEB_HOOK ADD CONSTRAINT IDX_WEB_HOOK_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
||||
5
src/main/resources/update/1_7.sql
Normal file
5
src/main/resources/update/1_7.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
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
src/main/resources/update/1_8.sql
Normal file
1
src/main/resources/update/1_8.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE ACCOUNT ADD COLUMN REMOVED BOOLEAN DEFAULT FALSE;
|
||||
6
src/main/resources/update/2_3.sql
Normal file
6
src/main/resources/update/2_3.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
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);
|
||||
18
src/main/resources/update/2_7.sql
Normal file
18
src/main/resources/update/2_7.sql
Normal file
@@ -0,0 +1,18 @@
|
||||
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
src/main/resources/update/2_8.sql
Normal file
1
src/main/resources/update/2_8.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE COMMIT_COMMENT ALTER COLUMN FILE_NAME NVARCHAR(260);
|
||||
42
src/main/resources/update/3_1.sql
Normal file
42
src/main/resources/update/3_1.sql
Normal file
@@ -0,0 +1,42 @@
|
||||
DROP TABLE IF EXISTS ACCESS_TOKEN;
|
||||
|
||||
CREATE TABLE ACCESS_TOKEN (
|
||||
ACCESS_TOKEN_ID INT NOT NULL AUTO_INCREMENT,
|
||||
TOKEN_HASH VARCHAR(40) NOT NULL,
|
||||
USER_NAME VARCHAR(100) NOT NULL,
|
||||
NOTE TEXT NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE ACCESS_TOKEN ADD CONSTRAINT IDX_ACCESS_TOKEN_PK PRIMARY KEY (ACCESS_TOKEN_ID);
|
||||
ALTER TABLE ACCESS_TOKEN ADD CONSTRAINT IDX_ACCESS_TOKEN_FK0 FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME)
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE ACCESS_TOKEN ADD CONSTRAINT IDX_ACCESS_TOKEN_TOKEN_HASH UNIQUE(TOKEN_HASH);
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS COMMIT_STATUS;
|
||||
CREATE TABLE COMMIT_STATUS(
|
||||
COMMIT_STATUS_ID INT AUTO_INCREMENT,
|
||||
USER_NAME VARCHAR(100) NOT NULL,
|
||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
||||
COMMIT_ID VARCHAR(40) NOT NULL,
|
||||
CONTEXT VARCHAR(255) NOT NULL, -- context is too long (maximum is 255 characters)
|
||||
STATE VARCHAR(10) NOT NULL, -- pending, success, error, or failure
|
||||
TARGET_URL VARCHAR(200),
|
||||
DESCRIPTION TEXT,
|
||||
CREATOR VARCHAR(100) NOT NULL,
|
||||
REGISTERED_DATE TIMESTAMP NOT NULL, -- CREATED_AT
|
||||
UPDATED_DATE TIMESTAMP NOT NULL -- UPDATED_AT
|
||||
);
|
||||
ALTER TABLE COMMIT_STATUS ADD CONSTRAINT IDX_COMMIT_STATUS_PK PRIMARY KEY (COMMIT_STATUS_ID);
|
||||
ALTER TABLE COMMIT_STATUS ADD CONSTRAINT IDX_COMMIT_STATUS_1
|
||||
UNIQUE (USER_NAME, REPOSITORY_NAME, COMMIT_ID, CONTEXT);
|
||||
ALTER TABLE COMMIT_STATUS ADD CONSTRAINT IDX_COMMIT_STATUS_FK1
|
||||
FOREIGN KEY (USER_NAME, REPOSITORY_NAME)
|
||||
REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME)
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE COMMIT_STATUS ADD CONSTRAINT IDX_COMMIT_STATUS_FK2
|
||||
FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME)
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE COMMIT_STATUS ADD CONSTRAINT IDX_COMMIT_STATUS_FK3
|
||||
FOREIGN KEY (CREATOR) REFERENCES ACCOUNT (USER_NAME)
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -1,25 +1,50 @@
|
||||
import app._
|
||||
import org.scalatra._
|
||||
|
||||
import gitbucket.core.controller._
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.servlet.{AccessTokenAuthenticationFilter, BasicAuthenticationFilter, TransactionFilter}
|
||||
import gitbucket.core.util.Directory
|
||||
|
||||
import java.util.EnumSet
|
||||
import javax.servlet._
|
||||
|
||||
import org.scalatra._
|
||||
|
||||
|
||||
class ScalatraBootstrap extends LifeCycle {
|
||||
override def init(context: ServletContext) {
|
||||
// Register TransactionFilter and BasicAuthenticationFilter at first
|
||||
context.addFilter("transactionFilter", new TransactionFilter)
|
||||
context.getFilterRegistration("transactionFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
||||
context.addFilter("basicAuthenticationFilter", new BasicAuthenticationFilter)
|
||||
context.getFilterRegistration("basicAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*")
|
||||
context.addFilter("accessTokenAuthenticationFilter", new AccessTokenAuthenticationFilter)
|
||||
context.getFilterRegistration("accessTokenAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/api/v3/*")
|
||||
// Register controllers
|
||||
context.mount(new AnonymousAccessController, "/*")
|
||||
|
||||
PluginRegistry().getControllers.foreach { case (controller, path) =>
|
||||
context.mount(controller, path)
|
||||
}
|
||||
|
||||
context.mount(new IndexController, "/")
|
||||
context.mount(new SignInController, "/*")
|
||||
context.mount(new SearchController, "/")
|
||||
context.mount(new FileUploadController, "/upload")
|
||||
context.mount(new DashboardController, "/*")
|
||||
context.mount(new UserManagementController, "/*")
|
||||
context.mount(new SystemSettingsController, "/*")
|
||||
context.mount(new CreateRepositoryController, "/*")
|
||||
context.mount(new AccountController, "/*")
|
||||
context.mount(new RepositoryViewerController, "/*")
|
||||
context.mount(new WikiController, "/*")
|
||||
context.mount(new LabelsController, "/*")
|
||||
context.mount(new MilestonesController, "/*")
|
||||
context.mount(new IssuesController, "/*")
|
||||
context.mount(new SettingsController, "/*")
|
||||
context.mount(new PullRequestsController, "/*")
|
||||
context.mount(new RepositorySettingsController, "/*")
|
||||
|
||||
val dir = new java.io.File(_root_.util.Directory.GitBucketHome)
|
||||
// Create GITBUCKET_HOME directory if it does not exist
|
||||
val dir = new java.io.File(Directory.GitBucketHome)
|
||||
if(!dir.exists){
|
||||
dir.mkdirs()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
package app
|
||||
|
||||
import service._
|
||||
import util.OneselfAuthenticator
|
||||
import util.StringUtil._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
class AccountController extends AccountControllerBase
|
||||
with SystemSettingsService with AccountService with RepositoryService with OneselfAuthenticator
|
||||
|
||||
trait AccountControllerBase extends ControllerBase {
|
||||
self: SystemSettingsService with AccountService with RepositoryService with OneselfAuthenticator =>
|
||||
|
||||
case class AccountNewForm(userName: String, password: String,mailAddress: String, url: Option[String])
|
||||
|
||||
case class AccountEditForm(password: Option[String], mailAddress: String, url: Option[String])
|
||||
|
||||
val newForm = mapping(
|
||||
"userName" -> trim(label("User name" , text(required, maxlength(100), identifier, uniqueUserName))),
|
||||
"password" -> trim(label("Password" , text(required, maxlength(20)))),
|
||||
"mailAddress" -> trim(label("Mail Address" , text(required, maxlength(100), uniqueMailAddress()))),
|
||||
"url" -> trim(label("URL" , optional(text(maxlength(200)))))
|
||||
)(AccountNewForm.apply)
|
||||
|
||||
val editForm = mapping(
|
||||
"password" -> trim(label("Password" , optional(text(maxlength(20))))),
|
||||
"mailAddress" -> trim(label("Mail Address" , text(required, maxlength(100), uniqueMailAddress("userName")))),
|
||||
"url" -> trim(label("URL" , optional(text(maxlength(200)))))
|
||||
)(AccountEditForm.apply)
|
||||
|
||||
/**
|
||||
* Displays user information.
|
||||
*/
|
||||
get("/:userName") {
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).map {
|
||||
account.html.info(_, getVisibleRepositories(userName, baseUrl, context.loginAccount.map(_.userName)))
|
||||
} getOrElse NotFound
|
||||
}
|
||||
|
||||
get("/:userName/_edit")(oneselfOnly {
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).map(x => account.html.edit(Some(x))) getOrElse NotFound
|
||||
})
|
||||
|
||||
post("/:userName/_edit", editForm)(oneselfOnly { form =>
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).map { account =>
|
||||
updateAccount(account.copy(
|
||||
password = form.password.map(encrypt).getOrElse(account.password),
|
||||
mailAddress = form.mailAddress,
|
||||
url = form.url))
|
||||
redirect("/%s".format(userName))
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
get("/register"){
|
||||
if(loadSystemSettings().allowAccountRegistration){
|
||||
account.html.edit(None)
|
||||
} else NotFound
|
||||
}
|
||||
|
||||
post("/register", newForm){ newForm =>
|
||||
if(loadSystemSettings().allowAccountRegistration){
|
||||
createAccount(newForm.userName, encrypt(newForm.password), newForm.mailAddress, false, newForm.url)
|
||||
redirect("/signin")
|
||||
} else NotFound
|
||||
}
|
||||
|
||||
// TODO Merge with UserManagementController
|
||||
private def uniqueUserName: Constraint = new Constraint(){
|
||||
def validate(name: String, value: String): Option[String] =
|
||||
getAccountByUserName(value).map { _ => "User already exists." }
|
||||
}
|
||||
|
||||
// TODO Merge with UserManagementController
|
||||
private def uniqueMailAddress(paramName: String = ""): Constraint = new Constraint(){
|
||||
def validate(name: String, value: String): Option[String] =
|
||||
getAccountByMailAddress(value)
|
||||
.filter { x => if(paramName.isEmpty) true else Some(x.userName) != params.get(paramName) }
|
||||
.map { _ => "Mail address is already registered." }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package app
|
||||
|
||||
import model.Account
|
||||
import util.Validations
|
||||
import org.scalatra._
|
||||
import org.scalatra.json._
|
||||
import org.json4s._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
/**
|
||||
* Provides generic features for ScalatraServlet implementations.
|
||||
*/
|
||||
abstract class ControllerBase extends ScalatraFilter
|
||||
with ClientSideValidationFormSupport with JacksonJsonSupport with Validations {
|
||||
|
||||
implicit val jsonFormats = DefaultFormats
|
||||
|
||||
/**
|
||||
* Returns the context object for the request.
|
||||
*/
|
||||
implicit def context: Context = Context(servletContext.getContextPath, LoginAccount, currentURL)
|
||||
|
||||
private def currentURL: String = {
|
||||
val queryString = request.getQueryString
|
||||
request.getRequestURI + (if(queryString != null) "?" + queryString else "")
|
||||
}
|
||||
|
||||
private def LoginAccount: Option[Account] = {
|
||||
session.get("LOGIN_ACCOUNT") match {
|
||||
case Some(x: Account) => Some(x)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
def ajaxGet(path : String)(action : => Any) : Route = {
|
||||
super.get(path){
|
||||
request.setAttribute("AJAX", "true")
|
||||
action
|
||||
}
|
||||
}
|
||||
|
||||
override def ajaxGet[T](path : String, form : MappingValueType[T])(action : T => Any) : Route = {
|
||||
super.ajaxGet(path, form){ form =>
|
||||
request.setAttribute("AJAX", "true")
|
||||
action(form)
|
||||
}
|
||||
}
|
||||
|
||||
def ajaxPost(path : String)(action : => Any) : Route = {
|
||||
super.post(path){
|
||||
request.setAttribute("AJAX", "true")
|
||||
action
|
||||
}
|
||||
}
|
||||
|
||||
override def ajaxPost[T](path : String, form : MappingValueType[T])(action : T => Any) : Route = {
|
||||
super.ajaxPost(path, form){ form =>
|
||||
request.setAttribute("AJAX", "true")
|
||||
action(form)
|
||||
}
|
||||
}
|
||||
|
||||
protected def NotFound() = {
|
||||
if(request.getAttribute("AJAX") == null){
|
||||
org.scalatra.NotFound(html.error("Not Found"))
|
||||
} else {
|
||||
org.scalatra.NotFound()
|
||||
}
|
||||
}
|
||||
|
||||
protected def Unauthorized()(implicit context: app.Context) = {
|
||||
if(request.getAttribute("AJAX") == null){
|
||||
if(context.loginAccount.isDefined){
|
||||
org.scalatra.Unauthorized(redirect("/"))
|
||||
} else {
|
||||
org.scalatra.Unauthorized(redirect("/signin?" + currentURL))
|
||||
}
|
||||
} else {
|
||||
org.scalatra.Unauthorized()
|
||||
}
|
||||
}
|
||||
|
||||
protected def baseUrl = {
|
||||
val url = request.getRequestURL.toString
|
||||
url.substring(0, url.length - (request.getRequestURI.length - request.getContextPath.length))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case class Context(path: String, loginAccount: Option[Account], currentUrl: String)
|
||||
@@ -1,99 +0,0 @@
|
||||
package app
|
||||
|
||||
import util.Directory._
|
||||
import util.UsersAuthenticator
|
||||
import service._
|
||||
import java.io.File
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib._
|
||||
import org.apache.commons.io._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
class CreateRepositoryController extends CreateRepositoryControllerBase
|
||||
with RepositoryService with AccountService with WikiService with LabelsService with UsersAuthenticator
|
||||
|
||||
/**
|
||||
* Creates new repository.
|
||||
*/
|
||||
trait CreateRepositoryControllerBase extends ControllerBase {
|
||||
self: RepositoryService with WikiService with LabelsService with UsersAuthenticator =>
|
||||
|
||||
case class RepositoryCreationForm(name: String, description: Option[String])
|
||||
|
||||
val form = mapping(
|
||||
"name" -> trim(label("Repository name", text(required, maxlength(40), identifier, unique))),
|
||||
"description" -> trim(label("Description" , optional(text())))
|
||||
)(RepositoryCreationForm.apply)
|
||||
|
||||
/**
|
||||
* Show the new repository form.
|
||||
*/
|
||||
get("/new")(usersOnly {
|
||||
html.newrepo()
|
||||
})
|
||||
|
||||
/**
|
||||
* Create new repository.
|
||||
*/
|
||||
post("/new", form)(usersOnly { form =>
|
||||
val loginUserName = context.loginAccount.get.userName
|
||||
|
||||
// Insert to the database at first
|
||||
createRepository(form.name, loginUserName, form.description)
|
||||
|
||||
// Insert default labels
|
||||
createLabel(loginUserName, form.name, "bug", "fc2929")
|
||||
createLabel(loginUserName, form.name, "duplicate", "cccccc")
|
||||
createLabel(loginUserName, form.name, "enhancement", "84b6eb")
|
||||
createLabel(loginUserName, form.name, "invalid", "e6e6e6")
|
||||
createLabel(loginUserName, form.name, "question", "cc317c")
|
||||
createLabel(loginUserName, form.name, "wontfix", "ffffff")
|
||||
|
||||
// Create the actual repository
|
||||
val gitdir = getRepositoryDir(loginUserName, form.name)
|
||||
val repository = new RepositoryBuilder().setGitDir(gitdir).setBare.build
|
||||
|
||||
repository.create
|
||||
|
||||
val config = repository.getConfig
|
||||
config.setBoolean("http", null, "receivepack", true)
|
||||
config.save
|
||||
|
||||
val tmpdir = getInitRepositoryDir(loginUserName, form.name)
|
||||
try {
|
||||
// Clone the repository
|
||||
Git.cloneRepository.setURI(gitdir.toURI.toString).setDirectory(tmpdir).call
|
||||
|
||||
// Create README.md
|
||||
FileUtils.writeStringToFile(new File(tmpdir, "README.md"),
|
||||
if(form.description.nonEmpty){
|
||||
form.name + "\n===============\n\n" + form.description.get
|
||||
} else {
|
||||
form.name + "\n===============\n"
|
||||
}, "UTF-8")
|
||||
|
||||
val git = Git.open(tmpdir)
|
||||
git.add.addFilepattern("README.md").call
|
||||
git.commit.setMessage("Initial commit").call
|
||||
git.push.call
|
||||
|
||||
} finally {
|
||||
FileUtils.deleteDirectory(tmpdir)
|
||||
}
|
||||
|
||||
// Create Wiki repository
|
||||
createWikiRepository(context.loginAccount.get, form.name)
|
||||
|
||||
// redirect to the repository
|
||||
redirect("/%s/%s".format(loginUserName, form.name))
|
||||
})
|
||||
|
||||
/**
|
||||
* Duplicate check for the repository name.
|
||||
*/
|
||||
private def unique: Constraint = new Constraint(){
|
||||
def validate(name: String, value: String): Option[String] =
|
||||
getRepositoryNamesOfUser(context.loginAccount.get.userName).find(_ == value).map(_ => "Repository already exists.")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package app
|
||||
|
||||
import service._
|
||||
|
||||
class IndexController extends IndexControllerBase with RepositoryService with AccountService with SystemSettingsService
|
||||
|
||||
trait IndexControllerBase extends ControllerBase { self: RepositoryService with SystemSettingsService =>
|
||||
|
||||
get("/"){
|
||||
html.index(getAccessibleRepositories(context.loginAccount, baseUrl), loadSystemSettings(),
|
||||
context.loginAccount.map{ account => getRepositoryNamesOfUser(account.userName) }.getOrElse(Nil))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,252 +0,0 @@
|
||||
package app
|
||||
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
import service._
|
||||
import IssuesService._
|
||||
import util.{CollaboratorsAuthenticator, ReferrerAuthenticator, ReadableUsersAuthenticator}
|
||||
import org.scalatra.Ok
|
||||
|
||||
class IssuesController extends IssuesControllerBase
|
||||
with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService
|
||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator
|
||||
|
||||
trait IssuesControllerBase extends ControllerBase {
|
||||
self: IssuesService with RepositoryService with LabelsService with MilestonesService
|
||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator =>
|
||||
|
||||
case class IssueCreateForm(title: String, content: Option[String],
|
||||
assignedUserName: Option[String], milestoneId: Option[Int], labelNames: Option[String])
|
||||
|
||||
case class IssueEditForm(title: String, content: Option[String])
|
||||
|
||||
case class CommentForm(issueId: Int, content: String)
|
||||
|
||||
val issueCreateForm = mapping(
|
||||
"title" -> trim(label("Title", text(required))),
|
||||
"content" -> trim(optional(text())),
|
||||
"assignedUserName" -> trim(optional(text())),
|
||||
"milestoneId" -> trim(optional(number())),
|
||||
"labelNames" -> trim(optional(text()))
|
||||
)(IssueCreateForm.apply)
|
||||
|
||||
val issueEditForm = mapping(
|
||||
"title" -> trim(label("Title", text(required))),
|
||||
"content" -> trim(optional(text()))
|
||||
)(IssueEditForm.apply)
|
||||
|
||||
val commentForm = mapping(
|
||||
"issueId" -> label("Issue Id", number()),
|
||||
"content" -> trim(label("Comment", text(required)))
|
||||
)(CommentForm.apply)
|
||||
|
||||
get("/:owner/:repository/issues")(referrersOnly {
|
||||
searchIssues("all", _)
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/assigned/:userName")(referrersOnly {
|
||||
searchIssues("assigned", _)
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/created_by/:userName")(referrersOnly {
|
||||
searchIssues("created_by", _)
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
val issueId = params("id")
|
||||
|
||||
getIssue(owner, name, issueId) map {
|
||||
issues.html.issue(
|
||||
_,
|
||||
getComments(owner, name, issueId.toInt),
|
||||
getIssueLabels(owner, name, issueId.toInt),
|
||||
(getCollaborators(owner, name) :+ owner).sorted,
|
||||
getMilestones(owner, name),
|
||||
getLabels(owner, name),
|
||||
hasWritePermission(owner, name, context.loginAccount),
|
||||
repository)
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
|
||||
issues.html.create(
|
||||
(getCollaborators(owner, name) :+ owner).sorted,
|
||||
getMilestones(owner, name),
|
||||
getLabels(owner, name),
|
||||
hasWritePermission(owner, name, context.loginAccount),
|
||||
repository)
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
val writable = hasWritePermission(owner, name, context.loginAccount)
|
||||
|
||||
val issueId = createIssue(owner, name, context.loginAccount.get.userName, form.title, form.content,
|
||||
if(writable) form.assignedUserName else None,
|
||||
if(writable) form.milestoneId else None)
|
||||
|
||||
if(writable){
|
||||
form.labelNames.map { value =>
|
||||
val labels = getLabels(owner, name)
|
||||
value.split(",").foreach { labelName =>
|
||||
labels.find(_.labelName == labelName).map { label =>
|
||||
registerIssueLabel(owner, name, issueId, label.labelId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
redirect("/%s/%s/issues/%d".format(owner, name, issueId))
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (form, repository) =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
|
||||
getIssue(owner, name, params("id")).map { issue =>
|
||||
if(isEditable(owner, name, issue.openedUserName)){
|
||||
updateIssue(owner, name, issue.issueId, form.title, form.content)
|
||||
redirect("/%s/%s/issues/_data/%d".format(owner, name, issue.issueId))
|
||||
} else Unauthorized
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
|
||||
getIssue(owner, name, form.issueId.toString).map { issue =>
|
||||
redirect("/%s/%s/issues/%d#comment-%d".format(
|
||||
owner, name, form.issueId,
|
||||
createComment(owner, name, context.loginAccount.get.userName,
|
||||
form.issueId,
|
||||
form.content,
|
||||
if(isEditable(owner, name, issue.openedUserName)){
|
||||
params.get("action") filter { action =>
|
||||
updateClosed(owner, name, form.issueId, if(action == "close") true else false) > 0
|
||||
}
|
||||
} else None)
|
||||
))
|
||||
}
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
|
||||
getComment(owner, name, params("id")).map { comment =>
|
||||
if(isEditable(owner, name, comment.commentedUserName)){
|
||||
updateComment(comment.commentId, form.content)
|
||||
redirect("/%s/%s/issue_comments/_data/%d".format(owner, name, comment.commentId))
|
||||
} else Unauthorized
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository =>
|
||||
getIssue(repository.owner, repository.name, params("id")) map { x =>
|
||||
if(isEditable(x.userName, x.repositoryName, x.openedUserName)){
|
||||
params.get("dataType") collect {
|
||||
case t if t == "html" => issues.html.editissue(
|
||||
x.title, x.content, x.issueId, x.userName, x.repositoryName)
|
||||
} getOrElse {
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map("title" -> x.title,
|
||||
"content" -> view.Markdown.toHtml(x.content getOrElse "No description given.",
|
||||
repository, false, true, true)
|
||||
))
|
||||
}
|
||||
} else Unauthorized
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository =>
|
||||
getComment(repository.owner, repository.name, params("id")) map { x =>
|
||||
if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
|
||||
params.get("dataType") collect {
|
||||
case t if t == "html" => issues.html.editcomment(
|
||||
x.content, x.commentId, x.userName, x.repositoryName)
|
||||
} getOrElse {
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map("content" -> view.Markdown.toHtml(x.content,
|
||||
repository, false, true, true)
|
||||
))
|
||||
}
|
||||
} else Unauthorized
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/label/new")(collaboratorsOnly { repository =>
|
||||
val issueId = params("id").toInt
|
||||
|
||||
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
||||
issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/label/delete")(collaboratorsOnly { repository =>
|
||||
val issueId = params("id").toInt
|
||||
|
||||
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
||||
issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/assign")(collaboratorsOnly { repository =>
|
||||
updateAssignedUserName(repository.owner, repository.name, params("id").toInt,
|
||||
params.get("assignedUserName") filter (_.trim != ""))
|
||||
Ok("updated")
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/milestone")(collaboratorsOnly { repository =>
|
||||
updateMilestoneId(repository.owner, repository.name, params("id").toInt,
|
||||
params.get("milestoneId") collect { case x if x.trim != "" => x.toInt })
|
||||
Ok("updated")
|
||||
})
|
||||
|
||||
private def isEditable(owner: String, repository: String, author: String)(implicit context: app.Context): Boolean =
|
||||
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||
|
||||
private def searchIssues(filter: String, repository: RepositoryService.RepositoryInfo) = {
|
||||
val owner = repository.owner
|
||||
val repoName = repository.name
|
||||
val userName = if(filter != "all") Some(params("userName")) else None
|
||||
val sessionKey = "%s/%s/issues".format(owner, repoName)
|
||||
|
||||
val page = try {
|
||||
val i = params.getOrElse("page", "1").toInt
|
||||
if(i <= 0) 1 else i
|
||||
} catch {
|
||||
case e: NumberFormatException => 1
|
||||
}
|
||||
|
||||
// retrieve search condition
|
||||
val condition = if(request.getQueryString == null){
|
||||
session.get(sessionKey).getOrElse(IssueSearchCondition()).asInstanceOf[IssueSearchCondition]
|
||||
} else IssueSearchCondition(request)
|
||||
|
||||
session.put(sessionKey, condition)
|
||||
|
||||
issues.html.list(
|
||||
searchIssue(owner, repoName, condition, filter, userName, (page - 1) * IssueLimit, IssueLimit),
|
||||
page,
|
||||
getLabels(owner, repoName),
|
||||
getMilestones(owner, repoName).filter(_.closedDate.isEmpty),
|
||||
countIssue(owner, repoName, condition.copy(state = "open"), filter, userName),
|
||||
countIssue(owner, repoName, condition.copy(state = "closed"), filter, userName),
|
||||
countIssue(owner, repoName, condition, "all", None),
|
||||
context.loginAccount.map(x => countIssue(owner, repoName, condition, "assigned", Some(x.userName))),
|
||||
context.loginAccount.map(x => countIssue(owner, repoName, condition, "created_by", Some(x.userName))),
|
||||
countIssueGroupByLabels(owner, repoName, condition, filter, userName),
|
||||
condition,
|
||||
filter,
|
||||
repository,
|
||||
hasWritePermission(owner, repoName, context.loginAccount))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package app
|
||||
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import service._
|
||||
import util.CollaboratorsAuthenticator
|
||||
|
||||
class LabelsController extends LabelsControllerBase
|
||||
with LabelsService with RepositoryService with AccountService with CollaboratorsAuthenticator
|
||||
|
||||
trait LabelsControllerBase extends ControllerBase {
|
||||
self: LabelsService with RepositoryService with CollaboratorsAuthenticator =>
|
||||
|
||||
case class LabelForm(labelName: String, color: String)
|
||||
|
||||
val newForm = mapping(
|
||||
"newLabelName" -> trim(label("Label name", text(required, identifier, maxlength(100)))),
|
||||
"newColor" -> trim(label("Color", text(required, color)))
|
||||
)(LabelForm.apply)
|
||||
|
||||
val editForm = mapping(
|
||||
"editLabelName" -> trim(label("Label name", text(required, identifier, maxlength(100)))),
|
||||
"editColor" -> trim(label("Color", text(required, color)))
|
||||
)(LabelForm.apply)
|
||||
|
||||
post("/:owner/:repository/issues/label/new", newForm)(collaboratorsOnly { (form, repository) =>
|
||||
createLabel(repository.owner, repository.name, form.labelName, form.color.substring(1))
|
||||
redirect("/%s/%s/issues".format(repository.owner, repository.name))
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/label/edit")(collaboratorsOnly { repository =>
|
||||
issues.labels.html.editlist(getLabels(repository.owner, repository.name), repository)
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/label/:labelId/edit")(collaboratorsOnly { repository =>
|
||||
getLabel(repository.owner, repository.name, params("labelId").toInt).map { label =>
|
||||
issues.labels.html.edit(Some(label), repository)
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/label/:labelId/edit", editForm)(collaboratorsOnly { (form, repository) =>
|
||||
updateLabel(repository.owner, repository.name, params("labelId").toInt, form.labelName, form.color.substring(1))
|
||||
issues.labels.html.editlist(getLabels(repository.owner, repository.name), repository)
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/label/:labelId/delete")(collaboratorsOnly { repository =>
|
||||
deleteLabel(repository.owner, repository.name, params("labelId").toInt)
|
||||
issues.labels.html.editlist(getLabels(repository.owner, repository.name), repository)
|
||||
})
|
||||
|
||||
}
|
||||
@@ -1,249 +0,0 @@
|
||||
package app
|
||||
|
||||
import util.Directory._
|
||||
import util.Implicits._
|
||||
import _root_.util.{ReferrerAuthenticator, JGitUtil, FileUtil}
|
||||
import service._
|
||||
import org.scalatra._
|
||||
import java.io.File
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib._
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.eclipse.jgit.treewalk._
|
||||
|
||||
class RepositoryViewerController extends RepositoryViewerControllerBase
|
||||
with RepositoryService with AccountService with ReferrerAuthenticator
|
||||
|
||||
/**
|
||||
* The repository viewer.
|
||||
*/
|
||||
trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
self: RepositoryService with AccountService with ReferrerAuthenticator =>
|
||||
|
||||
/**
|
||||
* Returns converted HTML from Markdown for preview.
|
||||
*/
|
||||
post("/:owner/:repository/_preview")(referrersOnly { repository =>
|
||||
contentType = "text/html"
|
||||
view.helpers.markdown(params("content"), repository,
|
||||
params("enableWikiLink").toBoolean,
|
||||
params("enableCommitLink").toBoolean,
|
||||
params("enableIssueLink").toBoolean)
|
||||
})
|
||||
|
||||
/**
|
||||
* Displays the file list of the repository root and the default branch.
|
||||
*/
|
||||
get("/:owner/:repository")(referrersOnly {
|
||||
fileList(_)
|
||||
})
|
||||
|
||||
/**
|
||||
* Displays the file list of the repository root and the specified branch.
|
||||
*/
|
||||
get("/:owner/:repository/tree/:id")(referrersOnly {
|
||||
fileList(_, params("id"))
|
||||
})
|
||||
|
||||
/**
|
||||
* Displays the file list of the specified path and branch.
|
||||
*/
|
||||
get("/:owner/:repository/tree/:id/*")(referrersOnly {
|
||||
fileList(_, params("id"), multiParams("splat").head)
|
||||
})
|
||||
|
||||
/**
|
||||
* Displays the commit list of the specified branch.
|
||||
*/
|
||||
get("/:owner/:repository/commits/:branch")(referrersOnly { repository =>
|
||||
val branchName = params("branch")
|
||||
val page = params.getOrElse("page", "1").toInt
|
||||
|
||||
JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git =>
|
||||
val (logs, hasNext) = JGitUtil.getCommitLog(git, branchName, page, 30)
|
||||
|
||||
repo.html.commits(Nil, branchName, repository, logs.splitWith{ (commit1, commit2) =>
|
||||
view.helpers.date(commit1.time) == view.helpers.date(commit2.time)
|
||||
}, page, hasNext)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Displays the commit list of the specified resource.
|
||||
*/
|
||||
get("/:owner/:repository/commits/:branch/*")(referrersOnly { repository =>
|
||||
val branchName = params("branch")
|
||||
val path = multiParams("splat").head //.replaceFirst("^tree/.+?/", "")
|
||||
val page = params.getOrElse("page", "1").toInt
|
||||
|
||||
JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git =>
|
||||
val (logs, hasNext) = JGitUtil.getCommitLog(git, branchName, page, 30, path)
|
||||
|
||||
repo.html.commits(path.split("/").toList, branchName, repository,
|
||||
logs.splitWith{ (commit1, commit2) =>
|
||||
view.helpers.date(commit1.time) == view.helpers.date(commit2.time)
|
||||
}, page, hasNext)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Displays the file content of the specified branch or commit.
|
||||
*/
|
||||
get("/:owner/:repository/blob/:id/*")(referrersOnly { repository =>
|
||||
val id = params("id") // branch name or commit id
|
||||
val raw = params.get("raw").getOrElse("false").toBoolean
|
||||
val path = multiParams("splat").head //.replaceFirst("^tree/.+?/", "")
|
||||
|
||||
JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
||||
|
||||
@scala.annotation.tailrec
|
||||
def getPathObjectId(path: String, walk: TreeWalk): ObjectId = walk.next match {
|
||||
case true if(walk.getPathString == path) => walk.getObjectId(0)
|
||||
case true => getPathObjectId(path, walk)
|
||||
}
|
||||
|
||||
val treeWalk = new TreeWalk(git.getRepository)
|
||||
val objectId = try {
|
||||
treeWalk.addTree(revCommit.getTree)
|
||||
treeWalk.setRecursive(true)
|
||||
getPathObjectId(path, treeWalk)
|
||||
} finally {
|
||||
treeWalk.release
|
||||
}
|
||||
|
||||
if(raw){
|
||||
// Download
|
||||
contentType = "application/octet-stream"
|
||||
JGitUtil.getContent(git, objectId, false).get
|
||||
} else {
|
||||
// Viewer
|
||||
val large = FileUtil.isLarge(git.getRepository.getObjectDatabase.open(objectId).getSize)
|
||||
val viewer = if(FileUtil.isImage(path)) "image" else if(large) "large" else "other"
|
||||
val bytes = if(viewer == "other") JGitUtil.getContent(git, objectId, false) else None
|
||||
|
||||
val content = if(viewer == "other"){
|
||||
if(bytes.isDefined && FileUtil.isText(bytes.get)){
|
||||
// text
|
||||
JGitUtil.ContentInfo("text", bytes.map(new String(_, "UTF-8")))
|
||||
} else {
|
||||
// binary
|
||||
JGitUtil.ContentInfo("binary", None)
|
||||
}
|
||||
} else {
|
||||
// image or large
|
||||
JGitUtil.ContentInfo(viewer, None)
|
||||
}
|
||||
|
||||
repo.html.blob(id, repository, path.split("/").toList, content, new JGitUtil.CommitInfo(revCommit))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Displays details of the specified commit.
|
||||
*/
|
||||
get("/:owner/:repository/commit/:id")(referrersOnly { repository =>
|
||||
val id = params("id")
|
||||
|
||||
JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
||||
|
||||
repo.html.commit(id, new JGitUtil.CommitInfo(revCommit),
|
||||
JGitUtil.getBranchesOfCommit(git, revCommit.getName), JGitUtil.getTagsOfCommit(git, revCommit.getName),
|
||||
repository, JGitUtil.getDiffs(git, id))
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Displays tags.
|
||||
*/
|
||||
get("/:owner/:repository/tags")(referrersOnly {
|
||||
repo.html.tags(_)
|
||||
})
|
||||
|
||||
/**
|
||||
* Download repository contents as an archive.
|
||||
*/
|
||||
get("/:owner/:repository/archive/:name")(referrersOnly { repository =>
|
||||
val name = params("name")
|
||||
|
||||
if(name.endsWith(".zip")){
|
||||
val revision = name.replaceFirst("\\.zip$", "")
|
||||
val workDir = getDownloadWorkDir(repository.owner, repository.name, session.getId)
|
||||
if(workDir.exists){
|
||||
FileUtils.deleteDirectory(workDir)
|
||||
}
|
||||
workDir.mkdirs
|
||||
|
||||
// clone the repository
|
||||
val cloneDir = new File(workDir, revision)
|
||||
JGitUtil.withGit(Git.cloneRepository
|
||||
.setURI(getRepositoryDir(repository.owner, repository.name).toURI.toString)
|
||||
.setDirectory(cloneDir)
|
||||
.call){ git =>
|
||||
|
||||
// checkout the specified revision
|
||||
git.checkout.setName(revision).call
|
||||
}
|
||||
|
||||
// remove .git
|
||||
FileUtils.deleteDirectory(new File(cloneDir, ".git"))
|
||||
|
||||
// create zip file
|
||||
val zipFile = new File(workDir, (if(revision.length == 40) revision.substring(0, 10) else revision) + ".zip")
|
||||
FileUtil.createZipFile(zipFile, cloneDir)
|
||||
|
||||
contentType = "application/octet-stream"
|
||||
zipFile
|
||||
} else {
|
||||
BadRequest
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Provides HTML of the file list.
|
||||
*
|
||||
* @param repository the repository information
|
||||
* @param revstr the branch name or commit id(optional)
|
||||
* @param path the directory path (optional)
|
||||
* @return HTML of the file list
|
||||
*/
|
||||
private def fileList(repository: RepositoryService.RepositoryInfo, revstr: String = "", path: String = ".") = {
|
||||
getRepository(repository.owner, repository.name, baseUrl).map { repositoryInfo =>
|
||||
val revision = if(revstr.isEmpty){
|
||||
repositoryInfo.repository.defaultBranch
|
||||
} else {
|
||||
revstr
|
||||
}
|
||||
|
||||
JGitUtil.withGit(getRepositoryDir(repositoryInfo.owner, repositoryInfo.name)){ git =>
|
||||
// get latest commit
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(revision))
|
||||
|
||||
val files = JGitUtil.getFileList(git, revision, path)
|
||||
|
||||
// process README.md
|
||||
val readme = files.find(_.name == "README.md").map { file =>
|
||||
new String(JGitUtil.getContent(Git.open(getRepositoryDir(repositoryInfo.owner, repositoryInfo.name)), file.id, true).get, "UTF-8")
|
||||
}
|
||||
|
||||
repo.html.files(
|
||||
// current branch
|
||||
revision,
|
||||
// repository
|
||||
repositoryInfo,
|
||||
// current path
|
||||
if(path == ".") Nil else path.split("/").toList,
|
||||
// latest commit
|
||||
new JGitUtil.CommitInfo(revCommit),
|
||||
// file list
|
||||
files,
|
||||
// readme
|
||||
readme
|
||||
)
|
||||
}
|
||||
} getOrElse NotFound
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
package app
|
||||
|
||||
import service._
|
||||
import util.Directory._
|
||||
import util.{UsersAuthenticator, OwnerAuthenticator}
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.apache.commons.io.FileUtils
|
||||
|
||||
class SettingsController extends SettingsControllerBase
|
||||
with RepositoryService with AccountService with OwnerAuthenticator with UsersAuthenticator
|
||||
|
||||
trait SettingsControllerBase extends ControllerBase {
|
||||
self: RepositoryService with AccountService with OwnerAuthenticator with UsersAuthenticator =>
|
||||
|
||||
case class OptionsForm(description: Option[String], defaultBranch: String, isPrivate: Boolean)
|
||||
|
||||
val optionsForm = mapping(
|
||||
"description" -> trim(label("Description" , optional(text()))),
|
||||
"defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100)))),
|
||||
"isPrivate" -> trim(label("Repository Type", boolean()))
|
||||
)(OptionsForm.apply)
|
||||
|
||||
case class CollaboratorForm(userName: String)
|
||||
|
||||
val collaboratorForm = mapping(
|
||||
"userName" -> trim(label("Username", text(required, collaborator)))
|
||||
)(CollaboratorForm.apply)
|
||||
|
||||
/**
|
||||
* Redirect to the Options page.
|
||||
*/
|
||||
get("/:owner/:repository/settings")(ownerOnly { repository =>
|
||||
redirect("/%s/%s/settings/options".format(repository.owner, repository.name))
|
||||
})
|
||||
|
||||
/**
|
||||
* Display the Options page.
|
||||
*/
|
||||
get("/:owner/:repository/settings/options")(ownerOnly {
|
||||
settings.html.options(_)
|
||||
})
|
||||
|
||||
/**
|
||||
* Save the repository options.
|
||||
*/
|
||||
post("/:owner/:repository/settings/options", optionsForm)(ownerOnly { (form, repository) =>
|
||||
saveRepositoryOptions(repository.owner, repository.name, form.description, form.defaultBranch, form.isPrivate)
|
||||
redirect("/%s/%s/settings/options".format(repository.owner, repository.name))
|
||||
})
|
||||
|
||||
/**
|
||||
* Display the Collaborators page.
|
||||
*/
|
||||
get("/:owner/:repository/settings/collaborators")(ownerOnly { repository =>
|
||||
settings.html.collaborators(getCollaborators(repository.owner, repository.name), repository)
|
||||
})
|
||||
|
||||
/**
|
||||
* JSON API for collaborator completion.
|
||||
*/
|
||||
get("/:owner/:repository/settings/collaborators/proposals")(usersOnly {
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(Map("options" -> getAllUsers.map(_.userName).toArray))
|
||||
})
|
||||
|
||||
/**
|
||||
* Add the collaborator.
|
||||
*/
|
||||
post("/:owner/:repository/settings/collaborators/add", collaboratorForm)(ownerOnly { (form, repository) =>
|
||||
addCollaborator(repository.owner, repository.name, form.userName)
|
||||
redirect("/%s/%s/settings/collaborators".format(repository.owner, repository.name))
|
||||
})
|
||||
|
||||
/**
|
||||
* Add the collaborator.
|
||||
*/
|
||||
get("/:owner/:repository/settings/collaborators/remove")(ownerOnly { repository =>
|
||||
removeCollaborator(repository.owner, repository.name, params("name"))
|
||||
redirect("/%s/%s/settings/collaborators".format(repository.owner, repository.name))
|
||||
})
|
||||
|
||||
/**
|
||||
* Display the delete repository page.
|
||||
*/
|
||||
get("/:owner/:repository/settings/delete")(ownerOnly {
|
||||
settings.html.delete(_)
|
||||
})
|
||||
|
||||
/**
|
||||
* Delete the repository.
|
||||
*/
|
||||
post("/:owner/:repository/settings/delete")(ownerOnly { repository =>
|
||||
deleteRepository(repository.owner, repository.name)
|
||||
|
||||
FileUtils.deleteDirectory(getRepositoryDir(repository.owner, repository.name))
|
||||
FileUtils.deleteDirectory(getWikiRepositoryDir(repository.owner, repository.name))
|
||||
FileUtils.deleteDirectory(getTemporaryDir(repository.owner, repository.name))
|
||||
|
||||
redirect("/%s".format(repository.owner))
|
||||
})
|
||||
|
||||
/**
|
||||
* Provides Constraint to validate the collaborator name.
|
||||
*/
|
||||
private def collaborator: Constraint = new Constraint(){
|
||||
def validate(name: String, value: String): Option[String] = {
|
||||
getAccountByUserName(value) match {
|
||||
case None => Some("User does not exist.")
|
||||
case Some(x) if(x.userName == context.loginAccount.get.userName) => Some("User can access this repository already.")
|
||||
case Some(x) => {
|
||||
val paths = request.getRequestURI.split("/")
|
||||
if(getCollaborators(paths(1), paths(2)).contains(x.userName)){
|
||||
Some("User can access this repository already.")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package app
|
||||
|
||||
import service._
|
||||
import util.StringUtil._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
class SignInController extends SignInControllerBase with SystemSettingsService with AccountService
|
||||
|
||||
trait SignInControllerBase extends ControllerBase { self: SystemSettingsService with AccountService =>
|
||||
|
||||
case class SignInForm(userName: String, password: String)
|
||||
|
||||
val form = mapping(
|
||||
"userName" -> trim(label("Username", text(required))),
|
||||
"password" -> trim(label("Password", text(required)))
|
||||
)(SignInForm.apply)
|
||||
|
||||
get("/signin"){
|
||||
val queryString = request.getQueryString
|
||||
if(queryString != null && queryString.startsWith("/")){
|
||||
session.setAttribute("REDIRECT", queryString)
|
||||
}
|
||||
html.signin(loadSystemSettings())
|
||||
}
|
||||
|
||||
post("/signin", form){ form =>
|
||||
val account = getAccountByUserName(form.userName)
|
||||
if(account.isEmpty || account.get.password != encrypt(form.password)){
|
||||
redirect("/signin")
|
||||
} else {
|
||||
session.setAttribute("LOGIN_ACCOUNT", account.get)
|
||||
updateLastLoginDate(account.get.userName)
|
||||
|
||||
session.get("REDIRECT").map { redirectUrl =>
|
||||
session.removeAttribute("REDIRECT")
|
||||
redirect(redirectUrl.asInstanceOf[String])
|
||||
}.getOrElse {
|
||||
redirect("/%s".format(account.get.userName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get("/signout"){
|
||||
session.invalidate
|
||||
redirect("/")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package app
|
||||
|
||||
import service.{AccountService, SystemSettingsService}
|
||||
import SystemSettingsService._
|
||||
import util.AdminAuthenticator
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
class SystemSettingsController extends SystemSettingsControllerBase
|
||||
with SystemSettingsService with AccountService with AdminAuthenticator
|
||||
|
||||
trait SystemSettingsControllerBase extends ControllerBase {
|
||||
self: SystemSettingsService with AccountService with AdminAuthenticator =>
|
||||
|
||||
private case class SystemSettingsForm(allowAccountRegistration: Boolean)
|
||||
|
||||
private val form = mapping(
|
||||
"allowAccountRegistration" -> trim(label("Account registration", boolean()))
|
||||
)(SystemSettingsForm.apply)
|
||||
|
||||
|
||||
get("/admin/system")(adminOnly {
|
||||
admin.html.system(loadSystemSettings())
|
||||
})
|
||||
|
||||
post("/admin/system", form)(adminOnly { form =>
|
||||
saveSystemSettings(SystemSettings(form.allowAccountRegistration))
|
||||
redirect("/admin/system")
|
||||
})
|
||||
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package app
|
||||
|
||||
import service._
|
||||
import util.AdminAuthenticator
|
||||
import util.StringUtil._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
class UserManagementController extends UserManagementControllerBase with AccountService with AdminAuthenticator
|
||||
|
||||
trait UserManagementControllerBase extends ControllerBase { self: AccountService with AdminAuthenticator =>
|
||||
|
||||
case class UserNewForm(userName: String, password: String, mailAddress: String, isAdmin: Boolean, url: Option[String])
|
||||
case class UserEditForm(userName: String, password: Option[String], mailAddress: String, isAdmin: Boolean, url: Option[String])
|
||||
|
||||
val newForm = mapping(
|
||||
"userName" -> trim(label("Username" , text(required, maxlength(100), identifier, uniqueUserName))),
|
||||
"password" -> trim(label("Password" , text(required, maxlength(20)))),
|
||||
"mailAddress" -> trim(label("Mail Address" , text(required, maxlength(100), uniqueMailAddress()))),
|
||||
"isAdmin" -> trim(label("User Type" , boolean())),
|
||||
"url" -> trim(label("URL" , optional(text(maxlength(200)))))
|
||||
)(UserNewForm.apply)
|
||||
|
||||
val editForm = mapping(
|
||||
"userName" -> trim(label("Username" , text(required, maxlength(100), identifier))),
|
||||
"password" -> trim(label("Password" , optional(text(maxlength(20))))),
|
||||
"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)))))
|
||||
)(UserEditForm.apply)
|
||||
|
||||
get("/admin/users")(adminOnly {
|
||||
admin.users.html.list(getAllUsers())
|
||||
})
|
||||
|
||||
get("/admin/users/_new")(adminOnly {
|
||||
admin.users.html.edit(None)
|
||||
})
|
||||
|
||||
post("/admin/users/_new", newForm)(adminOnly { form =>
|
||||
createAccount(form.userName, encrypt(form.password), form.mailAddress, form.isAdmin, form.url)
|
||||
redirect("/admin/users")
|
||||
})
|
||||
|
||||
get("/admin/users/:userName/_edit")(adminOnly {
|
||||
val userName = params("userName")
|
||||
admin.users.html.edit(getAccountByUserName(userName))
|
||||
})
|
||||
|
||||
post("/admin/users/:name/_edit", editForm)(adminOnly { form =>
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).map { account =>
|
||||
updateAccount(getAccountByUserName(userName).get.copy(
|
||||
password = form.password.map(encrypt).getOrElse(account.password),
|
||||
mailAddress = form.mailAddress,
|
||||
isAdmin = form.isAdmin,
|
||||
url = form.url))
|
||||
|
||||
redirect("/admin/users")
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
// TODO Merge with AccountController?
|
||||
private def uniqueUserName: Constraint = new Constraint(){
|
||||
def validate(name: String, value: String): Option[String] =
|
||||
getAccountByUserName(value).map { _ => "User already exists." }
|
||||
}
|
||||
|
||||
// TODO Merge with AccountController?
|
||||
private def uniqueMailAddress(paramName: String = ""): Constraint = new Constraint(){
|
||||
def validate(name: String, value: String): Option[String] =
|
||||
getAccountByMailAddress(value)
|
||||
.filter { x => if(paramName.isEmpty) true else Some(x.userName) != params.get(paramName) }
|
||||
.map { _ => "Mail address is already registered." }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
package app
|
||||
|
||||
import service._
|
||||
import util.{CollaboratorsAuthenticator, ReferrerAuthenticator, JGitUtil}
|
||||
import util.Directory._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
class WikiController extends WikiControllerBase
|
||||
with WikiService with RepositoryService with AccountService with CollaboratorsAuthenticator with ReferrerAuthenticator
|
||||
|
||||
trait WikiControllerBase extends ControllerBase {
|
||||
self: WikiService with RepositoryService with CollaboratorsAuthenticator with ReferrerAuthenticator =>
|
||||
|
||||
case class WikiPageEditForm(pageName: String, content: String, message: Option[String], currentPageName: String)
|
||||
|
||||
val newForm = mapping(
|
||||
"pageName" -> trim(label("Page name" , text(required, maxlength(40), identifier, unique))),
|
||||
"content" -> trim(label("Content" , text(required))),
|
||||
"message" -> trim(label("Message" , optional(text()))),
|
||||
"currentPageName" -> trim(label("Current page name" , text()))
|
||||
)(WikiPageEditForm.apply)
|
||||
|
||||
val editForm = mapping(
|
||||
"pageName" -> trim(label("Page name" , text(required, maxlength(40), identifier))),
|
||||
"content" -> trim(label("Content" , text(required))),
|
||||
"message" -> trim(label("Message" , optional(text()))),
|
||||
"currentPageName" -> trim(label("Current page name" , text(required)))
|
||||
)(WikiPageEditForm.apply)
|
||||
|
||||
get("/:owner/:repository/wiki")(referrersOnly { repository =>
|
||||
getWikiPage(repository.owner, repository.name, "Home").map { page =>
|
||||
wiki.html.page("Home", page, repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
} getOrElse redirect("/%s/%s/wiki/Home/_edit".format(repository.owner, repository.name))
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/:page")(referrersOnly { repository =>
|
||||
val pageName = params("page")
|
||||
|
||||
getWikiPage(repository.owner, repository.name, pageName).map { page =>
|
||||
wiki.html.page(pageName, page, repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
} getOrElse redirect("/%s/%s/wiki/%s/_edit".format(repository.owner, repository.name, pageName)) // TODO URLEncode
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/:page/_history")(referrersOnly { repository =>
|
||||
val pageName = params("page")
|
||||
|
||||
JGitUtil.withGit(getWikiRepositoryDir(repository.owner, repository.name)){ git =>
|
||||
wiki.html.history(Some(pageName), JGitUtil.getCommitLog(git, "master", path = pageName + ".md")._1, repository)
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/:page/_compare/:commitId")(referrersOnly { repository =>
|
||||
val pageName = params("page")
|
||||
val commitId = params("commitId").split("\\.\\.\\.")
|
||||
|
||||
JGitUtil.withGit(getWikiRepositoryDir(repository.owner, repository.name)){ git =>
|
||||
wiki.html.compare(Some(pageName), getWikiDiffs(git, commitId(0), commitId(1)), repository)
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/_compare/:commitId")(referrersOnly { repository =>
|
||||
val commitId = params("commitId").split("\\.\\.\\.")
|
||||
|
||||
JGitUtil.withGit(getWikiRepositoryDir(repository.owner, repository.name)){ git =>
|
||||
wiki.html.compare(None, getWikiDiffs(git, commitId(0), commitId(1)), repository)
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/:page/_edit")(collaboratorsOnly { repository =>
|
||||
val pageName = params("page")
|
||||
wiki.html.edit(pageName, getWikiPage(repository.owner, repository.name, pageName), repository)
|
||||
})
|
||||
|
||||
post("/:owner/:repository/wiki/_edit", editForm)(collaboratorsOnly { (form, repository) =>
|
||||
saveWikiPage(repository.owner, repository.name, form.currentPageName, form.pageName,
|
||||
form.content, context.loginAccount.get, form.message.getOrElse(""))
|
||||
updateLastActivityDate(repository.owner, repository.name)
|
||||
|
||||
redirect("/%s/%s/wiki/%s".format(repository.owner, repository.name, form.pageName))
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/_new")(collaboratorsOnly {
|
||||
wiki.html.edit("", None, _)
|
||||
})
|
||||
|
||||
post("/:owner/:repository/wiki/_new", newForm)(collaboratorsOnly { (form, repository) =>
|
||||
saveWikiPage(repository.owner, repository.name, form.currentPageName, form.pageName,
|
||||
form.content, context.loginAccount.get, form.message.getOrElse(""))
|
||||
updateLastActivityDate(repository.owner, repository.name)
|
||||
|
||||
redirect("/%s/%s/wiki/%s".format(repository.owner, repository.name, form.pageName))
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/:page/_delete")(collaboratorsOnly { repository =>
|
||||
val pageName = params("page")
|
||||
|
||||
deleteWikiPage(repository.owner, repository.name, pageName, context.loginAccount.get.userName, "Delete %s".format(pageName))
|
||||
updateLastActivityDate(repository.owner, repository.name)
|
||||
|
||||
redirect("/%s/%s/wiki".format(repository.owner, repository.name))
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/_pages")(referrersOnly { repository =>
|
||||
wiki.html.pages(getWikiPageList(repository.owner, repository.name), repository,
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
|
||||
JGitUtil.withGit(getWikiRepositoryDir(repository.owner, repository.name)){ git =>
|
||||
wiki.html.history(None, JGitUtil.getCommitLog(git, "master")._1, repository)
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/_blob/*")(referrersOnly { repository =>
|
||||
getFileContent(repository.owner, repository.name, multiParams("splat").head).map { content =>
|
||||
contentType = "application/octet-stream"
|
||||
content
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
private def unique: Constraint = new Constraint(){
|
||||
def validate(name: String, value: String): Option[String] =
|
||||
getWikiPageList(params("owner"), params("repository")).find(_ == value).map(_ => "Page already exists.")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.model.{Account, CommitState, CommitStatus}
|
||||
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref
|
||||
*/
|
||||
case class ApiCombinedCommitStatus(
|
||||
state: String,
|
||||
sha: String,
|
||||
total_count: Int,
|
||||
statuses: Iterable[ApiCommitStatus],
|
||||
repository: ApiRepository){
|
||||
// val commit_url = ApiPath(s"/api/v3/repos/${repository.full_name}/${sha}")
|
||||
val url = ApiPath(s"/api/v3/repos/${repository.full_name}/commits/${sha}/status")
|
||||
}
|
||||
object ApiCombinedCommitStatus {
|
||||
def apply(sha:String, statuses: Iterable[(CommitStatus, Account)], repository:ApiRepository): ApiCombinedCommitStatus = ApiCombinedCommitStatus(
|
||||
state = CommitState.combine(statuses.map(_._1.state).toSet).name,
|
||||
sha = sha,
|
||||
total_count= statuses.size,
|
||||
statuses = statuses.map{ case (s, a)=> ApiCommitStatus(s, ApiUser(a)) },
|
||||
repository = repository)
|
||||
}
|
||||
26
src/main/scala/gitbucket/core/api/ApiComment.scala
Normal file
26
src/main/scala/gitbucket/core/api/ApiComment.scala
Normal file
@@ -0,0 +1,26 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.model.IssueComment
|
||||
|
||||
import java.util.Date
|
||||
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/issues/comments/
|
||||
*/
|
||||
case class ApiComment(
|
||||
id: Int,
|
||||
user: ApiUser,
|
||||
body: String,
|
||||
created_at: Date,
|
||||
updated_at: Date)
|
||||
|
||||
object ApiComment{
|
||||
def apply(comment: IssueComment, user: ApiUser): ApiComment =
|
||||
ApiComment(
|
||||
id = comment.commentId,
|
||||
user = user,
|
||||
body = comment.content,
|
||||
created_at = comment.registeredDate,
|
||||
updated_at = comment.updatedDate)
|
||||
}
|
||||
48
src/main/scala/gitbucket/core/api/ApiCommit.scala
Normal file
48
src/main/scala/gitbucket/core/api/ApiCommit.scala
Normal file
@@ -0,0 +1,48 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.util.JGitUtil
|
||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||
import gitbucket.core.util.RepositoryName
|
||||
|
||||
import org.eclipse.jgit.diff.DiffEntry
|
||||
import org.eclipse.jgit.api.Git
|
||||
|
||||
import java.util.Date
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/commits/
|
||||
*/
|
||||
case class ApiCommit(
|
||||
id: String,
|
||||
message: String,
|
||||
timestamp: Date,
|
||||
added: List[String],
|
||||
removed: List[String],
|
||||
modified: List[String],
|
||||
author: ApiPersonIdent,
|
||||
committer: ApiPersonIdent)(repositoryName:RepositoryName){
|
||||
val url = ApiPath(s"/api/v3/${repositoryName.fullName}/commits/${id}")
|
||||
val html_url = ApiPath(s"/${repositoryName.fullName}/commit/${id}")
|
||||
}
|
||||
|
||||
object ApiCommit{
|
||||
def apply(git: Git, repositoryName: RepositoryName, commit: CommitInfo): ApiCommit = {
|
||||
val diffs = JGitUtil.getDiffs(git, commit.id, false)
|
||||
ApiCommit(
|
||||
id = commit.id,
|
||||
message = commit.fullMessage,
|
||||
timestamp = commit.commitTime,
|
||||
added = diffs._1.collect {
|
||||
case x if x.changeType == DiffEntry.ChangeType.ADD => x.newPath
|
||||
},
|
||||
removed = diffs._1.collect {
|
||||
case x if x.changeType == DiffEntry.ChangeType.DELETE => x.oldPath
|
||||
},
|
||||
modified = diffs._1.collect {
|
||||
case x if x.changeType != DiffEntry.ChangeType.ADD && x.changeType != DiffEntry.ChangeType.DELETE => x.newPath
|
||||
},
|
||||
author = ApiPersonIdent.author(commit),
|
||||
committer = ApiPersonIdent.committer(commit)
|
||||
)(repositoryName)
|
||||
}
|
||||
}
|
||||
42
src/main/scala/gitbucket/core/api/ApiCommitListItem.scala
Normal file
42
src/main/scala/gitbucket/core/api/ApiCommitListItem.scala
Normal file
@@ -0,0 +1,42 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.api.ApiCommitListItem._
|
||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||
import gitbucket.core.util.RepositoryName
|
||||
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/commits/
|
||||
*/
|
||||
case class ApiCommitListItem(
|
||||
sha: String,
|
||||
commit: Commit,
|
||||
author: Option[ApiUser],
|
||||
committer: Option[ApiUser],
|
||||
parents: Seq[Parent])(repositoryName: RepositoryName) {
|
||||
val url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${sha}")
|
||||
}
|
||||
|
||||
object ApiCommitListItem {
|
||||
def apply(commit: CommitInfo, repositoryName: RepositoryName): ApiCommitListItem = ApiCommitListItem(
|
||||
sha = commit.id,
|
||||
commit = Commit(
|
||||
message = commit.fullMessage,
|
||||
author = ApiPersonIdent.author(commit),
|
||||
committer = ApiPersonIdent.committer(commit)
|
||||
)(commit.id, repositoryName),
|
||||
author = None,
|
||||
committer = None,
|
||||
parents = commit.parents.map(Parent(_)(repositoryName)))(repositoryName)
|
||||
|
||||
case class Parent(sha: String)(repositoryName: RepositoryName){
|
||||
val url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${sha}")
|
||||
}
|
||||
|
||||
case class Commit(
|
||||
message: String,
|
||||
author: ApiPersonIdent,
|
||||
committer: ApiPersonIdent)(sha:String, repositoryName: RepositoryName) {
|
||||
val url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/commits/${sha}")
|
||||
}
|
||||
}
|
||||
38
src/main/scala/gitbucket/core/api/ApiCommitStatus.scala
Normal file
38
src/main/scala/gitbucket/core/api/ApiCommitStatus.scala
Normal file
@@ -0,0 +1,38 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.model.CommitStatus
|
||||
import gitbucket.core.util.RepositoryName
|
||||
|
||||
import java.util.Date
|
||||
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/statuses/#create-a-status
|
||||
* https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref
|
||||
*/
|
||||
case class ApiCommitStatus(
|
||||
created_at: Date,
|
||||
updated_at: Date,
|
||||
state: String,
|
||||
target_url: Option[String],
|
||||
description: Option[String],
|
||||
id: Int,
|
||||
context: String,
|
||||
creator: ApiUser
|
||||
)(sha: String, repositoryName: RepositoryName) {
|
||||
val url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${sha}/statuses")
|
||||
}
|
||||
|
||||
|
||||
object ApiCommitStatus {
|
||||
def apply(status: CommitStatus, creator:ApiUser): ApiCommitStatus = ApiCommitStatus(
|
||||
created_at = status.registeredDate,
|
||||
updated_at = status.updatedDate,
|
||||
state = status.state.name,
|
||||
target_url = status.targetUrl,
|
||||
description= status.description,
|
||||
id = status.commitStatusId,
|
||||
context = status.context,
|
||||
creator = creator
|
||||
)(status.commitId, RepositoryName(status))
|
||||
}
|
||||
5
src/main/scala/gitbucket/core/api/ApiError.scala
Normal file
5
src/main/scala/gitbucket/core/api/ApiError.scala
Normal file
@@ -0,0 +1,5 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
case class ApiError(
|
||||
message: String,
|
||||
documentation_url: Option[String] = None)
|
||||
31
src/main/scala/gitbucket/core/api/ApiIssue.scala
Normal file
31
src/main/scala/gitbucket/core/api/ApiIssue.scala
Normal file
@@ -0,0 +1,31 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.model.Issue
|
||||
|
||||
import java.util.Date
|
||||
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/issues/
|
||||
*/
|
||||
case class ApiIssue(
|
||||
number: Int,
|
||||
title: String,
|
||||
user: ApiUser,
|
||||
// labels,
|
||||
state: String,
|
||||
created_at: Date,
|
||||
updated_at: Date,
|
||||
body: String)
|
||||
|
||||
object ApiIssue{
|
||||
def apply(issue: Issue, user: ApiUser): ApiIssue =
|
||||
ApiIssue(
|
||||
number = issue.issueId,
|
||||
title = issue.title,
|
||||
user = user,
|
||||
state = if(issue.closed){ "closed" }else{ "open" },
|
||||
body = issue.content.getOrElse(""),
|
||||
created_at = issue.registeredDate,
|
||||
updated_at = issue.updatedDate)
|
||||
}
|
||||
6
src/main/scala/gitbucket/core/api/ApiPath.scala
Normal file
6
src/main/scala/gitbucket/core/api/ApiPath.scala
Normal file
@@ -0,0 +1,6 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
/**
|
||||
* path for api url. if set path '/repos/aa/bb' then, expand 'http://server:post/repos/aa/bb' when converted to json.
|
||||
*/
|
||||
case class ApiPath(path: String)
|
||||
25
src/main/scala/gitbucket/core/api/ApiPersonIdent.scala
Normal file
25
src/main/scala/gitbucket/core/api/ApiPersonIdent.scala
Normal file
@@ -0,0 +1,25 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||
|
||||
import java.util.Date
|
||||
|
||||
|
||||
case class ApiPersonIdent(
|
||||
name: String,
|
||||
email: String,
|
||||
date: Date)
|
||||
|
||||
|
||||
object ApiPersonIdent {
|
||||
def author(commit: CommitInfo): ApiPersonIdent =
|
||||
ApiPersonIdent(
|
||||
name = commit.authorName,
|
||||
email = commit.authorEmailAddress,
|
||||
date = commit.authorTime)
|
||||
def committer(commit: CommitInfo): ApiPersonIdent =
|
||||
ApiPersonIdent(
|
||||
name = commit.committerName,
|
||||
email = commit.committerEmailAddress,
|
||||
date = commit.commitTime)
|
||||
}
|
||||
59
src/main/scala/gitbucket/core/api/ApiPullRequest.scala
Normal file
59
src/main/scala/gitbucket/core/api/ApiPullRequest.scala
Normal file
@@ -0,0 +1,59 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.model.{Issue, PullRequest}
|
||||
|
||||
import java.util.Date
|
||||
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/pulls/
|
||||
*/
|
||||
case class ApiPullRequest(
|
||||
number: Int,
|
||||
updated_at: Date,
|
||||
created_at: Date,
|
||||
head: ApiPullRequest.Commit,
|
||||
base: ApiPullRequest.Commit,
|
||||
mergeable: Option[Boolean],
|
||||
title: String,
|
||||
body: String,
|
||||
user: ApiUser) {
|
||||
val html_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}")
|
||||
//val diff_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}.diff")
|
||||
//val patch_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}.patch")
|
||||
val url = ApiPath(s"${base.repo.url.path}/pulls/${number}")
|
||||
//val issue_url = ApiPath(s"${base.repo.url.path}/issues/${number}")
|
||||
val commits_url = ApiPath(s"${base.repo.url.path}/pulls/${number}/commits")
|
||||
val review_comments_url = ApiPath(s"${base.repo.url.path}/pulls/${number}/comments")
|
||||
val review_comment_url = ApiPath(s"${base.repo.url.path}/pulls/comments/{number}")
|
||||
val comments_url = ApiPath(s"${base.repo.url.path}/issues/${number}/comments")
|
||||
val statuses_url = ApiPath(s"${base.repo.url.path}/statuses/${head.sha}")
|
||||
}
|
||||
|
||||
object ApiPullRequest{
|
||||
def apply(issue: Issue, pullRequest: PullRequest, headRepo: ApiRepository, baseRepo: ApiRepository, user: ApiUser): ApiPullRequest = ApiPullRequest(
|
||||
number = issue.issueId,
|
||||
updated_at = issue.updatedDate,
|
||||
created_at = issue.registeredDate,
|
||||
head = Commit(
|
||||
sha = pullRequest.commitIdTo,
|
||||
ref = pullRequest.requestBranch,
|
||||
repo = headRepo)(issue.userName),
|
||||
base = Commit(
|
||||
sha = pullRequest.commitIdFrom,
|
||||
ref = pullRequest.branch,
|
||||
repo = baseRepo)(issue.userName),
|
||||
mergeable = None, // TODO: need check mergeable.
|
||||
title = issue.title,
|
||||
body = issue.content.getOrElse(""),
|
||||
user = user
|
||||
)
|
||||
|
||||
case class Commit(
|
||||
sha: String,
|
||||
ref: String,
|
||||
repo: ApiRepository)(baseOwner:String){
|
||||
val label = if( baseOwner == repo.owner.login ){ ref }else{ s"${repo.owner.login}:${ref}" }
|
||||
val user = repo.owner
|
||||
}
|
||||
}
|
||||
48
src/main/scala/gitbucket/core/api/ApiRepository.scala
Normal file
48
src/main/scala/gitbucket/core/api/ApiRepository.scala
Normal file
@@ -0,0 +1,48 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.model.{Account, Repository}
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
|
||||
|
||||
// https://developer.github.com/v3/repos/
|
||||
case class ApiRepository(
|
||||
name: String,
|
||||
full_name: String,
|
||||
description: String,
|
||||
watchers: Int,
|
||||
forks: Int,
|
||||
`private`: Boolean,
|
||||
default_branch: String,
|
||||
owner: ApiUser) {
|
||||
val forks_count = forks
|
||||
val watchers_coun = watchers
|
||||
val url = ApiPath(s"/api/v3/repos/${full_name}")
|
||||
val http_url = ApiPath(s"/git/${full_name}.git")
|
||||
val clone_url = ApiPath(s"/git/${full_name}.git")
|
||||
val html_url = ApiPath(s"/${full_name}")
|
||||
}
|
||||
|
||||
object ApiRepository{
|
||||
def apply(
|
||||
repository: Repository,
|
||||
owner: ApiUser,
|
||||
forkedCount: Int =0,
|
||||
watchers: Int = 0): ApiRepository =
|
||||
ApiRepository(
|
||||
name = repository.repositoryName,
|
||||
full_name = s"${repository.userName}/${repository.repositoryName}",
|
||||
description = repository.description.getOrElse(""),
|
||||
watchers = 0,
|
||||
forks = forkedCount,
|
||||
`private` = repository.isPrivate,
|
||||
default_branch = repository.defaultBranch,
|
||||
owner = owner
|
||||
)
|
||||
|
||||
def apply(repositoryInfo: RepositoryInfo, owner: ApiUser): ApiRepository =
|
||||
ApiRepository(repositoryInfo.repository, owner, forkedCount=repositoryInfo.forkedCount)
|
||||
|
||||
def apply(repositoryInfo: RepositoryInfo, owner: Account): ApiRepository =
|
||||
this(repositoryInfo.repository, ApiUser(owner))
|
||||
|
||||
}
|
||||
36
src/main/scala/gitbucket/core/api/ApiUser.scala
Normal file
36
src/main/scala/gitbucket/core/api/ApiUser.scala
Normal file
@@ -0,0 +1,36 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.model.Account
|
||||
|
||||
import java.util.Date
|
||||
|
||||
|
||||
case class ApiUser(
|
||||
login: String,
|
||||
email: String,
|
||||
`type`: String,
|
||||
site_admin: Boolean,
|
||||
created_at: Date) {
|
||||
val url = ApiPath(s"/api/v3/users/${login}")
|
||||
val html_url = ApiPath(s"/${login}")
|
||||
// val followers_url = ApiPath(s"/api/v3/users/${login}/followers")
|
||||
// val following_url = ApiPath(s"/api/v3/users/${login}/following{/other_user}")
|
||||
// val gists_url = ApiPath(s"/api/v3/users/${login}/gists{/gist_id}")
|
||||
// val starred_url = ApiPath(s"/api/v3/users/${login}/starred{/owner}{/repo}")
|
||||
// val subscriptions_url = ApiPath(s"/api/v3/users/${login}/subscriptions")
|
||||
// val organizations_url = ApiPath(s"/api/v3/users/${login}/orgs")
|
||||
// val repos_url = ApiPath(s"/api/v3/users/${login}/repos")
|
||||
// val events_url = ApiPath(s"/api/v3/users/${login}/events{/privacy}")
|
||||
// val received_events_url = ApiPath(s"/api/v3/users/${login}/received_events")
|
||||
}
|
||||
|
||||
|
||||
object ApiUser{
|
||||
def apply(user: Account): ApiUser = ApiUser(
|
||||
login = user.fullName,
|
||||
email = user.mailAddress,
|
||||
`type` = if(user.isGroupAccount){ "Organization" }else{ "User" },
|
||||
site_admin = user.isAdmin,
|
||||
created_at = user.registeredDate
|
||||
)
|
||||
}
|
||||
7
src/main/scala/gitbucket/core/api/CreateAComment.scala
Normal file
7
src/main/scala/gitbucket/core/api/CreateAComment.scala
Normal file
@@ -0,0 +1,7 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/issues/comments/#create-a-comment
|
||||
* api form
|
||||
*/
|
||||
case class CreateAComment(body: String)
|
||||
26
src/main/scala/gitbucket/core/api/CreateAStatus.scala
Normal file
26
src/main/scala/gitbucket/core/api/CreateAStatus.scala
Normal file
@@ -0,0 +1,26 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.model.CommitState
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/statuses/#create-a-status
|
||||
* api form
|
||||
*/
|
||||
case class CreateAStatus(
|
||||
/* state is Required. The state of the status. Can be one of pending, success, error, or failure. */
|
||||
state: String,
|
||||
/* context is a string label to differentiate this status from the status of other systems. Default: "default" */
|
||||
context: Option[String],
|
||||
/* The target URL to associate with this status. This URL will be linked from the GitHub UI to allow users to easily see the ‘source’ of the Status. */
|
||||
target_url: Option[String],
|
||||
/* description is a short description of the status.*/
|
||||
description: Option[String]
|
||||
) {
|
||||
def isValid: Boolean = {
|
||||
CommitState.valueOf(state).isDefined &&
|
||||
// only http
|
||||
target_url.filterNot(f => "\\Ahttps?://".r.findPrefixOf(f).isDefined && f.length<255).isEmpty &&
|
||||
context.filterNot(f => f.length<255).isEmpty &&
|
||||
description.filterNot(f => f.length<1000).isEmpty
|
||||
}
|
||||
}
|
||||
44
src/main/scala/gitbucket/core/api/JsonFormat.scala
Normal file
44
src/main/scala/gitbucket/core/api/JsonFormat.scala
Normal file
@@ -0,0 +1,44 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.DateTimeZone
|
||||
import org.joda.time.format._
|
||||
import org.json4s._
|
||||
import org.json4s.jackson.Serialization
|
||||
|
||||
import java.util.Date
|
||||
|
||||
import scala.util.Try
|
||||
|
||||
|
||||
object JsonFormat {
|
||||
case class Context(baseUrl:String)
|
||||
val parserISO = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")
|
||||
val jsonFormats = Serialization.formats(NoTypeHints) + new CustomSerializer[Date](format =>
|
||||
(
|
||||
{ case JString(s) => Try(parserISO.parseDateTime(s)).toOption.map(_.toDate)
|
||||
.getOrElse(throw new MappingException("Can't convert " + s + " to Date")) },
|
||||
{ case x: Date => JString(parserISO.print(new DateTime(x).withZone(DateTimeZone.UTC))) }
|
||||
)
|
||||
) + FieldSerializer[ApiUser]() + FieldSerializer[ApiPullRequest]() + FieldSerializer[ApiRepository]() +
|
||||
FieldSerializer[ApiCommitListItem.Parent]() + FieldSerializer[ApiCommitListItem]() + FieldSerializer[ApiCommitListItem.Commit]() +
|
||||
FieldSerializer[ApiCommitStatus]() + FieldSerializer[ApiCommit]() + FieldSerializer[ApiCombinedCommitStatus]() +
|
||||
FieldSerializer[ApiPullRequest.Commit]()
|
||||
|
||||
|
||||
def apiPathSerializer(c: Context) = new CustomSerializer[ApiPath](format =>
|
||||
(
|
||||
{
|
||||
case JString(s) if s.startsWith(c.baseUrl) => ApiPath(s.substring(c.baseUrl.length))
|
||||
case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath")
|
||||
},
|
||||
{
|
||||
case ApiPath(path) => JString(c.baseUrl+path)
|
||||
}
|
||||
)
|
||||
)
|
||||
/**
|
||||
* convert object to json string
|
||||
*/
|
||||
def apply(obj: AnyRef)(implicit c: Context): String = Serialization.write(obj)(jsonFormats + apiPathSerializer(c))
|
||||
}
|
||||
535
src/main/scala/gitbucket/core/controller/AccountController.scala
Normal file
535
src/main/scala/gitbucket/core/controller/AccountController.scala
Normal file
@@ -0,0 +1,535 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.account.html
|
||||
import gitbucket.core.api._
|
||||
import gitbucket.core.helper
|
||||
import gitbucket.core.model.GroupMember
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.ssh.SshUtil
|
||||
import gitbucket.core.util.ControlUtil._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.StringUtil._
|
||||
import gitbucket.core.util._
|
||||
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
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
|
||||
|
||||
|
||||
class AccountController extends AccountControllerBase
|
||||
with AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
|
||||
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
|
||||
with AccessTokenService with WebHookService
|
||||
|
||||
|
||||
trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
self: AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
|
||||
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
|
||||
with AccessTokenService with WebHookService =>
|
||||
|
||||
case class AccountNewForm(userName: String, password: String, fullName: String, mailAddress: String,
|
||||
url: Option[String], fileId: Option[String])
|
||||
|
||||
case class AccountEditForm(password: Option[String], fullName: String, mailAddress: String,
|
||||
url: Option[String], fileId: Option[String], clearImage: Boolean)
|
||||
|
||||
case class SshKeyForm(title: String, publicKey: String)
|
||||
|
||||
case class PersonalTokenForm(note: String)
|
||||
|
||||
val newForm = mapping(
|
||||
"userName" -> trim(label("User name" , 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()))),
|
||||
"url" -> trim(label("URL" , optional(text(maxlength(200))))),
|
||||
"fileId" -> trim(label("File ID" , optional(text())))
|
||||
)(AccountNewForm.apply)
|
||||
|
||||
val editForm = mapping(
|
||||
"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")))),
|
||||
"url" -> trim(label("URL" , optional(text(maxlength(200))))),
|
||||
"fileId" -> trim(label("File ID" , optional(text()))),
|
||||
"clearImage" -> trim(label("Clear image" , boolean()))
|
||||
)(AccountEditForm.apply)
|
||||
|
||||
val sshKeyForm = mapping(
|
||||
"title" -> trim(label("Title", text(required, maxlength(100)))),
|
||||
"publicKey" -> trim(label("Key" , text(required, validPublicKey)))
|
||||
)(SshKeyForm.apply)
|
||||
|
||||
val personalTokenForm = mapping(
|
||||
"note" -> trim(label("Token", text(required, maxlength(100))))
|
||||
)(PersonalTokenForm.apply)
|
||||
|
||||
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)
|
||||
|
||||
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()))
|
||||
)(EditGroupForm.apply)
|
||||
|
||||
case class RepositoryCreationForm(owner: String, name: String, description: Option[String], isPrivate: Boolean, createReadme: Boolean)
|
||||
case class ForkRepositoryForm(owner: String, name: String)
|
||||
|
||||
val newRepositoryForm = mapping(
|
||||
"owner" -> trim(label("Owner" , text(required, maxlength(40), identifier, existsAccount))),
|
||||
"name" -> trim(label("Repository name", text(required, maxlength(40), identifier, uniqueRepository))),
|
||||
"description" -> trim(label("Description" , optional(text()))),
|
||||
"isPrivate" -> trim(label("Repository Type", boolean())),
|
||||
"createReadme" -> trim(label("Create README" , boolean()))
|
||||
)(RepositoryCreationForm.apply)
|
||||
|
||||
val forkRepositoryForm = mapping(
|
||||
"owner" -> trim(label("Repository owner", text(required))),
|
||||
"name" -> trim(label("Repository name", text(required)))
|
||||
)(ForkRepositoryForm.apply)
|
||||
|
||||
case class AccountForm(accountName: String)
|
||||
|
||||
val accountForm = mapping(
|
||||
"account" -> trim(label("Group/User name", text(required, validAccountName)))
|
||||
)(AccountForm.apply)
|
||||
|
||||
/**
|
||||
* Displays user information.
|
||||
*/
|
||||
get("/:userName") {
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).map { account =>
|
||||
params.getOrElse("tab", "repositories") match {
|
||||
// Public Activity
|
||||
case "activity" =>
|
||||
gitbucket.core.account.html.activity(account,
|
||||
if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
||||
getActivitiesByUser(userName, true))
|
||||
|
||||
// Members
|
||||
case "members" if(account.isGroupAccount) => {
|
||||
val members = getGroupMembers(account.userName)
|
||||
gitbucket.core.account.html.members(account, members.map(_.userName),
|
||||
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }))
|
||||
}
|
||||
|
||||
// Repositories
|
||||
case _ => {
|
||||
val members = getGroupMembers(account.userName)
|
||||
gitbucket.core.account.html.repositories(account,
|
||||
if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
||||
getVisibleRepositories(context.loginAccount, context.baseUrl, Some(userName)),
|
||||
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }))
|
||||
}
|
||||
}
|
||||
} getOrElse NotFound
|
||||
}
|
||||
|
||||
get("/:userName.atom") {
|
||||
val userName = params("userName")
|
||||
contentType = "application/atom+xml; type=feed"
|
||||
helper.xml.feed(getActivitiesByUser(userName, true))
|
||||
}
|
||||
|
||||
get("/:userName/_avatar"){
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).flatMap(_.image).map { image =>
|
||||
RawData(FileUtil.getMimeType(image), new java.io.File(getUserUploadDir(userName), image))
|
||||
} getOrElse {
|
||||
contentType = "image/png"
|
||||
Thread.currentThread.getContextClassLoader.getResourceAsStream("noimage.png")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).map { x =>
|
||||
html.edit(x, flash.get("info"))
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
post("/:userName/_edit", editForm)(oneselfOnly { form =>
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).map { account =>
|
||||
updateAccount(account.copy(
|
||||
password = form.password.map(sha1).getOrElse(account.password),
|
||||
fullName = form.fullName,
|
||||
mailAddress = form.mailAddress,
|
||||
url = form.url))
|
||||
|
||||
updateImage(userName, form.fileId, form.clearImage)
|
||||
flash += "info" -> "Account information has been updated."
|
||||
redirect(s"/${userName}/_edit")
|
||||
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
get("/:userName/_delete")(oneselfOnly {
|
||||
val userName = params("userName")
|
||||
|
||||
getAccountByUserName(userName, true).foreach { account =>
|
||||
// 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(isRemoved = true))
|
||||
}
|
||||
|
||||
session.invalidate
|
||||
redirect("/")
|
||||
})
|
||||
|
||||
get("/:userName/_ssh")(oneselfOnly {
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).map { x =>
|
||||
html.ssh(x, getPublicKeys(x.userName))
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
post("/:userName/_ssh", sshKeyForm)(oneselfOnly { form =>
|
||||
val userName = params("userName")
|
||||
addPublicKey(userName, form.title, form.publicKey)
|
||||
redirect(s"/${userName}/_ssh")
|
||||
})
|
||||
|
||||
get("/:userName/_ssh/delete/:id")(oneselfOnly {
|
||||
val userName = params("userName")
|
||||
val sshKeyId = params("id").toInt
|
||||
deletePublicKey(userName, sshKeyId)
|
||||
redirect(s"/${userName}/_ssh")
|
||||
})
|
||||
|
||||
get("/:userName/_application")(oneselfOnly {
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).map { x =>
|
||||
var tokens = getAccessTokens(x.userName)
|
||||
val generatedToken = flash.get("generatedToken") match {
|
||||
case Some((tokenId:Int, token:String)) => {
|
||||
val gt = tokens.find(_.accessTokenId == tokenId)
|
||||
gt.map{ t =>
|
||||
tokens = tokens.filterNot(_ == t)
|
||||
(t, token)
|
||||
}
|
||||
}
|
||||
case _ => None
|
||||
}
|
||||
html.application(x, tokens, generatedToken)
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
post("/:userName/_personalToken", personalTokenForm)(oneselfOnly { form =>
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).map { x =>
|
||||
val (tokenId, token) = generateAccessToken(userName, form.note)
|
||||
flash += "generatedToken" -> (tokenId, token)
|
||||
}
|
||||
redirect(s"/${userName}/_application")
|
||||
})
|
||||
|
||||
get("/:userName/_personalToken/delete/:id")(oneselfOnly {
|
||||
val userName = params("userName")
|
||||
val tokenId = params("id").toInt
|
||||
deleteAccessToken(userName, tokenId)
|
||||
redirect(s"/${userName}/_application")
|
||||
})
|
||||
|
||||
get("/register"){
|
||||
if(context.settings.allowAccountRegistration){
|
||||
if(context.loginAccount.isDefined){
|
||||
redirect("/")
|
||||
} else {
|
||||
html.register()
|
||||
}
|
||||
} else NotFound
|
||||
}
|
||||
|
||||
post("/register", newForm){ form =>
|
||||
if(context.settings.allowAccountRegistration){
|
||||
createAccount(form.userName, sha1(form.password), form.fullName, form.mailAddress, false, form.url)
|
||||
updateImage(form.userName, form.fileId, false)
|
||||
redirect("/signin")
|
||||
} else NotFound
|
||||
}
|
||||
|
||||
get("/groups/new")(usersOnly {
|
||||
html.group(None, List(GroupMember("", context.loginAccount.get.userName, true)))
|
||||
})
|
||||
|
||||
post("/groups/new", newGroupForm)(usersOnly { 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(s"/${form.groupName}")
|
||||
})
|
||||
|
||||
get("/:groupName/_editgroup")(managersOnly {
|
||||
defining(params("groupName")){ groupName =>
|
||||
html.group(getAccountByUserName(groupName, true), getGroupMembers(groupName))
|
||||
}
|
||||
})
|
||||
|
||||
get("/:groupName/_deletegroup")(managersOnly {
|
||||
defining(params("groupName")){ groupName =>
|
||||
// Remove from GROUP_MEMBER
|
||||
updateGroupMembers(groupName, Nil)
|
||||
// Remove repositories
|
||||
getRepositoryNamesOfUser(groupName).foreach { repositoryName =>
|
||||
deleteRepository(groupName, repositoryName)
|
||||
FileUtils.deleteDirectory(getRepositoryDir(groupName, repositoryName))
|
||||
FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName))
|
||||
FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName))
|
||||
}
|
||||
}
|
||||
redirect("/")
|
||||
})
|
||||
|
||||
post("/:groupName/_editgroup", editGroupForm)(managersOnly { 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, false)
|
||||
|
||||
// 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(s"/${form.groupName}")
|
||||
|
||||
} getOrElse NotFound
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Show the new repository form.
|
||||
*/
|
||||
get("/new")(usersOnly {
|
||||
html.newrepo(getGroupsByUserName(context.loginAccount.get.userName), context.settings.isCreateRepoOptionPublic)
|
||||
})
|
||||
|
||||
/**
|
||||
* Create new repository.
|
||||
*/
|
||||
post("/new", newRepositoryForm)(usersOnly { form =>
|
||||
LockUtil.lock(s"${form.owner}/${form.name}"){
|
||||
if(getRepository(form.owner, form.name, context.baseUrl).isEmpty){
|
||||
val ownerAccount = getAccountByUserName(form.owner).get
|
||||
val loginAccount = context.loginAccount.get
|
||||
val loginUserName = loginAccount.userName
|
||||
|
||||
// Insert to the database at first
|
||||
createRepository(form.name, form.owner, form.description, form.isPrivate)
|
||||
|
||||
// Add collaborators for group repository
|
||||
if(ownerAccount.isGroupAccount){
|
||||
getGroupMembers(form.owner).foreach { member =>
|
||||
addCollaborator(form.owner, form.name, member.userName)
|
||||
}
|
||||
}
|
||||
|
||||
// Insert default labels
|
||||
insertDefaultLabels(form.owner, form.name)
|
||||
|
||||
// Create the actual repository
|
||||
val gitdir = getRepositoryDir(form.owner, form.name)
|
||||
JGitUtil.initRepository(gitdir)
|
||||
|
||||
if(form.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(form.description.nonEmpty){
|
||||
form.name + "\n" +
|
||||
"===============\n" +
|
||||
"\n" +
|
||||
form.description.get
|
||||
} else {
|
||||
form.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, form.owner, form.name)
|
||||
|
||||
// Record activity
|
||||
recordCreateRepositoryActivity(form.owner, form.name, loginUserName)
|
||||
}
|
||||
|
||||
// redirect to the repository
|
||||
redirect(s"/${form.owner}/${form.name}")
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/fork")(readableUsersOnly { repository =>
|
||||
val loginAccount = context.loginAccount.get
|
||||
val loginUserName = loginAccount.userName
|
||||
val groups = getGroupsByUserName(loginUserName)
|
||||
groups match {
|
||||
case _: List[String] =>
|
||||
val managerPermissions = groups.map { group =>
|
||||
val members = getGroupMembers(group)
|
||||
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager })
|
||||
}
|
||||
helper.html.forkrepository(
|
||||
repository,
|
||||
(groups zip managerPermissions).toMap
|
||||
)
|
||||
case _ => redirect(s"/${loginUserName}")
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/fork", accountForm)(readableUsersOnly { (form, repository) =>
|
||||
val loginAccount = context.loginAccount.get
|
||||
val loginUserName = loginAccount.userName
|
||||
val accountName = form.accountName
|
||||
|
||||
LockUtil.lock(s"${accountName}/${repository.name}"){
|
||||
if(getRepository(accountName, repository.name, baseUrl).isDefined ||
|
||||
(accountName != loginUserName && !getGroupsByUserName(loginUserName).contains(accountName))){
|
||||
// redirect to the repository if repository already exists
|
||||
redirect(s"/${accountName}/${repository.name}")
|
||||
} else {
|
||||
// Insert to the database at first
|
||||
val originUserName = repository.repository.originUserName.getOrElse(repository.owner)
|
||||
val originRepositoryName = repository.repository.originRepositoryName.getOrElse(repository.name)
|
||||
|
||||
createRepository(
|
||||
repositoryName = repository.name,
|
||||
userName = accountName,
|
||||
description = repository.repository.description,
|
||||
isPrivate = repository.repository.isPrivate,
|
||||
originRepositoryName = Some(originRepositoryName),
|
||||
originUserName = Some(originUserName),
|
||||
parentRepositoryName = Some(repository.name),
|
||||
parentUserName = Some(repository.owner)
|
||||
)
|
||||
|
||||
// Insert default labels
|
||||
insertDefaultLabels(accountName, repository.name)
|
||||
|
||||
// clone repository actually
|
||||
JGitUtil.cloneRepository(
|
||||
getRepositoryDir(repository.owner, repository.name),
|
||||
getRepositoryDir(accountName, repository.name))
|
||||
|
||||
// Create Wiki repository
|
||||
JGitUtil.cloneRepository(
|
||||
getWikiRepositoryDir(repository.owner, repository.name),
|
||||
getWikiRepositoryDir(accountName, repository.name))
|
||||
|
||||
// Record activity
|
||||
recordForkActivity(repository.owner, repository.name, loginUserName, accountName)
|
||||
// redirect to the repository
|
||||
redirect(s"/${accountName}/${repository.name}")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
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(){
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
if(getAccountByUserName(value).isEmpty) Some("User or group does not exist.") else None
|
||||
}
|
||||
|
||||
private def uniqueRepository: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] =
|
||||
params.get("owner").flatMap { userName =>
|
||||
getRepositoryNamesOfUser(userName).find(_ == value).map(_ => "Repository already exists.")
|
||||
}
|
||||
}
|
||||
|
||||
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.")
|
||||
}
|
||||
}
|
||||
|
||||
private def validPublicKey: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = SshUtil.str2PublicKey(value) match {
|
||||
case Some(_) => None
|
||||
case None => Some("Key is invalid.")
|
||||
}
|
||||
}
|
||||
|
||||
private def validAccountName: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
getAccountByUserName(value) match {
|
||||
case Some(_) => None
|
||||
case None => Some("Invalid Group/User Account.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
class AnonymousAccessController extends AnonymousAccessControllerBase
|
||||
|
||||
trait AnonymousAccessControllerBase extends ControllerBase {
|
||||
get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) {
|
||||
if(!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
|
||||
!context.currentPath.startsWith("/register")) {
|
||||
Unauthorized()
|
||||
} else {
|
||||
pass()
|
||||
}
|
||||
}
|
||||
}
|
||||
237
src/main/scala/gitbucket/core/controller/ControllerBase.scala
Normal file
237
src/main/scala/gitbucket/core/controller/ControllerBase.scala
Normal file
@@ -0,0 +1,237 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.api.ApiError
|
||||
import gitbucket.core.model.Account
|
||||
import gitbucket.core.service.{AccountService, SystemSettingsService}
|
||||
import gitbucket.core.util.ControlUtil._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util._
|
||||
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.json4s._
|
||||
import org.scalatra._
|
||||
import org.scalatra.i18n._
|
||||
import org.scalatra.json._
|
||||
|
||||
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
|
||||
import javax.servlet.{FilterChain, ServletResponse, ServletRequest}
|
||||
|
||||
import scala.util.Try
|
||||
|
||||
|
||||
/**
|
||||
* Provides generic features for controller implementations.
|
||||
*/
|
||||
abstract class ControllerBase extends ScalatraFilter
|
||||
with ClientSideValidationFormSupport with JacksonJsonSupport with I18nSupport with FlashMapSupport with Validations
|
||||
with SystemSettingsService {
|
||||
|
||||
implicit val jsonFormats = DefaultFormats
|
||||
|
||||
// TODO Scala 2.11
|
||||
// // Don't set content type via Accept header.
|
||||
// override def format(implicit request: HttpServletRequest) = ""
|
||||
|
||||
override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = try {
|
||||
val httpRequest = request.asInstanceOf[HttpServletRequest]
|
||||
val httpResponse = response.asInstanceOf[HttpServletResponse]
|
||||
val context = request.getServletContext.getContextPath
|
||||
val path = httpRequest.getRequestURI.substring(context.length)
|
||||
|
||||
if(path.startsWith("/console/")){
|
||||
val account = httpRequest.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]
|
||||
val baseUrl = this.baseUrl(httpRequest)
|
||||
if(account == null){
|
||||
// Redirect to login form
|
||||
httpResponse.sendRedirect(baseUrl + "/signin?redirect=" + StringUtil.urlEncode(path))
|
||||
} else if(account.isAdmin){
|
||||
// H2 Console (administrators only)
|
||||
chain.doFilter(request, response)
|
||||
} else {
|
||||
// Redirect to dashboard
|
||||
httpResponse.sendRedirect(baseUrl + "/")
|
||||
}
|
||||
} else if(path.startsWith("/git/")){
|
||||
// Git repository
|
||||
chain.doFilter(request, response)
|
||||
} else {
|
||||
if(path.startsWith("/api/v3/")){
|
||||
httpRequest.setAttribute(Keys.Request.APIv3, true)
|
||||
}
|
||||
// Scalatra actions
|
||||
super.doFilter(request, response, chain)
|
||||
}
|
||||
} finally {
|
||||
contextCache.remove();
|
||||
}
|
||||
|
||||
private val contextCache = new java.lang.ThreadLocal[Context]()
|
||||
|
||||
/**
|
||||
* Returns the context object for the request.
|
||||
*/
|
||||
implicit def context: Context = {
|
||||
contextCache.get match {
|
||||
case null => {
|
||||
val context = Context(loadSystemSettings(), LoginAccount, request)
|
||||
contextCache.set(context)
|
||||
context
|
||||
}
|
||||
case context => context
|
||||
}
|
||||
}
|
||||
|
||||
private def LoginAccount: Option[Account] = request.getAs[Account](Keys.Session.LoginAccount).orElse(session.getAs[Account](Keys.Session.LoginAccount))
|
||||
|
||||
def ajaxGet(path : String)(action : => Any) : Route =
|
||||
super.get(path){
|
||||
request.setAttribute(Keys.Request.Ajax, "true")
|
||||
action
|
||||
}
|
||||
|
||||
override def ajaxGet[T](path : String, form : ValueType[T])(action : T => Any) : Route =
|
||||
super.ajaxGet(path, form){ form =>
|
||||
request.setAttribute(Keys.Request.Ajax, "true")
|
||||
action(form)
|
||||
}
|
||||
|
||||
def ajaxPost(path : String)(action : => Any) : Route =
|
||||
super.post(path){
|
||||
request.setAttribute(Keys.Request.Ajax, "true")
|
||||
action
|
||||
}
|
||||
|
||||
override def ajaxPost[T](path : String, form : ValueType[T])(action : T => Any) : Route =
|
||||
super.ajaxPost(path, form){ form =>
|
||||
request.setAttribute(Keys.Request.Ajax, "true")
|
||||
action(form)
|
||||
}
|
||||
|
||||
protected def NotFound() =
|
||||
if(request.hasAttribute(Keys.Request.Ajax)){
|
||||
org.scalatra.NotFound()
|
||||
} else if(request.hasAttribute(Keys.Request.APIv3)){
|
||||
contentType = formats("json")
|
||||
org.scalatra.NotFound(ApiError("Not Found"))
|
||||
} else {
|
||||
org.scalatra.NotFound(gitbucket.core.html.error("Not Found"))
|
||||
}
|
||||
|
||||
protected def Unauthorized()(implicit context: Context) =
|
||||
if(request.hasAttribute(Keys.Request.Ajax)){
|
||||
org.scalatra.Unauthorized()
|
||||
} else if(request.hasAttribute(Keys.Request.APIv3)){
|
||||
contentType = formats("json")
|
||||
org.scalatra.Unauthorized(ApiError("Requires authentication"))
|
||||
} else {
|
||||
if(context.loginAccount.isDefined){
|
||||
org.scalatra.Unauthorized(redirect("/"))
|
||||
} else {
|
||||
if(request.getMethod.toUpperCase == "POST"){
|
||||
org.scalatra.Unauthorized(redirect("/signin"))
|
||||
} else {
|
||||
org.scalatra.Unauthorized(redirect("/signin?redirect=" + StringUtil.urlEncode(
|
||||
defining(request.getQueryString){ queryString =>
|
||||
request.getRequestURI.substring(request.getContextPath.length) + (if(queryString != null) "?" + queryString else "")
|
||||
}
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Scala 2.11
|
||||
override def url(path: String, params: Iterable[(String, Any)] = Iterable.empty,
|
||||
includeContextPath: Boolean = true, includeServletPath: Boolean = true,
|
||||
absolutize: Boolean = true, withSessionId: Boolean = true)
|
||||
(implicit request: HttpServletRequest, response: HttpServletResponse): String =
|
||||
if (path.startsWith("http")) path
|
||||
else baseUrl + super.url(path, params, false, false, false)
|
||||
|
||||
/**
|
||||
* Use this method to response the raw data against XSS.
|
||||
*/
|
||||
protected def RawData[T](contentType: String, rawData: T): T = {
|
||||
if(contentType.split(";").head.trim.toLowerCase.startsWith("text/html")){
|
||||
this.contentType = "text/plain"
|
||||
} else {
|
||||
this.contentType = contentType
|
||||
}
|
||||
response.addHeader("X-Content-Type-Options", "nosniff")
|
||||
rawData
|
||||
}
|
||||
|
||||
// jenkins send message as 'application/x-www-form-urlencoded' but scalatra already parsed as multi-part-request.
|
||||
def extractFromJsonBody[A](implicit request:HttpServletRequest, mf:Manifest[A]): Option[A] = {
|
||||
(request.contentType.map(_.split(";").head.toLowerCase) match{
|
||||
case Some("application/x-www-form-urlencoded") => multiParams.keys.headOption.map(parse(_))
|
||||
case Some("application/json") => Some(parsedBody)
|
||||
case _ => Some(parse(request.body))
|
||||
}).filterNot(_ == JNothing).flatMap(j => Try(j.extract[A]).toOption)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Context object for the current request.
|
||||
*/
|
||||
case class Context(settings: SystemSettingsService.SystemSettings, loginAccount: Option[Account], request: HttpServletRequest){
|
||||
|
||||
val path = settings.baseUrl.getOrElse(request.getContextPath)
|
||||
val currentPath = request.getRequestURI.substring(request.getContextPath.length)
|
||||
val baseUrl = settings.baseUrl(request)
|
||||
val host = new java.net.URL(baseUrl).getHost
|
||||
|
||||
/**
|
||||
* Get object from cache.
|
||||
*
|
||||
* If object has not been cached with the specified key then retrieves by given action.
|
||||
* Cached object are available during a request.
|
||||
*/
|
||||
def cache[A](key: String)(action: => A): A =
|
||||
defining(Keys.Request.Cache(key)){ cacheKey =>
|
||||
Option(request.getAttribute(cacheKey).asInstanceOf[A]).getOrElse {
|
||||
val newObject = action
|
||||
request.setAttribute(cacheKey, newObject)
|
||||
newObject
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Base trait for controllers which manages account information.
|
||||
*/
|
||||
trait AccountManagementControllerBase extends ControllerBase {
|
||||
self: AccountService =>
|
||||
|
||||
protected def updateImage(userName: String, fileId: Option[String], clearImage: Boolean): Unit =
|
||||
if(clearImage){
|
||||
getAccountByUserName(userName).flatMap(_.image).map { image =>
|
||||
new java.io.File(getUserUploadDir(userName), image).delete()
|
||||
updateAvatarImage(userName, None)
|
||||
}
|
||||
} else {
|
||||
fileId.map { fileId =>
|
||||
val filename = "avatar." + FileUtil.getExtension(session.getAndRemove(Keys.Session.Upload(fileId)).get)
|
||||
FileUtils.moveFile(
|
||||
new java.io.File(getTemporaryDir(session.getId), fileId),
|
||||
new java.io.File(getUserUploadDir(userName), filename)
|
||||
)
|
||||
updateAvatarImage(userName, Some(filename))
|
||||
}
|
||||
}
|
||||
|
||||
protected def uniqueUserName: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
getAccountByUserName(value, true).map { _ => "User already exists." }
|
||||
}
|
||||
|
||||
protected def uniqueMailAddress(paramName: String = ""): Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] =
|
||||
getAccountByMailAddress(value, true)
|
||||
.filter { x => if(paramName.isEmpty) true else Some(x.userName) != params.get(paramName) }
|
||||
.map { _ => "Mail address is already registered." }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.dashboard.html
|
||||
import gitbucket.core.service.{RepositoryService, PullRequestService, AccountService, IssuesService}
|
||||
import gitbucket.core.util.{StringUtil, Keys, UsersAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.service.IssuesService._
|
||||
|
||||
class DashboardController extends DashboardControllerBase
|
||||
with IssuesService with PullRequestService with RepositoryService with AccountService
|
||||
with UsersAuthenticator
|
||||
|
||||
trait DashboardControllerBase extends ControllerBase {
|
||||
self: IssuesService with PullRequestService with RepositoryService with AccountService
|
||||
with UsersAuthenticator =>
|
||||
|
||||
get("/dashboard/issues")(usersOnly {
|
||||
val q = request.getParameter("q")
|
||||
val account = context.loginAccount.get
|
||||
Option(q).map { q =>
|
||||
val condition = IssueSearchCondition(q, Map[String, Int]())
|
||||
q match {
|
||||
case q if(q.contains("is:pr")) => redirect(s"/dashboard/pulls?q=${StringUtil.urlEncode(q)}")
|
||||
case q if(q.contains(s"author:${account.userName}")) => redirect(s"/dashboard/issues/created_by${condition.toURL}")
|
||||
case q if(q.contains(s"assignee:${account.userName}")) => redirect(s"/dashboard/issues/assigned${condition.toURL}")
|
||||
case q if(q.contains(s"mentions:${account.userName}")) => redirect(s"/dashboard/issues/mentioned${condition.toURL}")
|
||||
case _ => searchIssues("created_by")
|
||||
}
|
||||
} getOrElse {
|
||||
searchIssues("created_by")
|
||||
}
|
||||
})
|
||||
|
||||
get("/dashboard/issues/assigned")(usersOnly {
|
||||
searchIssues("assigned")
|
||||
})
|
||||
|
||||
get("/dashboard/issues/created_by")(usersOnly {
|
||||
searchIssues("created_by")
|
||||
})
|
||||
|
||||
get("/dashboard/issues/mentioned")(usersOnly {
|
||||
searchIssues("mentioned")
|
||||
})
|
||||
|
||||
get("/dashboard/pulls")(usersOnly {
|
||||
val q = request.getParameter("q")
|
||||
val account = context.loginAccount.get
|
||||
Option(q).map { q =>
|
||||
val condition = IssueSearchCondition(q, Map[String, Int]())
|
||||
q match {
|
||||
case q if(q.contains("is:issue")) => redirect(s"/dashboard/issues?q=${StringUtil.urlEncode(q)}")
|
||||
case q if(q.contains(s"author:${account.userName}")) => redirect(s"/dashboard/pulls/created_by${condition.toURL}")
|
||||
case q if(q.contains(s"assignee:${account.userName}")) => redirect(s"/dashboard/pulls/assigned${condition.toURL}")
|
||||
case q if(q.contains(s"mentions:${account.userName}")) => redirect(s"/dashboard/pulls/mentioned${condition.toURL}")
|
||||
case _ => searchPullRequests("created_by")
|
||||
}
|
||||
} getOrElse {
|
||||
searchPullRequests("created_by")
|
||||
}
|
||||
})
|
||||
|
||||
get("/dashboard/pulls/created_by")(usersOnly {
|
||||
searchPullRequests("created_by")
|
||||
})
|
||||
|
||||
get("/dashboard/pulls/assigned")(usersOnly {
|
||||
searchPullRequests("assigned")
|
||||
})
|
||||
|
||||
get("/dashboard/pulls/mentioned")(usersOnly {
|
||||
searchPullRequests("mentioned")
|
||||
})
|
||||
|
||||
private def getOrCreateCondition(key: String, filter: String, userName: String) = {
|
||||
val condition = session.putAndGet(key, if(request.hasQueryString){
|
||||
val q = request.getParameter("q")
|
||||
if(q == null){
|
||||
IssueSearchCondition(request)
|
||||
} else {
|
||||
IssueSearchCondition(q, Map[String, Int]())
|
||||
}
|
||||
} else session.getAs[IssueSearchCondition](key).getOrElse(IssueSearchCondition()))
|
||||
|
||||
filter match {
|
||||
case "assigned" => condition.copy(assigned = Some(userName), author = None , mentioned = None)
|
||||
case "mentioned" => condition.copy(assigned = None , author = None , mentioned = Some(userName))
|
||||
case _ => condition.copy(assigned = None , author = Some(userName), mentioned = None)
|
||||
}
|
||||
}
|
||||
|
||||
private def searchIssues(filter: String) = {
|
||||
import IssuesService._
|
||||
|
||||
val userName = context.loginAccount.get.userName
|
||||
val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName)
|
||||
val userRepos = getUserRepositories(userName, context.baseUrl, true).map(repo => repo.owner -> repo.name)
|
||||
val page = IssueSearchCondition.page(request)
|
||||
|
||||
html.issues(
|
||||
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, userRepos: _*),
|
||||
page,
|
||||
countIssue(condition.copy(state = "open" ), false, userRepos: _*),
|
||||
countIssue(condition.copy(state = "closed"), false, userRepos: _*),
|
||||
filter match {
|
||||
case "assigned" => condition.copy(assigned = Some(userName))
|
||||
case "mentioned" => condition.copy(mentioned = Some(userName))
|
||||
case _ => condition.copy(author = Some(userName))
|
||||
},
|
||||
filter,
|
||||
getGroupNames(userName))
|
||||
}
|
||||
|
||||
private def searchPullRequests(filter: String) = {
|
||||
import IssuesService._
|
||||
import PullRequestService._
|
||||
|
||||
val userName = context.loginAccount.get.userName
|
||||
val condition = getOrCreateCondition(Keys.Session.DashboardPulls, filter, userName)
|
||||
val allRepos = getAllRepositories(userName)
|
||||
val page = IssueSearchCondition.page(request)
|
||||
|
||||
html.pulls(
|
||||
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, allRepos: _*),
|
||||
page,
|
||||
countIssue(condition.copy(state = "open" ), true, allRepos: _*),
|
||||
countIssue(condition.copy(state = "closed"), true, allRepos: _*),
|
||||
filter match {
|
||||
case "assigned" => condition.copy(assigned = Some(userName))
|
||||
case "mentioned" => condition.copy(mentioned = Some(userName))
|
||||
case _ => condition.copy(author = Some(userName))
|
||||
},
|
||||
filter,
|
||||
getGroupNames(userName))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.util.{Keys, FileUtil}
|
||||
import gitbucket.core.util.ControlUtil._
|
||||
import gitbucket.core.util.Directory._
|
||||
import org.scalatra._
|
||||
import org.scalatra.servlet.{MultipartConfig, FileUploadSupport, FileItem}
|
||||
import org.apache.commons.io.FileUtils
|
||||
|
||||
/**
|
||||
* Provides Ajax based file upload functionality.
|
||||
*
|
||||
* This servlet saves uploaded file.
|
||||
*/
|
||||
class FileUploadController extends ScalatraServlet with FileUploadSupport {
|
||||
|
||||
configureMultipartHandling(MultipartConfig(maxFileSize = Some(3 * 1024 * 1024)))
|
||||
|
||||
post("/image"){
|
||||
execute { (file, fileId) =>
|
||||
FileUtils.writeByteArrayToFile(new java.io.File(getTemporaryDir(session.getId), fileId), file.get)
|
||||
session += Keys.Session.Upload(fileId) -> file.name
|
||||
}
|
||||
}
|
||||
|
||||
post("/image/:owner/:repository"){
|
||||
execute { (file, fileId) =>
|
||||
FileUtils.writeByteArrayToFile(new java.io.File(
|
||||
getAttachedDir(params("owner"), params("repository")),
|
||||
fileId + "." + FileUtil.getExtension(file.getName)), file.get)
|
||||
}
|
||||
}
|
||||
|
||||
private def execute(f: (FileItem, String) => Unit) = fileParams.get("file") match {
|
||||
case Some(file) if(FileUtil.isImage(file.name)) =>
|
||||
defining(FileUtil.generateFileId){ fileId =>
|
||||
f(file, fileId)
|
||||
|
||||
Ok(fileId)
|
||||
}
|
||||
case _ => BadRequest
|
||||
}
|
||||
|
||||
}
|
||||
122
src/main/scala/gitbucket/core/controller/IndexController.scala
Normal file
122
src/main/scala/gitbucket/core/controller/IndexController.scala
Normal file
@@ -0,0 +1,122 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.api._
|
||||
import gitbucket.core.helper.xml
|
||||
import gitbucket.core.html
|
||||
import gitbucket.core.model.Account
|
||||
import gitbucket.core.service.{RepositoryService, ActivityService, AccountService}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.{LDAPUtil, Keys, UsersAuthenticator}
|
||||
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
|
||||
class IndexController extends IndexControllerBase
|
||||
with RepositoryService with ActivityService with AccountService with UsersAuthenticator
|
||||
|
||||
|
||||
trait IndexControllerBase extends ControllerBase {
|
||||
self: RepositoryService with ActivityService with AccountService with UsersAuthenticator =>
|
||||
|
||||
case class SignInForm(userName: String, password: String)
|
||||
|
||||
val form = mapping(
|
||||
"userName" -> trim(label("Username", text(required))),
|
||||
"password" -> trim(label("Password", text(required)))
|
||||
)(SignInForm.apply)
|
||||
|
||||
get("/"){
|
||||
val loginAccount = context.loginAccount
|
||||
if(loginAccount.isEmpty) {
|
||||
html.index(getRecentActivities(),
|
||||
getVisibleRepositories(loginAccount, context.baseUrl, withoutPhysicalInfo = true),
|
||||
loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl, withoutPhysicalInfo = true) }.getOrElse(Nil)
|
||||
)
|
||||
} else {
|
||||
val loginUserName = loginAccount.get.userName
|
||||
val loginUserGroups = getGroupsByUserName(loginUserName)
|
||||
var visibleOwnerSet : Set[String] = Set(loginUserName)
|
||||
|
||||
visibleOwnerSet ++= loginUserGroups
|
||||
|
||||
html.index(getRecentActivitiesByOwners(visibleOwnerSet),
|
||||
getVisibleRepositories(loginAccount, context.baseUrl, withoutPhysicalInfo = true),
|
||||
loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl, withoutPhysicalInfo = true) }.getOrElse(Nil)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get("/signin"){
|
||||
val redirect = params.get("redirect")
|
||||
if(redirect.isDefined && redirect.get.startsWith("/")){
|
||||
flash += Keys.Flash.Redirect -> redirect.get
|
||||
}
|
||||
html.signin()
|
||||
}
|
||||
|
||||
post("/signin", form){ form =>
|
||||
authenticate(context.settings, form.userName, form.password) match {
|
||||
case Some(account) => signin(account)
|
||||
case None => redirect("/signin")
|
||||
}
|
||||
}
|
||||
|
||||
get("/signout"){
|
||||
session.invalidate
|
||||
redirect("/")
|
||||
}
|
||||
|
||||
get("/activities.atom"){
|
||||
contentType = "application/atom+xml; type=feed"
|
||||
xml.feed(getRecentActivities())
|
||||
}
|
||||
|
||||
/**
|
||||
* Set account information into HttpSession and redirect.
|
||||
*/
|
||||
private def signin(account: Account) = {
|
||||
session.setAttribute(Keys.Session.LoginAccount, account)
|
||||
updateLastLoginDate(account.userName)
|
||||
|
||||
if(LDAPUtil.isDummyMailAddress(account)) {
|
||||
redirect("/" + account.userName + "/_edit")
|
||||
}
|
||||
|
||||
flash.get(Keys.Flash.Redirect).asInstanceOf[Option[String]].map { redirectUrl =>
|
||||
if(redirectUrl.stripSuffix("/") == request.getContextPath){
|
||||
redirect("/")
|
||||
} else {
|
||||
redirect(redirectUrl)
|
||||
}
|
||||
}.getOrElse {
|
||||
redirect("/")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON API for collaborator completion.
|
||||
*/
|
||||
get("/_user/proposals")(usersOnly {
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map("options" -> getAllUsers().filter(!_.isGroupAccount).map(_.userName).toArray)
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* JSON APU for checking user existence.
|
||||
*/
|
||||
post("/_user/existence")(usersOnly {
|
||||
getAccountByUserName(params("userName")).isDefined
|
||||
})
|
||||
|
||||
/**
|
||||
* @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."))
|
||||
}
|
||||
}
|
||||
469
src/main/scala/gitbucket/core/controller/IssuesController.scala
Normal file
469
src/main/scala/gitbucket/core/controller/IssuesController.scala
Normal file
@@ -0,0 +1,469 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.api._
|
||||
import gitbucket.core.issues.html
|
||||
import gitbucket.core.model.Issue
|
||||
import gitbucket.core.service.IssuesService._
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.ControlUtil._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.view
|
||||
import gitbucket.core.view.Markdown
|
||||
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.scalatra.Ok
|
||||
|
||||
|
||||
class IssuesController extends IssuesControllerBase
|
||||
with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
|
||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with WebHookIssueCommentService
|
||||
|
||||
trait IssuesControllerBase extends ControllerBase {
|
||||
self: IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
|
||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with WebHookIssueCommentService =>
|
||||
|
||||
case class IssueCreateForm(title: String, content: Option[String],
|
||||
assignedUserName: Option[String], milestoneId: Option[Int], labelNames: Option[String])
|
||||
case class CommentForm(issueId: Int, content: String)
|
||||
case class IssueStateForm(issueId: Int, content: Option[String])
|
||||
|
||||
val issueCreateForm = mapping(
|
||||
"title" -> trim(label("Title", text(required))),
|
||||
"content" -> trim(optional(text())),
|
||||
"assignedUserName" -> trim(optional(text())),
|
||||
"milestoneId" -> trim(optional(number())),
|
||||
"labelNames" -> trim(optional(text()))
|
||||
)(IssueCreateForm.apply)
|
||||
|
||||
val issueTitleEditForm = mapping(
|
||||
"title" -> trim(label("Title", text(required)))
|
||||
)(x => x)
|
||||
val issueEditForm = mapping(
|
||||
"content" -> trim(optional(text()))
|
||||
)(x => x)
|
||||
|
||||
val commentForm = mapping(
|
||||
"issueId" -> label("Issue Id", number()),
|
||||
"content" -> trim(label("Comment", text(required)))
|
||||
)(CommentForm.apply)
|
||||
|
||||
val issueStateForm = mapping(
|
||||
"issueId" -> label("Issue Id", number()),
|
||||
"content" -> trim(optional(text()))
|
||||
)(IssueStateForm.apply)
|
||||
|
||||
get("/:owner/:repository/issues")(referrersOnly { repository =>
|
||||
val q = request.getParameter("q")
|
||||
if(Option(q).exists(_.contains("is:pr"))){
|
||||
redirect(s"/${repository.owner}/${repository.name}/pulls?q=" + StringUtil.urlEncode(q))
|
||||
} else {
|
||||
searchIssues(repository)
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
|
||||
defining(repository.owner, repository.name, params("id")){ case (owner, name, issueId) =>
|
||||
getIssue(owner, name, issueId) map {
|
||||
html.issue(
|
||||
_,
|
||||
getComments(owner, name, issueId.toInt),
|
||||
getIssueLabels(owner, name, issueId.toInt),
|
||||
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
|
||||
getMilestonesWithIssueCount(owner, name),
|
||||
getLabels(owner, name),
|
||||
hasWritePermission(owner, name, context.loginAccount),
|
||||
repository)
|
||||
} getOrElse NotFound
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 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) => ApiComment(issueComment, ApiUser(user)) })
|
||||
}).getOrElse(NotFound)
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
html.create(
|
||||
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
|
||||
getMilestones(owner, name),
|
||||
getLabels(owner, name),
|
||||
hasWritePermission(owner, name, context.loginAccount),
|
||||
repository)
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) =>
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
val writable = hasWritePermission(owner, name, context.loginAccount)
|
||||
val userName = context.loginAccount.get.userName
|
||||
|
||||
// insert issue
|
||||
val issueId = createIssue(owner, name, userName, form.title, form.content,
|
||||
if(writable) form.assignedUserName else None,
|
||||
if(writable) form.milestoneId else None)
|
||||
|
||||
// insert labels
|
||||
if(writable){
|
||||
form.labelNames.map { value =>
|
||||
val labels = getLabels(owner, name)
|
||||
value.split(",").foreach { labelName =>
|
||||
labels.find(_.labelName == labelName).map { label =>
|
||||
registerIssueLabel(owner, name, issueId, label.labelId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// record activity
|
||||
recordCreateIssueActivity(owner, name, userName, issueId, form.title)
|
||||
|
||||
getIssue(owner, name, issueId.toString).foreach { issue =>
|
||||
// extract references and create refer comment
|
||||
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""))
|
||||
|
||||
// call web hooks
|
||||
callIssuesWebHook("opened", repository, issue, context.baseUrl, context.loginAccount.get)
|
||||
}
|
||||
|
||||
// notifications
|
||||
Notifier().toNotify(repository, issueId, form.content.getOrElse("")){
|
||||
Notifier.msgIssue(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
|
||||
}
|
||||
|
||||
redirect(s"/${owner}/${name}/issues/${issueId}")
|
||||
}
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/edit_title/:id", issueTitleEditForm)(readableUsersOnly { (title, repository) =>
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
getIssue(owner, name, params("id")).map { issue =>
|
||||
if(isEditable(owner, name, issue.openedUserName)){
|
||||
// update issue
|
||||
updateIssue(owner, name, issue.issueId, title, issue.content)
|
||||
// extract references and create refer comment
|
||||
createReferComment(owner, name, issue.copy(title = title), title)
|
||||
|
||||
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
||||
} else Unauthorized
|
||||
} getOrElse NotFound
|
||||
}
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (content, repository) =>
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
getIssue(owner, name, params("id")).map { issue =>
|
||||
if(isEditable(owner, name, issue.openedUserName)){
|
||||
// update issue
|
||||
updateIssue(owner, name, issue.issueId, issue.title, content)
|
||||
// extract references and create refer comment
|
||||
createReferComment(owner, name, issue, content.getOrElse(""))
|
||||
|
||||
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
||||
} else Unauthorized
|
||||
} getOrElse NotFound
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
|
||||
handleComment(form.issueId, Some(form.content), repository)() map { case (issue, id) =>
|
||||
redirect(s"/${repository.owner}/${repository.name}/${
|
||||
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
|
||||
} 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, ApiUser(context.loginAccount.get)))
|
||||
}) getOrElse NotFound
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
|
||||
handleComment(form.issueId, form.content, repository)() map { case (issue, id) =>
|
||||
redirect(s"/${repository.owner}/${repository.name}/${
|
||||
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
getComment(owner, name, params("id")).map { comment =>
|
||||
if(isEditable(owner, name, comment.commentedUserName)){
|
||||
updateComment(comment.commentId, form.content)
|
||||
redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}")
|
||||
} else Unauthorized
|
||||
} getOrElse NotFound
|
||||
}
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issue_comments/delete/:id")(readableUsersOnly { repository =>
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
getComment(owner, name, params("id")).map { comment =>
|
||||
if(isEditable(owner, name, comment.commentedUserName)){
|
||||
Ok(deleteComment(comment.commentId))
|
||||
} else Unauthorized
|
||||
} getOrElse NotFound
|
||||
}
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository =>
|
||||
getIssue(repository.owner, repository.name, params("id")) map { x =>
|
||||
if(isEditable(x.userName, x.repositoryName, x.openedUserName)){
|
||||
params.get("dataType") collect {
|
||||
case t if t == "html" => html.editissue(
|
||||
x.content, x.issueId, x.userName, x.repositoryName)
|
||||
} getOrElse {
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map("title" -> x.title,
|
||||
"content" -> Markdown.toHtml(x.content getOrElse "No description given.",
|
||||
repository, false, true, true, isEditable(x.userName, x.repositoryName, x.openedUserName))
|
||||
))
|
||||
}
|
||||
} else Unauthorized
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository =>
|
||||
getComment(repository.owner, repository.name, params("id")) map { x =>
|
||||
if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
|
||||
params.get("dataType") collect {
|
||||
case t if t == "html" => html.editcomment(
|
||||
x.content, x.commentId, x.userName, x.repositoryName)
|
||||
} getOrElse {
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map("content" -> view.Markdown.toHtml(x.content,
|
||||
repository, false, true, true, isEditable(x.userName, x.repositoryName, x.commentedUserName))
|
||||
))
|
||||
}
|
||||
} else Unauthorized
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/label/new")(collaboratorsOnly { repository =>
|
||||
defining(params("id").toInt){ issueId =>
|
||||
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
||||
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||
}
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/label/delete")(collaboratorsOnly { repository =>
|
||||
defining(params("id").toInt){ issueId =>
|
||||
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
||||
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||
}
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/assign")(collaboratorsOnly { repository =>
|
||||
updateAssignedUserName(repository.owner, repository.name, params("id").toInt, assignedUserName("assignedUserName"))
|
||||
Ok("updated")
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/milestone")(collaboratorsOnly { repository =>
|
||||
updateMilestoneId(repository.owner, repository.name, params("id").toInt, milestoneId("milestoneId"))
|
||||
milestoneId("milestoneId").map { milestoneId =>
|
||||
getMilestonesWithIssueCount(repository.owner, repository.name)
|
||||
.find(_._1.milestoneId == milestoneId).map { case (_, openCount, closeCount) =>
|
||||
gitbucket.core.issues.milestones.html.progress(openCount + closeCount, closeCount)
|
||||
} getOrElse NotFound
|
||||
} getOrElse Ok()
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/batchedit/state")(collaboratorsOnly { repository =>
|
||||
defining(params.get("value")){ action =>
|
||||
action match {
|
||||
case Some("open") => executeBatch(repository) { handleComment(_, None, repository)( _ => Some("reopen")) }
|
||||
case Some("close") => executeBatch(repository) { handleComment(_, None, repository)( _ => Some("close")) }
|
||||
case _ => // TODO BadRequest
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/batchedit/label")(collaboratorsOnly { repository =>
|
||||
params("value").toIntOpt.map{ labelId =>
|
||||
executeBatch(repository) { issueId =>
|
||||
getIssueLabel(repository.owner, repository.name, issueId, labelId) getOrElse {
|
||||
registerIssueLabel(repository.owner, repository.name, issueId, labelId)
|
||||
}
|
||||
}
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/batchedit/assign")(collaboratorsOnly { repository =>
|
||||
defining(assignedUserName("value")){ value =>
|
||||
executeBatch(repository) {
|
||||
updateAssignedUserName(repository.owner, repository.name, _, value)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/batchedit/milestone")(collaboratorsOnly { repository =>
|
||||
defining(milestoneId("value")){ value =>
|
||||
executeBatch(repository) {
|
||||
updateMilestoneId(repository.owner, repository.name, _, value)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/_attached/:file")(referrersOnly { repository =>
|
||||
(Directory.getAttachedDir(repository.owner, repository.name) match {
|
||||
case dir if(dir.exists && dir.isDirectory) =>
|
||||
dir.listFiles.find(_.getName.startsWith(params("file") + ".")).map { file =>
|
||||
RawData(FileUtil.getMimeType(file.getName), file)
|
||||
}
|
||||
case _ => None
|
||||
}) getOrElse NotFound
|
||||
})
|
||||
|
||||
val assignedUserName = (key: String) => params.get(key) filter (_.trim != "")
|
||||
val milestoneId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
|
||||
|
||||
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
|
||||
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||
|
||||
private def executeBatch(repository: RepositoryService.RepositoryInfo)(execute: Int => Unit) = {
|
||||
params("checked").split(',') map(_.toInt) foreach execute
|
||||
params("from") match {
|
||||
case "issues" => redirect(s"/${repository.owner}/${repository.name}/issues")
|
||||
case "pulls" => redirect(s"/${repository.owner}/${repository.name}/pulls")
|
||||
}
|
||||
}
|
||||
|
||||
private def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String) = {
|
||||
StringUtil.extractIssueId(message).foreach { issueId =>
|
||||
if(getIssue(owner, repository, issueId).isDefined){
|
||||
createComment(owner, repository, context.loginAccount.get.userName, issueId.toInt,
|
||||
fromIssue.issueId + ":" + fromIssue.title, "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, issueId, _){
|
||||
Notifier.msgComment(s"${context.baseUrl}/${owner}/${name}/${
|
||||
if(issue.isPullRequest) "pull" else "issues"}/${issueId}#comment-${commentId.get}")
|
||||
}
|
||||
}
|
||||
action foreach {
|
||||
f.toNotify(repository, issueId, _){
|
||||
Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
commentId.map( issue -> _ )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def searchIssues(repository: RepositoryService.RepositoryInfo) = {
|
||||
defining(repository.owner, repository.name){ case (owner, repoName) =>
|
||||
val page = IssueSearchCondition.page(request)
|
||||
val sessionKey = Keys.Session.Issues(owner, repoName)
|
||||
|
||||
// retrieve search condition
|
||||
val condition = session.putAndGet(sessionKey,
|
||||
if(request.hasQueryString){
|
||||
val q = request.getParameter("q")
|
||||
if(q == null || q.trim.isEmpty){
|
||||
IssueSearchCondition(request)
|
||||
} else {
|
||||
IssueSearchCondition(q, getMilestones(owner, repoName).map(x => (x.title, x.milestoneId)).toMap)
|
||||
}
|
||||
} else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
|
||||
)
|
||||
|
||||
html.list(
|
||||
"issues",
|
||||
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
|
||||
page,
|
||||
(getCollaborators(owner, repoName) :+ owner).sorted,
|
||||
getMilestones(owner, repoName),
|
||||
getLabels(owner, repoName),
|
||||
countIssue(condition.copy(state = "open" ), false, owner -> repoName),
|
||||
countIssue(condition.copy(state = "closed"), false, owner -> repoName),
|
||||
condition,
|
||||
repository,
|
||||
hasWritePermission(owner, repoName, context.loginAccount))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.issues.labels.html
|
||||
import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, LabelsService}
|
||||
import gitbucket.core.util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.scalatra.i18n.Messages
|
||||
import org.scalatra.Ok
|
||||
|
||||
class LabelsController extends LabelsControllerBase
|
||||
with LabelsService with IssuesService with RepositoryService with AccountService
|
||||
with ReferrerAuthenticator with CollaboratorsAuthenticator
|
||||
|
||||
trait LabelsControllerBase extends ControllerBase {
|
||||
self: LabelsService with IssuesService with RepositoryService
|
||||
with ReferrerAuthenticator with CollaboratorsAuthenticator =>
|
||||
|
||||
case class LabelForm(labelName: String, color: String)
|
||||
|
||||
val labelForm = mapping(
|
||||
"labelName" -> trim(label("Label name", text(required, labelName, maxlength(100)))),
|
||||
"labelColor" -> trim(label("Color", text(required, color)))
|
||||
)(LabelForm.apply)
|
||||
|
||||
get("/:owner/:repository/issues/labels")(referrersOnly { repository =>
|
||||
html.list(
|
||||
getLabels(repository.owner, repository.name),
|
||||
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
||||
repository,
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/labels/new")(collaboratorsOnly { repository =>
|
||||
html.edit(None, repository)
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/labels/new", labelForm)(collaboratorsOnly { (form, repository) =>
|
||||
val labelId = createLabel(repository.owner, repository.name, form.labelName, form.color.substring(1))
|
||||
html.label(
|
||||
getLabel(repository.owner, repository.name, labelId).get,
|
||||
// TODO futility
|
||||
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
||||
repository,
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/labels/:labelId/edit")(collaboratorsOnly { repository =>
|
||||
getLabel(repository.owner, repository.name, params("labelId").toInt).map { label =>
|
||||
html.edit(Some(label), repository)
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/labels/:labelId/edit", labelForm)(collaboratorsOnly { (form, repository) =>
|
||||
updateLabel(repository.owner, repository.name, params("labelId").toInt, form.labelName, form.color.substring(1))
|
||||
html.label(
|
||||
getLabel(repository.owner, repository.name, params("labelId").toInt).get,
|
||||
// TODO futility
|
||||
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
||||
repository,
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/labels/:labelId/delete")(collaboratorsOnly { repository =>
|
||||
deleteLabel(repository.owner, repository.name, params("labelId").toInt)
|
||||
Ok()
|
||||
})
|
||||
|
||||
/**
|
||||
* Constraint for the identifier such as user name, repository name or page name.
|
||||
*/
|
||||
private def labelName: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
if(value.contains(',')){
|
||||
Some(s"${name} contains invalid character.")
|
||||
} else if(value.startsWith("_") || value.startsWith("-")){
|
||||
Some(s"${name} starts with invalid character.")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
package app
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.issues.milestones.html
|
||||
import gitbucket.core.service.{RepositoryService, MilestonesService, AccountService}
|
||||
import gitbucket.core.util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
import service._
|
||||
import util.{CollaboratorsAuthenticator, ReferrerAuthenticator, UsersAuthenticator}
|
||||
|
||||
class MilestonesController extends MilestonesControllerBase
|
||||
with MilestonesService with RepositoryService with AccountService
|
||||
with ReferrerAuthenticator with CollaboratorsAuthenticator
|
||||
@@ -22,7 +23,7 @@ trait MilestonesControllerBase extends ControllerBase {
|
||||
)(MilestoneForm.apply)
|
||||
|
||||
get("/:owner/:repository/issues/milestones")(referrersOnly { repository =>
|
||||
issues.milestones.html.list(
|
||||
html.list(
|
||||
params.getOrElse("state", "open"),
|
||||
getMilestonesWithIssueCount(repository.owner, repository.name),
|
||||
repository,
|
||||
@@ -30,43 +31,53 @@ trait MilestonesControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/milestones/new")(collaboratorsOnly {
|
||||
issues.milestones.html.edit(None, _)
|
||||
html.edit(None, _)
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/milestones/new", milestoneForm)(collaboratorsOnly { (form, repository) =>
|
||||
createMilestone(repository.owner, repository.name, form.title, form.description, form.dueDate)
|
||||
redirect("/%s/%s/issues/milestones".format(repository.owner, repository.name))
|
||||
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/milestones/:milestoneId/edit")(collaboratorsOnly { repository =>
|
||||
issues.milestones.html.edit(getMilestone(repository.owner, repository.name, params("milestoneId").toInt), repository)
|
||||
params("milestoneId").toIntOpt.map{ milestoneId =>
|
||||
html.edit(getMilestone(repository.owner, repository.name, milestoneId), repository)
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/milestones/:milestoneId/edit", milestoneForm)(collaboratorsOnly { (form, repository) =>
|
||||
getMilestone(repository.owner, repository.name, params("milestoneId").toInt).map { milestone =>
|
||||
updateMilestone(milestone.copy(title = form.title, description = form.description, dueDate = form.dueDate))
|
||||
redirect("/%s/%s/issues/milestones".format(repository.owner, repository.name))
|
||||
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
|
||||
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
|
||||
updateMilestone(milestone.copy(title = form.title, description = form.description, dueDate = form.dueDate))
|
||||
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
|
||||
}
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/milestones/:milestoneId/close")(collaboratorsOnly { repository =>
|
||||
getMilestone(repository.owner, repository.name, params("milestoneId").toInt).map { milestone =>
|
||||
closeMilestone(milestone)
|
||||
redirect("/%s/%s/issues/milestones".format(repository.owner, repository.name))
|
||||
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
|
||||
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
|
||||
closeMilestone(milestone)
|
||||
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
|
||||
}
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/milestones/:milestoneId/open")(collaboratorsOnly { repository =>
|
||||
getMilestone(repository.owner, repository.name, params("milestoneId").toInt).map { milestone =>
|
||||
openMilestone(milestone)
|
||||
redirect("/%s/%s/issues/milestones".format(repository.owner, repository.name))
|
||||
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
|
||||
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
|
||||
openMilestone(milestone)
|
||||
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
|
||||
}
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/milestones/:milestoneId/delete")(collaboratorsOnly { repository =>
|
||||
getMilestone(repository.owner, repository.name, params("milestoneId").toInt).map { milestone =>
|
||||
deleteMilestone(repository.owner, repository.name, milestone.milestoneId)
|
||||
redirect("/%s/%s/issues/milestones".format(repository.owner, repository.name))
|
||||
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
|
||||
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
|
||||
deleteMilestone(repository.owner, repository.name, milestone.milestoneId)
|
||||
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
|
||||
}
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user