mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-05 04:56:02 +01:00
Compare commits
1094 Commits
3.10_h2-1.
...
4.13
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43c8518a40 | ||
|
|
1b65ae2062 | ||
|
|
3ba31f205e | ||
|
|
bf28c2aacc | ||
|
|
80834220b3 | ||
|
|
cf763993cf | ||
|
|
4d9e1a83c8 | ||
|
|
850ebf2877 | ||
|
|
7923bde014 | ||
|
|
1eab821f9a | ||
|
|
042e76348a | ||
|
|
7d0b8dc1ec | ||
|
|
b0057481d8 | ||
|
|
d70c5947fa | ||
|
|
43f7a61c4b | ||
|
|
5a8516e8e5 | ||
|
|
4727aa90ab | ||
|
|
dcdc0cfa55 | ||
|
|
27dc5597bc | ||
|
|
af37d23a0d | ||
|
|
2143760185 | ||
|
|
e919505f4e | ||
|
|
4fc221f4f9 | ||
|
|
532f418c2f | ||
|
|
19016aa14a | ||
|
|
6016844327 | ||
|
|
352438ee0a | ||
|
|
d1dbdb1642 | ||
|
|
29da986b9f | ||
|
|
0b819ea762 | ||
|
|
b3dbaaae7a | ||
|
|
2dcc14b4d9 | ||
|
|
fe728baee7 | ||
|
|
d6f49eb442 | ||
|
|
890dbf99a7 | ||
|
|
61853a474a | ||
|
|
447183c779 | ||
|
|
b371f76cb6 | ||
|
|
a5971bbdde | ||
|
|
0827fef978 | ||
|
|
a66fcb3a77 | ||
|
|
ac9b93bbba | ||
|
|
7917483dfc | ||
|
|
c0a2c8a235 | ||
|
|
f28dc15252 | ||
|
|
9faa3e8402 | ||
|
|
0f33d66cc2 | ||
|
|
5f9fd23c47 | ||
|
|
e98d275de3 | ||
|
|
6ffb2dbad7 | ||
|
|
eb2bef824d | ||
|
|
16ccf83f7a | ||
|
|
f60685117d | ||
|
|
a830b80965 | ||
|
|
3795de97a4 | ||
|
|
c972782053 | ||
|
|
52f05a911b | ||
|
|
0a007dd4eb | ||
|
|
6223503511 | ||
|
|
2e499e88a6 | ||
|
|
658fe94d0f | ||
|
|
7c2cf86674 | ||
|
|
0db4cd35f1 | ||
|
|
289c38edeb | ||
|
|
650e9f0d0b | ||
|
|
755419fd56 | ||
|
|
458f1521b6 | ||
|
|
18fa77a25c | ||
|
|
db0b1d28bc | ||
|
|
518861ac0f | ||
|
|
fbb4f33b18 | ||
|
|
dc2cf05e8b | ||
|
|
df4b1d6f01 | ||
|
|
c5a53f0719 | ||
|
|
961e21e5a7 | ||
|
|
e33e304644 | ||
|
|
46896da46e | ||
|
|
207aa8b8c1 | ||
|
|
8b4017a082 | ||
|
|
f46f5909f1 | ||
|
|
5337b29532 | ||
|
|
7010b316fd | ||
|
|
6128258cfb | ||
|
|
83e619ecd4 | ||
|
|
3af89a7897 | ||
|
|
1e94b69a68 | ||
|
|
95b1945bc1 | ||
|
|
8aaba606bc | ||
|
|
f40657a7ff | ||
|
|
788e56d926 | ||
|
|
37303a8c5a | ||
|
|
c6449d4c10 | ||
|
|
9c078971ab | ||
|
|
0f0c3c1b1d | ||
|
|
835f35393e | ||
|
|
72c79542b7 | ||
|
|
e576e14460 | ||
|
|
a839e9eab5 | ||
|
|
e7b368ced2 | ||
|
|
d662df5a7d | ||
|
|
7c4a286937 | ||
|
|
2164b8ce31 | ||
|
|
71737eb018 | ||
|
|
ed543847a8 | ||
|
|
c0320d3139 | ||
|
|
b218c2284e | ||
|
|
fce8cbdaef | ||
|
|
f40bdb6494 | ||
|
|
74e940ea3c | ||
|
|
650aff5649 | ||
|
|
9793bfc074 | ||
|
|
0cba10994b | ||
|
|
ff7b0ca13f | ||
|
|
18b39fb868 | ||
|
|
b181aeb5ab | ||
|
|
9df2c221df | ||
|
|
e3c6621398 | ||
|
|
b736f904e9 | ||
|
|
00093728ee | ||
|
|
34c1fce8a2 | ||
|
|
6a520061cf | ||
|
|
c581d9e6b9 | ||
|
|
956af54d4f | ||
|
|
d5a0ade5d9 | ||
|
|
68c2f832ac | ||
|
|
614bba4a0f | ||
|
|
fde075f067 | ||
|
|
ea6bc9ccf0 | ||
|
|
b99c1c3f6e | ||
|
|
1e715d8b06 | ||
|
|
2a3e4af082 | ||
|
|
968c45c77d | ||
|
|
ee8be37f55 | ||
|
|
2d9727a695 | ||
|
|
0aa6e674e9 | ||
|
|
445bf1cc34 | ||
|
|
cfae6d76b2 | ||
|
|
83a27809ef | ||
|
|
5eb41d5b25 | ||
|
|
fe5961c59e | ||
|
|
114c50a354 | ||
|
|
56a39775e8 | ||
|
|
d0fa4da45a | ||
|
|
dcd4c55fb9 | ||
|
|
a7920a7dd7 | ||
|
|
3bb32f11d7 | ||
|
|
0a08879d8c | ||
|
|
5cb644279c | ||
|
|
c0e04ab0dc | ||
|
|
977e8e2472 | ||
|
|
9ba43071de | ||
|
|
37f940350f | ||
|
|
b65f7d9423 | ||
|
|
b91263ffb3 | ||
|
|
d27b9222ba | ||
|
|
c41c15dbc1 | ||
|
|
25b4b90633 | ||
|
|
cdea9475c1 | ||
|
|
a76d14d81f | ||
|
|
81b7c142d8 | ||
|
|
4d0e0b7bd2 | ||
|
|
4e3c88f1f4 | ||
|
|
f29c80acae | ||
|
|
a2e150817c | ||
|
|
e3aa9d739d | ||
|
|
faa8f8aade | ||
|
|
f165e89a8d | ||
|
|
620c3161cf | ||
|
|
05739f60ce | ||
|
|
57e260df6d | ||
|
|
2cff3884e2 | ||
|
|
5ac01a0617 | ||
|
|
4344228b92 | ||
|
|
8f91499132 | ||
|
|
5b68ca1416 | ||
|
|
2cb29654e9 | ||
|
|
43aff74ad2 | ||
|
|
0d5964bd22 | ||
|
|
8087531d64 | ||
|
|
c939456f21 | ||
|
|
1312276151 | ||
|
|
8fa3bf7850 | ||
|
|
cdc8431865 | ||
|
|
f4da49b5bd | ||
|
|
3e4e278778 | ||
|
|
7b8a5a482a | ||
|
|
0401488ab1 | ||
|
|
a024491296 | ||
|
|
a684fa8a8e | ||
|
|
c5a5c737bf | ||
|
|
dfe2e8dda5 | ||
|
|
994d897b5b | ||
|
|
ec66a79e2b | ||
|
|
d611aa8737 | ||
|
|
07bbbe8ad0 | ||
|
|
3d9e3d8456 | ||
|
|
e56c7472c4 | ||
|
|
0f8ee0d57d | ||
|
|
99f1a0b400 | ||
|
|
c039b763c5 | ||
|
|
bc69a67b05 | ||
|
|
37d2a38517 | ||
|
|
b5f287d75e | ||
|
|
629aaa78d6 | ||
|
|
5a1ec385a8 | ||
|
|
42d3585df5 | ||
|
|
b6390ac383 | ||
|
|
32c307b5a5 | ||
|
|
b0056bc942 | ||
|
|
3fa6652415 | ||
|
|
ae4da97ece | ||
|
|
16f0b68490 | ||
|
|
a0c5414a93 | ||
|
|
be3fc923fc | ||
|
|
684cd714e5 | ||
|
|
ea498f269e | ||
|
|
98b6d16de7 | ||
|
|
4bcfe837b1 | ||
|
|
5721cbf6f4 | ||
|
|
200760cc56 | ||
|
|
28f77e7357 | ||
|
|
c0d9689022 | ||
|
|
7f436831fe | ||
|
|
96360f1266 | ||
|
|
4eb31b9ecc | ||
|
|
1e24cc4daf | ||
|
|
cf64f9e64f | ||
|
|
a538d030e9 | ||
|
|
b13e70e787 | ||
|
|
aacfb091b2 | ||
|
|
768a3b1756 | ||
|
|
925408db31 | ||
|
|
31e0c6aa1d | ||
|
|
4de80c3027 | ||
|
|
2b986609bf | ||
|
|
43b932304d | ||
|
|
fcc015a0f8 | ||
|
|
67eaf19add | ||
|
|
3008b51dbe | ||
|
|
ee65ae3e49 | ||
|
|
da64cf8800 | ||
|
|
ca0302723d | ||
|
|
026f5e2f26 | ||
|
|
ea4414f1a5 | ||
|
|
53977bdf80 | ||
|
|
952d1954d9 | ||
|
|
fbd34dc89f | ||
|
|
79d41ba57b | ||
|
|
ab1adc48f8 | ||
|
|
422eda927e | ||
|
|
39e55bde2d | ||
|
|
6ec533990f | ||
|
|
7a6a2471b1 | ||
|
|
7d9f308492 | ||
|
|
e8888ac191 | ||
|
|
afad27ee01 | ||
|
|
1ed8e287b3 | ||
|
|
202f56b6a0 | ||
|
|
e72a192e4a | ||
|
|
d3afb35fb0 | ||
|
|
306c9e45be | ||
|
|
fd8add4fcd | ||
|
|
5a510d5703 | ||
|
|
6c66fb130b | ||
|
|
7bb860dcd8 | ||
|
|
b64caa8f06 | ||
|
|
c7ece57842 | ||
|
|
161c5513df | ||
|
|
538c1d7a9a | ||
|
|
123c17d442 | ||
|
|
6b7fd7fb7b | ||
|
|
0c0fde3077 | ||
|
|
1cf6950e70 | ||
|
|
5eddeba3ef | ||
|
|
75f4903ffb | ||
|
|
9727259d0c | ||
|
|
6bb664e592 | ||
|
|
f106dea3d9 | ||
|
|
982cc15052 | ||
|
|
1ef3299574 | ||
|
|
49f0795b5f | ||
|
|
af697d8155 | ||
|
|
81a779d1d9 | ||
|
|
7c484297d7 | ||
|
|
a91e46f3e9 | ||
|
|
5f18de06f5 | ||
|
|
bef5b5f22e | ||
|
|
092e832d21 | ||
|
|
cd836f331e | ||
|
|
53537eaa09 | ||
|
|
8515ef5b26 | ||
|
|
a2524608c7 | ||
|
|
127ddcef6d | ||
|
|
076bc9e2d6 | ||
|
|
d19b2778fe | ||
|
|
4d947aef7b | ||
|
|
1f3fc62a0e | ||
|
|
8b089837f9 | ||
|
|
4c4327b569 | ||
|
|
d72e9b2692 | ||
|
|
e021868a96 | ||
|
|
0c3cf5b140 | ||
|
|
32bd52d74d | ||
|
|
55f52b7f78 | ||
|
|
4ef45d3987 | ||
|
|
ebc6121526 | ||
|
|
8a36acb673 | ||
|
|
9efe438697 | ||
|
|
4c7540451e | ||
|
|
cdeaede8bf | ||
|
|
ad73e1d529 | ||
|
|
68e858541d | ||
|
|
709e423a6d | ||
|
|
392139c061 | ||
|
|
64db3e7842 | ||
|
|
14e8071713 | ||
|
|
cea09fa766 | ||
|
|
019767e8c3 | ||
|
|
3c34689e7d | ||
|
|
d3cca0685a | ||
|
|
72049c5bdf | ||
|
|
d636413471 | ||
|
|
d1c6cbf55a | ||
|
|
e439a2f5f7 | ||
|
|
ecde6aefbf | ||
|
|
b95d912542 | ||
|
|
eb50b74b4a | ||
|
|
d460185317 | ||
|
|
2297ef0bec | ||
|
|
8d7ec16ed0 | ||
|
|
4dfc9fc456 | ||
|
|
3b99e619db | ||
|
|
9cded1b4de | ||
|
|
bfc88a489a | ||
|
|
f2a213f32a | ||
|
|
9663d21ce8 | ||
|
|
30c8d3c39c | ||
|
|
88e72bee2c | ||
|
|
c67441b6d4 | ||
|
|
e1802978d3 | ||
|
|
1ccdc79051 | ||
|
|
3ca0d35a1b | ||
|
|
7bbeceec97 | ||
|
|
1295e621ce | ||
|
|
5f4580399b | ||
|
|
8d735205aa | ||
|
|
64f15e015f | ||
|
|
a95abf7397 | ||
|
|
3054834b91 | ||
|
|
572ea5bf47 | ||
|
|
cad4d76138 | ||
|
|
892f2d5f41 | ||
|
|
1e3bd9ebc0 | ||
|
|
e93e32b349 | ||
|
|
9c9876c918 | ||
|
|
8d30d68a4a | ||
|
|
88a8552d4d | ||
|
|
7608a41f9c | ||
|
|
7f7c55aeee | ||
|
|
2ebed8ef94 | ||
|
|
2904bcf4a7 | ||
|
|
6630fa2f37 | ||
|
|
351e63e7b6 | ||
|
|
ea0f35a0a1 | ||
|
|
623c53e169 | ||
|
|
3e6fd2caf8 | ||
|
|
39f1aa4487 | ||
|
|
8ffd905a9f | ||
|
|
668f9ef919 | ||
|
|
ffb9bb10f5 | ||
|
|
2618f54442 | ||
|
|
6b3218dd43 | ||
|
|
56a9b7b0f1 | ||
|
|
4f4afc5686 | ||
|
|
1d03e83d95 | ||
|
|
5698692b26 | ||
|
|
87fb136b85 | ||
|
|
7af271e14a | ||
|
|
f44d44cb4a | ||
|
|
e7fc5f1753 | ||
|
|
f0e2775861 | ||
|
|
2488ab9bd4 | ||
|
|
f0872d410c | ||
|
|
9d69cc9d45 | ||
|
|
1c66052372 | ||
|
|
158f799ca1 | ||
|
|
907532fd13 | ||
|
|
0f6a433623 | ||
|
|
00eab5d584 | ||
|
|
5d928b1a62 | ||
|
|
50d6f0c96f | ||
|
|
a60b43b862 | ||
|
|
4b1235b484 | ||
|
|
f354b9cfd7 | ||
|
|
0c2283ce28 | ||
|
|
840479a022 | ||
|
|
1bceaaab1d | ||
|
|
65ece3292a | ||
|
|
e410623cac | ||
|
|
09f7f036aa | ||
|
|
5249224dec | ||
|
|
00def3a46d | ||
|
|
134c0010b5 | ||
|
|
fe3b40557a | ||
|
|
d3cdc5d5fc | ||
|
|
7ebb28be74 | ||
|
|
707cd3c5c3 | ||
|
|
4c5017d108 | ||
|
|
fdd91b1e0e | ||
|
|
9124777ce7 | ||
|
|
7c575fdc52 | ||
|
|
f9d6f1334f | ||
|
|
65d4900325 | ||
|
|
64248d1fce | ||
|
|
bec75120bc | ||
|
|
3b0eed48d9 | ||
|
|
25c4b1e6a7 | ||
|
|
cccff46715 | ||
|
|
fc0ffd1b4f | ||
|
|
b70a2a2327 | ||
|
|
b5ca7ca0e1 | ||
|
|
7bb5379b45 | ||
|
|
5692a8c83e | ||
|
|
6c6126148e | ||
|
|
5b2e24daef | ||
|
|
29f390e48c | ||
|
|
c49eff6e54 | ||
|
|
27930a5849 | ||
|
|
6ffc139d2f | ||
|
|
59ed027b60 | ||
|
|
5dc55822d7 | ||
|
|
9bfe5115cc | ||
|
|
aaf9c65f30 | ||
|
|
7fa921e7e5 | ||
|
|
47a55354fe | ||
|
|
d6197261fb | ||
|
|
8fd7df2a9d | ||
|
|
4eb148f4a6 | ||
|
|
8f1e460893 | ||
|
|
8c80f8a506 | ||
|
|
9eb9fc666c | ||
|
|
d70c6cece7 | ||
|
|
dbdee135a3 | ||
|
|
132bb6bee4 | ||
|
|
c64428e37f | ||
|
|
2dfa7a1190 | ||
|
|
06d559b47e | ||
|
|
83baaa6ed9 | ||
|
|
85d38a47f1 | ||
|
|
0c3c6ea15a | ||
|
|
2ce436bddc | ||
|
|
a60c607fcb | ||
|
|
0456739118 | ||
|
|
368052bd8f | ||
|
|
ce916a7d4b | ||
|
|
60ff046823 | ||
|
|
7d3bda42e2 | ||
|
|
83a39f1e39 | ||
|
|
de726d8d96 | ||
|
|
91bb241e8c | ||
|
|
8da55d8aa8 | ||
|
|
3355c46503 | ||
|
|
0a3d457218 | ||
|
|
7fa5fdfbd0 | ||
|
|
95f88891d0 | ||
|
|
550f8f415c | ||
|
|
5ab947d8ec | ||
|
|
ec793535e7 | ||
|
|
2f1d81cc4c | ||
|
|
0f189ca710 | ||
|
|
6afd51bb8d | ||
|
|
e415f9d24e | ||
|
|
ba5d587a1e | ||
|
|
92f778b6e9 | ||
|
|
b52981a845 | ||
|
|
9c5d3edc72 | ||
|
|
56d68c6145 | ||
|
|
4d13282915 | ||
|
|
872320ccab | ||
|
|
28ee80b727 | ||
|
|
2621de2cde | ||
|
|
82b102845f | ||
|
|
28c9f8b89a | ||
|
|
23fa937fd1 | ||
|
|
02330a2050 | ||
|
|
c65599d995 | ||
|
|
22ae1df4b1 | ||
|
|
6b22342166 | ||
|
|
53f6190267 | ||
|
|
f73daaef44 | ||
|
|
d99e382dfe | ||
|
|
aefbee2093 | ||
|
|
11fb0a7edf | ||
|
|
fe959aecff | ||
|
|
9b33655bd4 | ||
|
|
33acad85db | ||
|
|
6bfe3ea760 | ||
|
|
1532fd71d0 | ||
|
|
c14a732e2a | ||
|
|
a1372034ed | ||
|
|
98914269b7 | ||
|
|
d5e455336b | ||
|
|
7b84f25c56 | ||
|
|
2ca20af502 | ||
|
|
78df2accfc | ||
|
|
7a282fb67e | ||
|
|
db679967af | ||
|
|
5a94125585 | ||
|
|
9e98d30612 | ||
|
|
a47065e4a9 | ||
|
|
94d18c471c | ||
|
|
f8f3019228 | ||
|
|
c3d90b8593 | ||
|
|
62c1299f29 | ||
|
|
b75db98cad | ||
|
|
3592b3d13c | ||
|
|
ca814e2c08 | ||
|
|
48b6a590bf | ||
|
|
285ef02a17 | ||
|
|
18375c741e | ||
|
|
21030344cc | ||
|
|
a494027217 | ||
|
|
7bca01af59 | ||
|
|
acf3fa9980 | ||
|
|
34bcc85dcc | ||
|
|
c0ce0f8d19 | ||
|
|
2b5f74b9f3 | ||
|
|
c2538e772f | ||
|
|
f4a489f4e6 | ||
|
|
6370a72d81 | ||
|
|
e612991424 | ||
|
|
4bef6a4eeb | ||
|
|
4eba7070da | ||
|
|
161e6dc809 | ||
|
|
7937944c10 | ||
|
|
89dfaeeb93 | ||
|
|
77641185af | ||
|
|
4f78a3615c | ||
|
|
bfbf90158b | ||
|
|
825b2f9ebf | ||
|
|
56e7168461 | ||
|
|
c2d0d94f05 | ||
|
|
fc22cfbbdd | ||
|
|
d62adbf649 | ||
|
|
dba5539e3e | ||
|
|
f0a8b3bb17 | ||
|
|
f52e7e1bdd | ||
|
|
58ba26f21e | ||
|
|
bf7b30630c | ||
|
|
b5cac0308e | ||
|
|
373ea39048 | ||
|
|
427f5eec5f | ||
|
|
a4e9903e00 | ||
|
|
0d900a892c | ||
|
|
dc6fdaf482 | ||
|
|
b79498ed9f | ||
|
|
69e8f628df | ||
|
|
d3d8e3ce5f | ||
|
|
0499c47f4b | ||
|
|
7fd0cdd7d8 | ||
|
|
49eaf79e01 | ||
|
|
3a96c30aa8 | ||
|
|
6d550fa485 | ||
|
|
7f9d69bb51 | ||
|
|
709fab9ccc | ||
|
|
fd13a2db79 | ||
|
|
840d81f7bd | ||
|
|
5d08f4d339 | ||
|
|
ef48b2d5ef | ||
|
|
f54e4f337f | ||
|
|
d6f8a45889 | ||
|
|
3fdb444961 | ||
|
|
743965d3b8 | ||
|
|
0e787eddfd | ||
|
|
442c0d575e | ||
|
|
485516be2e | ||
|
|
6b2fbb3bf0 | ||
|
|
e510b1c26b | ||
|
|
8d35494169 | ||
|
|
cf1504bae7 | ||
|
|
9bb4e473b9 | ||
|
|
d67afebadc | ||
|
|
417886161c | ||
|
|
1b85d511e9 | ||
|
|
45d84f63c1 | ||
|
|
fff60b2704 | ||
|
|
c9339aec9e | ||
|
|
7c98ae1341 | ||
|
|
01c2291715 | ||
|
|
2e03f081d9 | ||
|
|
0cbafdd884 | ||
|
|
d5a9c2c15d | ||
|
|
1496591244 | ||
|
|
f5acce3901 | ||
|
|
5568a0ad8e | ||
|
|
26a18287c7 | ||
|
|
b0f819b9bd | ||
|
|
ebff7baf07 | ||
|
|
cf9a55d896 | ||
|
|
72f7b659f4 | ||
|
|
87192d025b | ||
|
|
fd181b9a0c | ||
|
|
9c4cc12a02 | ||
|
|
44497b559e | ||
|
|
09c50a149b | ||
|
|
88beb68e01 | ||
|
|
0da358311b | ||
|
|
cf97b63dab | ||
|
|
4b5f22144e | ||
|
|
0d342a6863 | ||
|
|
458820a09d | ||
|
|
135c34ef0f | ||
|
|
8187c5a013 | ||
|
|
6ff48c8130 | ||
|
|
d37c70cd8d | ||
|
|
8abf357405 | ||
|
|
c93ac71634 | ||
|
|
408180f071 | ||
|
|
4e98abfe5c | ||
|
|
efbb404bd4 | ||
|
|
66f409bfad | ||
|
|
44ec64fb4b | ||
|
|
fb27bd29e8 | ||
|
|
c26ca9d463 | ||
|
|
8c36ba33f4 | ||
|
|
fe1e18b495 | ||
|
|
29e632af04 | ||
|
|
2b9daae62b | ||
|
|
8a11f85dd1 | ||
|
|
b09c72b106 | ||
|
|
43456e817a | ||
|
|
7876a60106 | ||
|
|
38e71001cb | ||
|
|
a1307b7464 | ||
|
|
fd413d36ad | ||
|
|
509dfc57ca | ||
|
|
95bdd6228e | ||
|
|
fd1430371a | ||
|
|
437f944c6e | ||
|
|
f64b6e10bb | ||
|
|
5925bd3772 | ||
|
|
fe8d4616db | ||
|
|
99b40974c3 | ||
|
|
c463590ede | ||
|
|
82163eebc2 | ||
|
|
f1e427f926 | ||
|
|
c21dcdca80 | ||
|
|
2ce51472c3 | ||
|
|
514b1aeec1 | ||
|
|
3f34622fe0 | ||
|
|
8c6d5b8178 | ||
|
|
1971c29fd0 | ||
|
|
40ca9b6682 | ||
|
|
81aeed6f6c | ||
|
|
bf50b1bf82 | ||
|
|
1094e8ca2d | ||
|
|
860ccce89b | ||
|
|
59e5993eba | ||
|
|
c08627e5d6 | ||
|
|
3e0bb46699 | ||
|
|
9a972e40ef | ||
|
|
99ad0db1f6 | ||
|
|
12d11bc80c | ||
|
|
dc98079b55 | ||
|
|
88f56126a6 | ||
|
|
313c9976c8 | ||
|
|
5b6a1d0adc | ||
|
|
6db43e6ca7 | ||
|
|
46177e814c | ||
|
|
2fe79baed8 | ||
|
|
02e17c76a7 | ||
|
|
4d8acfd286 | ||
|
|
4a5c287b8f | ||
|
|
0d4047b4ee | ||
|
|
2b730ef180 | ||
|
|
ecbe7228b9 | ||
|
|
e36d0f65d6 | ||
|
|
30818fb797 | ||
|
|
6d7685fcce | ||
|
|
2cec5be3d9 | ||
|
|
038b70ff0b | ||
|
|
23a5f7dcf9 | ||
|
|
baa27d6090 | ||
|
|
f71acfcbe8 | ||
|
|
c65e843491 | ||
|
|
9103c88f0e | ||
|
|
90170f0fcc | ||
|
|
2e3de336cb | ||
|
|
da50d317e7 | ||
|
|
7a91e14b03 | ||
|
|
58eff0a1a3 | ||
|
|
8559f3e354 | ||
|
|
7606097a4d | ||
|
|
dfbace3d26 | ||
|
|
d61ab632f1 | ||
|
|
c3b341d945 | ||
|
|
59f063627c | ||
|
|
b4aba76005 | ||
|
|
81afea350d | ||
|
|
5c5da60dd6 | ||
|
|
89c69cdfc2 | ||
|
|
0789010248 | ||
|
|
37c23f615f | ||
|
|
b4d3573a84 | ||
|
|
5161ece63b | ||
|
|
5b7955cee6 | ||
|
|
60099e2b0d | ||
|
|
f04c486251 | ||
|
|
7b23bbf9ba | ||
|
|
0a532d9774 | ||
|
|
208b98285c | ||
|
|
56c9aa32a4 | ||
|
|
35080a9f33 | ||
|
|
6c41505c91 | ||
|
|
05bfaafe32 | ||
|
|
7534a88607 | ||
|
|
d7817d3d88 | ||
|
|
37780d467d | ||
|
|
ad4af67b30 | ||
|
|
29b0c22b0e | ||
|
|
c400678550 | ||
|
|
3c40e93346 | ||
|
|
247b664654 | ||
|
|
b3db0a6a7b | ||
|
|
0a03e41f1c | ||
|
|
a79f105eea | ||
|
|
74f3f6bf2e | ||
|
|
a4bf73724d | ||
|
|
8d91253ede | ||
|
|
b7355af49a | ||
|
|
7ec85cbf99 | ||
|
|
a6790b049d | ||
|
|
dce747b1e8 | ||
|
|
c22ee8acfd | ||
|
|
30b8b738b6 | ||
|
|
b916595da3 | ||
|
|
1accafa8b1 | ||
|
|
11700f4cb4 | ||
|
|
c9de8dd323 | ||
|
|
fd694e38aa | ||
|
|
8822c36b5f | ||
|
|
e614e31162 | ||
|
|
ad47ad4269 | ||
|
|
90b63090cc | ||
|
|
345685ed7c | ||
|
|
1d9fe5770e | ||
|
|
0c50545cbd | ||
|
|
53cbc36a01 | ||
|
|
85b2053004 | ||
|
|
eba240de65 | ||
|
|
1e5114cd54 | ||
|
|
90cb5de5f0 | ||
|
|
11d33e9389 | ||
|
|
c71e9331ae | ||
|
|
ec307b84d3 | ||
|
|
f37b5fa682 | ||
|
|
8cb1ac734d | ||
|
|
05ff2a854c | ||
|
|
d956ade5e3 | ||
|
|
73228506a5 | ||
|
|
2525bbafa8 | ||
|
|
338946dd3a | ||
|
|
2d225641ee | ||
|
|
3c727fe678 | ||
|
|
523ea0d437 | ||
|
|
9eff8f248b | ||
|
|
d50c858a26 | ||
|
|
6f4e94ba9a | ||
|
|
f2750c20a2 | ||
|
|
2da2b426a1 | ||
|
|
5a0bc127b7 | ||
|
|
23a482bbba | ||
|
|
6c2fce1b16 | ||
|
|
5d7346db91 | ||
|
|
443498433d | ||
|
|
a58ce07736 | ||
|
|
1903c3990c | ||
|
|
90441c8eec | ||
|
|
601919bcc6 | ||
|
|
6903b096f5 | ||
|
|
e44fed09fa | ||
|
|
8ed0c8a170 | ||
|
|
31118ac285 | ||
|
|
c851b7582f | ||
|
|
102a02d527 | ||
|
|
1968bf871a | ||
|
|
5cd4e48173 | ||
|
|
111d212cb5 | ||
|
|
d648d34393 | ||
|
|
bbaa5b38e7 | ||
|
|
3c50a78be2 | ||
|
|
82cc1fa530 | ||
|
|
8c0581973e | ||
|
|
bfa15f5d75 | ||
|
|
57e87b581d | ||
|
|
5aa6f5bce3 | ||
|
|
9ba098a805 | ||
|
|
b2d8567c26 | ||
|
|
19d97c93ce | ||
|
|
cad2daa2f9 | ||
|
|
585f0b5769 | ||
|
|
dd23d1109b | ||
|
|
5e84221d39 | ||
|
|
faf3e6c26b | ||
|
|
969da2c63b | ||
|
|
ba61891510 | ||
|
|
a581871a89 | ||
|
|
d96e1fa503 | ||
|
|
b66812d76c | ||
|
|
ae32016856 | ||
|
|
56aec15e68 | ||
|
|
d0c8e33ec5 | ||
|
|
7ab260e688 | ||
|
|
0d2c923664 | ||
|
|
e7a47fe3a4 | ||
|
|
e454f78c5a | ||
|
|
192e4ade3e | ||
|
|
733797cb6f | ||
|
|
f0e4157a46 | ||
|
|
2ad6948bb4 | ||
|
|
dd46d649a6 | ||
|
|
bbe8a9b9e4 | ||
|
|
376b109602 | ||
|
|
1d085d52bb | ||
|
|
d1f42e0ed7 | ||
|
|
101f8598ed | ||
|
|
da62f6f8fb | ||
|
|
5750286b5d | ||
|
|
f2ca4fb64b | ||
|
|
5f6b577cbf | ||
|
|
62004c279c | ||
|
|
838c7fb991 | ||
|
|
93786f0fd6 | ||
|
|
f3514e5625 | ||
|
|
a2b0ee0c24 | ||
|
|
2bcab30529 | ||
|
|
91cda6d245 | ||
|
|
a82e579d57 | ||
|
|
94421c7a63 | ||
|
|
ea7c8e62de | ||
|
|
b84421723b | ||
|
|
9a42b93d1f | ||
|
|
e162cd956a | ||
|
|
6431d25409 | ||
|
|
c8686f4b34 | ||
|
|
43097b4c1c | ||
|
|
539751a1d9 | ||
|
|
70e2079c7f | ||
|
|
e8737d263a | ||
|
|
4c4b08f1b8 | ||
|
|
c7e1edf262 | ||
|
|
876bb396fd | ||
|
|
a6788f858f | ||
|
|
b263764730 | ||
|
|
1b1bd371a4 | ||
|
|
f194a08cfe | ||
|
|
1211bfc7be | ||
|
|
eab7011e0f | ||
|
|
6f30ffa865 | ||
|
|
de3026248c | ||
|
|
413e75be5a | ||
|
|
6a8ec18f9a | ||
|
|
5b1b2ef3d7 | ||
|
|
9a705c62bf | ||
|
|
b103180bf6 | ||
|
|
536a0d3fe2 | ||
|
|
356202e28a | ||
|
|
6db36e12b5 | ||
|
|
bfcd5a2855 | ||
|
|
e218b52b78 | ||
|
|
46998dc1fa | ||
|
|
977f856854 | ||
|
|
da2a7bf77d | ||
|
|
3da3a048f0 | ||
|
|
7b5b453e56 | ||
|
|
c18f95edf8 | ||
|
|
71cf043f56 | ||
|
|
a31e4b5897 | ||
|
|
1679da4abe | ||
|
|
505bc71f9a | ||
|
|
4bc057c653 | ||
|
|
8eee13d7aa | ||
|
|
8981e339b4 | ||
|
|
e1dd5dd057 | ||
|
|
cb64f8eab8 | ||
|
|
c47d50d0df | ||
|
|
1f46da2273 | ||
|
|
06fc26cd06 | ||
|
|
3a4f9b9027 | ||
|
|
f98c849c7c | ||
|
|
aa0bd5b34a | ||
|
|
b52e904ed1 | ||
|
|
70e0dcf99d | ||
|
|
56bb20dfd2 | ||
|
|
7d7d2f488d | ||
|
|
72affd67b9 | ||
|
|
0cf1f43deb | ||
|
|
8494c682a7 | ||
|
|
1af5611159 | ||
|
|
4d39f63ef7 | ||
|
|
120d1c2fff | ||
|
|
62e9c0358a | ||
|
|
5a90848c75 | ||
|
|
760d443f74 | ||
|
|
5ee0e75dfe | ||
|
|
3b4d2d6f91 | ||
|
|
dfaabeb41d | ||
|
|
ff3205b6c7 | ||
|
|
0fae2dac35 | ||
|
|
4db4fe28b4 | ||
|
|
5b87efa032 | ||
|
|
3ad609bad7 | ||
|
|
8145cba111 | ||
|
|
24b9a9a12c | ||
|
|
ee7220ebd2 | ||
|
|
8fb72fd55e | ||
|
|
a1eded2d9a | ||
|
|
7f184e1126 | ||
|
|
09aafbcce1 | ||
|
|
7f5024a746 | ||
|
|
8fec0870a8 | ||
|
|
a8d2afaff7 | ||
|
|
8fd92f1c2f | ||
|
|
419ea16ead | ||
|
|
e72d808a3c | ||
|
|
44e8c0a9be | ||
|
|
e2c39d7815 | ||
|
|
687cd54f9a | ||
|
|
911754e1dc | ||
|
|
0067cbce6f | ||
|
|
f40f8427aa | ||
|
|
98ceff2391 | ||
|
|
642a51a208 | ||
|
|
9ec7c321d8 | ||
|
|
a3c419b6f5 | ||
|
|
15c28cffa4 | ||
|
|
f4d0f16481 | ||
|
|
45535e4fdf | ||
|
|
64635c5dc6 | ||
|
|
2fd95c7f1a | ||
|
|
eb6da85183 | ||
|
|
bcc05f021c | ||
|
|
d58ed55c3a | ||
|
|
057f029c80 | ||
|
|
c9a12ff913 | ||
|
|
66bf00b5d3 | ||
|
|
7ba3ca6f15 | ||
|
|
a1bacccc09 | ||
|
|
333eeb4bad | ||
|
|
3f2935612d | ||
|
|
4c87bdd959 | ||
|
|
3543073150 | ||
|
|
e50fe604c2 | ||
|
|
63369258bd | ||
|
|
e7c3376303 | ||
|
|
86163f66ce | ||
|
|
518f0bfc28 | ||
|
|
0a759f6127 | ||
|
|
afa79d01b1 | ||
|
|
860fc8ef4c | ||
|
|
5a2224623b | ||
|
|
dda3458e80 | ||
|
|
40e36e3f8b | ||
|
|
df06f509f5 | ||
|
|
0b4d0be1b4 | ||
|
|
0bef8d29d5 | ||
|
|
6265faa14f | ||
|
|
7593c97ad3 | ||
|
|
7c8de834bc | ||
|
|
637e9f906b | ||
|
|
97035be2e5 | ||
|
|
80fedbd335 | ||
|
|
3f6ebc1164 | ||
|
|
27b99319d3 | ||
|
|
a46d1ecf69 | ||
|
|
fb531526bd | ||
|
|
21d6143e40 | ||
|
|
129b424a3a | ||
|
|
689334a599 | ||
|
|
b3ee2222f3 | ||
|
|
da5e4fe5b1 | ||
|
|
f3b7318453 | ||
|
|
3d1c9bc9de | ||
|
|
9874eb7243 | ||
|
|
cc6f4d70da | ||
|
|
9b06bfaaf5 | ||
|
|
cdf0d06bcc | ||
|
|
501f542982 | ||
|
|
1a3504e885 | ||
|
|
fa617badc1 | ||
|
|
1a1267dc60 | ||
|
|
361babd327 | ||
|
|
64cacb18a4 | ||
|
|
833cfc3465 | ||
|
|
5a5bf34fe0 | ||
|
|
95746de5aa | ||
|
|
af7043f4bf | ||
|
|
4f3c780d05 | ||
|
|
249b27593e | ||
|
|
33a079e55f | ||
|
|
27eb1c36bd | ||
|
|
1201271949 | ||
|
|
5c12cca7f5 | ||
|
|
206d597d9b | ||
|
|
1b4c621fef | ||
|
|
03d4b9e9c6 | ||
|
|
82a9d9f7cf | ||
|
|
3e79dcf7a4 | ||
|
|
c9f3ec12a7 | ||
|
|
b781696803 | ||
|
|
19e74a4fe1 | ||
|
|
130aa1e515 | ||
|
|
8dcb8c2ecb | ||
|
|
b599219852 | ||
|
|
58078989f8 | ||
|
|
4276c0970b | ||
|
|
4abd4a4bac | ||
|
|
2b2ae718f7 | ||
|
|
ef201e3319 | ||
|
|
0ab28e6daa | ||
|
|
9b3e8bd22b | ||
|
|
15e8527e01 | ||
|
|
f991f3b454 | ||
|
|
d6a39403e2 | ||
|
|
aa065fd030 | ||
|
|
e92d1eae5a | ||
|
|
b1b132dfc3 | ||
|
|
90c379b3b9 | ||
|
|
b71ff6f179 | ||
|
|
8b44a00299 | ||
|
|
f5c1c0703d | ||
|
|
5036b1a1aa | ||
|
|
f63542dbd0 | ||
|
|
63e605d99c | ||
|
|
9e313bb2b3 | ||
|
|
a03fc4cf4a | ||
|
|
d18f92cfbf | ||
|
|
b96821ca44 | ||
|
|
c61ecdef7a | ||
|
|
b50ca63c2b | ||
|
|
f6624c0002 | ||
|
|
d62fc6e135 | ||
|
|
926c4d948f | ||
|
|
5fd87ca764 | ||
|
|
1042c50cc3 | ||
|
|
adff36c44b | ||
|
|
e8f6d2b990 | ||
|
|
a3b3262ad5 | ||
|
|
9efba82e94 | ||
|
|
781d465f8d | ||
|
|
766867f341 | ||
|
|
e237a44f56 | ||
|
|
249430449b | ||
|
|
73d935efe3 | ||
|
|
79251ef1d1 | ||
|
|
1081c0f16c | ||
|
|
3302b607f1 | ||
|
|
f13a3ee580 | ||
|
|
927969ac17 | ||
|
|
2d8aa4f8b5 | ||
|
|
645af4d2c0 | ||
|
|
34240d16b5 | ||
|
|
89210985d4 | ||
|
|
9d6258861a | ||
|
|
6661314be5 | ||
|
|
e187d026cc | ||
|
|
100b34085c | ||
|
|
ba75079409 | ||
|
|
cbb847704b | ||
|
|
739c99edce | ||
|
|
af2e2f5fd1 | ||
|
|
09fcf9c003 | ||
|
|
5dad0777d7 | ||
|
|
be3aa21651 | ||
|
|
abd729e45f | ||
|
|
162e9a9feb | ||
|
|
7500d017d5 | ||
|
|
e8c4004d5a | ||
|
|
a6f4d9d7cf | ||
|
|
3399278925 | ||
|
|
4457a317e6 | ||
|
|
990d082422 | ||
|
|
dfe3fbc02f | ||
|
|
ef3b02b718 | ||
|
|
9777d543b1 | ||
|
|
51acf72e0d | ||
|
|
6ad724d80c | ||
|
|
82b056bd43 | ||
|
|
4c417daee5 | ||
|
|
28c47dd9c7 | ||
|
|
cd62220ba0 | ||
|
|
96e6aa89e3 | ||
|
|
41e49423b2 | ||
|
|
3cc7bd3cdb |
13
.editorconfig
Normal file
13
.editorconfig
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.java]
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
7
.github/CONTRIBUTING.md
vendored
Normal file
7
.github/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Guideline for Issues
|
||||||
|
|
||||||
|
- At first, see [Wiki](https://github.com/gitbucket/gitbucket/wiki) and check issues whether there is a same question or request in the past.
|
||||||
|
- If you can't find same question and report, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
|
||||||
|
- We can also support in Japanese other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
|
||||||
|
- Write an issue in English. At least, write subject in English.
|
||||||
|
- The highest priority of GitBucket is the ease of installation and API compatibility with GitHub, so your feature request might be rejected if they go against those principles.
|
||||||
19
.github/ISSUE_TEMPLATE.md
vendored
Normal file
19
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
### Before submitting an issue to Gitbucket I have first:
|
||||||
|
|
||||||
|
- [] read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/.github/CONTRIBUTING.md)
|
||||||
|
- [] searched for similar already existing issue
|
||||||
|
- [] read the documentation and [wiki](https://github.com/gitbucket/gitbucket/wiki)
|
||||||
|
|
||||||
|
*(if you have performed all the above, remove the paragraph and continue describing the issue with template below)*
|
||||||
|
|
||||||
|
## Issue
|
||||||
|
**Impacted version**: xxxx
|
||||||
|
|
||||||
|
**Deployment mode**: *explain here how you use gitbucket : standalone app, under webcontainer (which one), with an http frontend (nginx, httpd, ...)*
|
||||||
|
|
||||||
|
**Problem description**:
|
||||||
|
- *be as explicit has you can*
|
||||||
|
- *describe the problem and its symptoms*
|
||||||
|
- *explain how to reproduce*
|
||||||
|
- *attach whatever information that can help understanding the context (screen capture, log files)*
|
||||||
|
- *do your best to use a correct english (re-read yourself)*
|
||||||
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
### Before submitting a pull-request to Gitbucket I have first:
|
||||||
|
|
||||||
|
- [] read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/.github/CONTRIBUTING.md)
|
||||||
|
- [] rebased my branch over master
|
||||||
|
- [] verified that project is compiling
|
||||||
|
- [] verified that tests are passing
|
||||||
|
- [] squashed my commits as appropriate *(keep several commits if it is relevant to understand the PR)*
|
||||||
|
- [] [marked as closed using commit message](https://help.github.com/articles/closing-issues-via-commit-messages/) all issue ID that this PR should correct
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -16,8 +16,11 @@ project/plugins/project/
|
|||||||
.classpath
|
.classpath
|
||||||
.project
|
.project
|
||||||
.cache
|
.cache
|
||||||
|
.cache-main
|
||||||
|
.cache-tests
|
||||||
.settings
|
.settings
|
||||||
|
|
||||||
# IntelliJ specific
|
# IntelliJ specific
|
||||||
.idea/
|
.idea/
|
||||||
.idea_modules/
|
.idea_modules/
|
||||||
|
*.iml
|
||||||
|
|||||||
35
.travis.yml
35
.travis.yml
@@ -1,6 +1,39 @@
|
|||||||
language: scala
|
language: scala
|
||||||
sudo: false
|
sudo: true
|
||||||
script:
|
script:
|
||||||
- sbt test
|
- sbt test
|
||||||
jdk:
|
jdk:
|
||||||
- oraclejdk8
|
- oraclejdk8
|
||||||
|
before_script:
|
||||||
|
- sudo apt-get install libaio1
|
||||||
|
- sudo /etc/init.d/mysql stop
|
||||||
|
- sudo /etc/init.d/postgresql stop
|
||||||
|
- sudo chmod +x /usr/local/bin/sbt
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- $HOME/.ivy2/cache
|
||||||
|
- $HOME/.sbt/boot
|
||||||
|
- $HOME/.sbt/launchers
|
||||||
|
- $HOME/.coursier
|
||||||
|
- $HOME/.embedmysql
|
||||||
|
- $HOME/.embedpostgresql
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- dist: trusty
|
||||||
|
group: edge
|
||||||
|
sudo: required
|
||||||
|
jdk: oraclejdk9
|
||||||
|
script:
|
||||||
|
# https://github.com/sbt/sbt/pull/2951
|
||||||
|
- git clone https://github.com/retronym/java9-rt-export
|
||||||
|
- cd java9-rt-export/
|
||||||
|
- git checkout 1019a2873d057dd7214f4135e84283695728395d
|
||||||
|
- jdk_switcher use oraclejdk8
|
||||||
|
- sbt package
|
||||||
|
- jdk_switcher use oraclejdk9
|
||||||
|
- mkdir -p $HOME/.sbt/0.13/java9-rt-ext; java -jar target/java9-rt-export-*.jar $HOME/.sbt/0.13/java9-rt-ext/rt.jar
|
||||||
|
- jar tf $HOME/.sbt/0.13/java9-rt-ext/rt.jar | grep java/lang/Object
|
||||||
|
- cd ..
|
||||||
|
- echo "sbt.version=0.13.14-RC1" > project/build.properties
|
||||||
|
- wget https://raw.githubusercontent.com/paulp/sbt-extras/9ade5fa54914ca8aded44105bf4b9a60966f3ccd/sbt && chmod +x ./sbt
|
||||||
|
- ./sbt -Dscala.ext.dirs=$HOME/.sbt/0.13/java9-rt-ext test
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
# Guideline for Issues
|
|
||||||
|
|
||||||
- If you have any question about GitBucket, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raise an issue.
|
|
||||||
- Make sure check whether there is a same question or request in the past.
|
|
||||||
- When raise a new issue, write subject in **English** at least.
|
|
||||||
- We can also support in Japaneses other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
|
|
||||||
- First priority of GitBucket is easy installation and reproduce GitHub behavior, so we might reject if your request is against it.
|
|
||||||
5
LICENSE
5
LICENSE
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
Apache License
|
Apache License
|
||||||
Version 2.0, January 2004
|
Version 2.0, January 2004
|
||||||
http://www.apache.org/licenses/
|
http://www.apache.org/licenses/
|
||||||
@@ -179,7 +178,7 @@
|
|||||||
APPENDIX: How to apply the Apache License to your work.
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
To apply the Apache License to your work, attach the following
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
replaced with your own identifying information. (Don't include
|
replaced with your own identifying information. (Don't include
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
comment syntax for the file format. We also recommend that a
|
comment syntax for the file format. We also recommend that a
|
||||||
@@ -187,7 +186,7 @@
|
|||||||
same "printed page" as the copyright notice for easier
|
same "printed page" as the copyright notice for easier
|
||||||
identification within third-party archives.
|
identification within third-party archives.
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
257
README.md
257
README.md
@@ -1,100 +1,217 @@
|
|||||||
GitBucket [](https://gitter.im/gitbucket/gitbucket) [](https://travis-ci.org/gitbucket/gitbucket)
|
GitBucket [](https://gitter.im/gitbucket/gitbucket) [](https://travis-ci.org/gitbucket/gitbucket)
|
||||||
=========
|
=========
|
||||||
|
|
||||||
GitBucket is a GitHub clone powered by Scala which has easy installation and high extensibility.
|
GitBucket is a Git web platform powered by Scala offering:
|
||||||
|
|
||||||
|
- Easy installation
|
||||||
|
- Intuitive UI
|
||||||
|
- High extensibility by plugins
|
||||||
|
- API compatibility with GitHub
|
||||||
|
|
||||||
|
You can try an [online demo](https://gitbucket.herokuapp.com/) *(ID: root / Pass: root)* of GitBucket, and also get the latest information at [GitBucket News](https://gitbucket.github.io/gitbucket-news/).
|
||||||
|
|
||||||
Features
|
Features
|
||||||
--------
|
--------
|
||||||
The current version of GitBucket provides a basic features below:
|
The current version of GitBucket provides many features such as:
|
||||||
|
|
||||||
- Public / Private Git repository (http and ssh access)
|
- Public / Private Git repositories (with http/https and ssh access)
|
||||||
- Repository viewer and online file editing
|
- GitLFS support
|
||||||
- Repository search (Code and Issues)
|
- Repository viewer including an online file editor
|
||||||
- Wiki
|
- Issues, Pull Requests and Wiki for repositories
|
||||||
- Issues
|
- Activity timeline and email notifications
|
||||||
- Fork / Pull request
|
- Account and group management with LDAP integration
|
||||||
- Email notification
|
- a Plug-in system
|
||||||
- Activity timeline
|
|
||||||
- Simple user and group management with LDAP integration
|
|
||||||
- Gravatar support
|
|
||||||
- Plug-in system
|
|
||||||
|
|
||||||
If you want to try the development version of GitBucket, see the documentation for developers at [Wiki](https://github.com/gitbucket/gitbucket/wiki).
|
If you want to try the development version of GitBucket, see the [Developer's Guide](https://github.com/gitbucket/gitbucket/blob/master/doc/how_to_run.md).
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
--------
|
--------
|
||||||
|
GitBucket requires **Java8**. You have to install it, if it is not already installed.
|
||||||
|
|
||||||
1. Download latest **gitbucket.war** from [the release page](https://github.com/gitbucket/gitbucket/releases).
|
1. Download the latest **gitbucket.war** from [the releases page](https://github.com/gitbucket/gitbucket/releases) and run it by `java -jar gitbucket.war`.
|
||||||
2. Deploy it to the Servlet 3.0 container such as Tomcat 7.x, Jetty 8.x, GlassFish 3.x or higher.
|
2. Go to `http://[hostname]:8080/` and log in with ID: **root** / Pass: **root**.
|
||||||
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 nginx)
|
You can specify following options:
|
||||||
|
|
||||||
The default administrator account is **root** and password is **root**.
|
- `--port=[NUMBER]`
|
||||||
|
- `--prefix=[CONTEXTPATH]`
|
||||||
|
- `--host=[HOSTNAME]`
|
||||||
|
- `--gitbucket.home=[DATA_DIR]`
|
||||||
|
- `--temp_dir=[TEMP_DIR]`
|
||||||
|
|
||||||
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.
|
`TEMP_DIR` is used as the [temporary directory for the jetty application context](https://www.eclipse.org/jetty/documentation/9.3.x/ref-temporary-directories.html). This is the directory into which the `gitbucket.war` file is unpacked, the source files are compiled, etc. If given this parameter **must** match the path of an existing directory or the application will quit reporting an error; if not given the path used will be a `tmp` directory inside the gitbucket home.
|
||||||
|
|
||||||
- --port=[NUMBER]
|
You can also deploy `gitbucket.war` to a servlet container which supports Servlet 3.0 (like Jetty, Tomcat, JBoss, etc)
|
||||||
- --prefix=[CONTEXTPATH]
|
|
||||||
- --host=[HOSTNAME]
|
|
||||||
- --gitbucket.home=[DATA_DIR]
|
|
||||||
|
|
||||||
To upgrade GitBucket, only replace gitbucket.war after stop GitBucket. All GitBucket data is stored in HOME/.gitbucket. So if you want to back up GitBucket data, copy this directory to the other disk.
|
For more information about installation on Mac or Windows Server (with IIS), or configuration of Apache or Nginx and also integration with other tools or services such as Jenkins or Slack, see [Wiki](https://github.com/gitbucket/gitbucket/wiki).
|
||||||
|
|
||||||
For Installation on Windows Server with IIS see [this wiki page](https://github.com/gitbucket/gitbucket/wiki/Installation-on-IIS-and-Helicontech-Zoo)
|
To upgrade GitBucket, replace `gitbucket.war` with the new version, after stopping GitBucket. All GitBucket data is stored in `HOME/.gitbucket` by default. So if you want to back up GitBucket's data, copy this directory to the backup location.
|
||||||
|
|
||||||
### Mac OS X
|
Plugins
|
||||||
#### Installing Via Homebrew
|
|
||||||
|
|
||||||
```
|
|
||||||
$ brew install gitbucket
|
|
||||||
==> Downloading https://github.com/takezoe/gitbucket/releases/download/1.10/gitbucket.war
|
|
||||||
######################################################################## 100.0%
|
|
||||||
==> Caveats
|
|
||||||
Note: When using launchctl the port will be 8080.
|
|
||||||
|
|
||||||
To have launchd start gitbucket at login:
|
|
||||||
ln -sfv /usr/local/opt/gitbucket/*.plist ~/Library/LaunchAgents
|
|
||||||
Then to load gitbucket now:
|
|
||||||
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.gitbucket.plist
|
|
||||||
Or, if you don't want/need launchctl, you can just run:
|
|
||||||
java -jar /usr/local/opt/gitbucket/libexec/gitbucket.war
|
|
||||||
==> Summary
|
|
||||||
/usr/local/Cellar/gitbucket/1.10: 3 files, 42M, built in 11 seconds
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Manual Installation
|
|
||||||
On OS X, generate `gitbucket.plist` by [this script](https://raw.githubusercontent.com/gitbucket/gitbucket/master/contrib/macosx/makePlist) and copy it to `~/Library/LaunchAgents/`
|
|
||||||
|
|
||||||
Run the following commands in `Terminal` to
|
|
||||||
|
|
||||||
- start gitbucket: `launchctl load ~/Library/LaunchAgents/gitbucket.plist`
|
|
||||||
- stop gitbucket: `launchctl unload ~/Library/LaunchAgents/gitbucket.plist`
|
|
||||||
|
|
||||||
Plug-ins
|
|
||||||
--------
|
--------
|
||||||
GitBucket has the plug-in system to extend GitBucket from outside of GitBucket. Some plug-ins are available now:
|
GitBucket has a plug-in system that allows extra functionality. Officially the following plug-ins are provided:
|
||||||
|
|
||||||
- [gitbucket-gist-plugin](https://github.com/gitbucket/gitbucket-gist-plugin)
|
- [gitbucket-gist-plugin](https://github.com/gitbucket/gitbucket-gist-plugin)
|
||||||
- [gitbucket-announce-plugin](https://github.com/gitbucket-plugins/gitbucket-announce-plugin)
|
- [gitbucket-emoji-plugin](https://github.com/gitbucket/gitbucket-emoji-plugin)
|
||||||
- [gitbucket-h2-backup-plugin](https://github.com/gitbucket-plugins/gitbucket-h2-backup-plugin)
|
- [gitbucket-pages-plugin](https://github.com/gitbucket/gitbucket-pages-plugin)
|
||||||
- [gitbucket-desktopnotify-plugin](https://github.com/yoshiyoshifujii/gitbucket-desktopnotify-plugin)
|
|
||||||
- [gitbucket-commitgraphs-plugin](https://github.com/yoshiyoshifujii/gitbucket-commitgraphs-plugin)
|
|
||||||
- [gitbucket-asciidoctor-plugin](https://github.com/lefou/gitbucket-asciidoctor-plugin)
|
|
||||||
|
|
||||||
You can find community plugins other than them at [gitbucket community plugins](http://gitbucket-plugins.github.io/).
|
You can find more plugins made by the community at [GitBucket community plugins](http://gitbucket-plugins.github.io/).
|
||||||
|
|
||||||
Support
|
Support
|
||||||
--------
|
--------
|
||||||
|
|
||||||
- If you have any question about GitBucket, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raise an issue.
|
- If you have any questions about GitBucket, see [Wiki](https://github.com/gitbucket/gitbucket/wiki) and check issues whether there is a same question or request in the past.
|
||||||
- Make sure check whether there is a same question or request in the past.
|
- If you can't find same question and report, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
|
||||||
- When raise a new issue, write subject in **English** at least.
|
- We can also provide support in Japanese other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
|
||||||
- We can also support in Japaneses other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
|
- Write an issue in English. At least, write subject in English.
|
||||||
- First priority of GitBucket is easy installation and reproduce GitHub behavior, so we might reject if your request is against it.
|
- The highest priority of GitBucket is the ease of installation and API compatibility with GitHub, so your feature request might be rejected if they go against those principles.
|
||||||
|
|
||||||
Release Notes
|
Release Notes
|
||||||
--------
|
-------------
|
||||||
|
### 4.13 - 29 May 2017
|
||||||
|
- Uploading files into the repository
|
||||||
|
- HTML is available in Markdown
|
||||||
|
- Added filter box to dropdown menus
|
||||||
|
|
||||||
|
### 4.12 - 30 Apr 2017
|
||||||
|
- [Gist plug-in](https://github.com/gitbucket/gitbucket-gist-plugin) provides JavaScript to embed snippet
|
||||||
|
- Dropdown menu filter in the branch comparing page
|
||||||
|
- Caution for the embedded H2 database
|
||||||
|
|
||||||
|
### 4.11 - 1 Apr 2017
|
||||||
|
- Deploy keys support
|
||||||
|
- Auto generate avatar images
|
||||||
|
- Collaborators of the private forked repository are copied from the original repository
|
||||||
|
- Cache avatar images in the browser
|
||||||
|
- New extension point to receive events about repository
|
||||||
|
|
||||||
|
### 4.10 - 25 Feb 2017
|
||||||
|
- Update to Scala 2.12, Scalatra 2.5 and Slick 3.2
|
||||||
|
- Display file size in the file viewer
|
||||||
|
|
||||||
|
### 4.9 - 29 Jan 2017
|
||||||
|
- GitLFS support
|
||||||
|
- Template for issues and pull requests
|
||||||
|
- Manual label color editing
|
||||||
|
- Account description
|
||||||
|
- `--tmp-dir` option for standalone mode
|
||||||
|
- More APIs for issues
|
||||||
|
- [List issues for a repository](https://developer.github.com/v3/issues/#list-issues-for-a-repository)
|
||||||
|
- [Create an issue](https://developer.github.com/v3/issues/#create-an-issue)
|
||||||
|
|
||||||
|
### 4.8 - 23 Dec 2016
|
||||||
|
- Search for repository names from the global header
|
||||||
|
- Filter repositories on the sidebar of the dashboard
|
||||||
|
- Search issues and wiki
|
||||||
|
- Keep pull request comments after new commits are pushed
|
||||||
|
- New web API to get a single issue
|
||||||
|
- Performance improvement for the repository viewer
|
||||||
|
|
||||||
|
### 4.7.1 - 28 Nov 2016
|
||||||
|
- Bug fix: group repositories are not shown in the your repositories list on the sidebar
|
||||||
|
- Small performance improvement of the dashboard
|
||||||
|
|
||||||
|
### 4.7 - 26 Nov 2016
|
||||||
|
- New permission system
|
||||||
|
- Dropdown filter for issue labels, milestones and assignees
|
||||||
|
- Keep sidebar folding status
|
||||||
|
- Link from milestone label to the issue list
|
||||||
|
|
||||||
|
### 4.6 - 29 Oct 2016
|
||||||
|
- Add disable option for forking
|
||||||
|
- Add History button to wiki page
|
||||||
|
- Git repository URL redirection for GitHub compatibility
|
||||||
|
- Get-Content API improvement
|
||||||
|
- Indicate who is group master in Members tab in group view
|
||||||
|
|
||||||
|
### 4.5 - 29 Sep 2016
|
||||||
|
- Attach files by dropping into textarea
|
||||||
|
- Issues / Pull requests switcher in dashboard
|
||||||
|
- HikariCP could be configured in `GITBUCKET_HOME/database.conf`
|
||||||
|
- Improve Cookie security
|
||||||
|
- Display commit count on the history button
|
||||||
|
- Improve mobile view
|
||||||
|
|
||||||
|
### 4.4 - 28 Aug 2016
|
||||||
|
- Import a SQL dump file to the database
|
||||||
|
- `go get` support in private repositories
|
||||||
|
- Sort milestones by due date
|
||||||
|
- apache-sshd has been updated to 1.2.0
|
||||||
|
|
||||||
|
### 4.3 - 30 Jul 2016
|
||||||
|
- Emoji support by [gitbucket-emoji-plugin](https://github.com/gitbucket/gitbucket-emoji-plugin)
|
||||||
|
- User name suggestion
|
||||||
|
- Add new web APIs and basic authentication support for API access
|
||||||
|
- Root Endpoint
|
||||||
|
- [List endpoints](https://developer.github.com/v3/#root-endpoint)
|
||||||
|
- [List Branches](https://developer.github.com/v3/repos/branches/#list-branches)
|
||||||
|
- [Get contents](https://developer.github.com/v3/repos/contents/#get-contents)
|
||||||
|
- [Get a Reference](https://developer.github.com/v3/git/refs/#get-a-reference)
|
||||||
|
- [List Collaborators](https://developer.github.com/v3/repos/collaborators/#list-collaborators)
|
||||||
|
- [List user repositories](https://developer.github.com/v3/repos/#list-user-repositories)
|
||||||
|
- [Get a group](https://developer.github.com/v3/orgs/#get-an-organization)
|
||||||
|
- [List group repositories](https://developer.github.com/v3/repos/#list-organization-repositories)
|
||||||
|
- Add new extension points
|
||||||
|
- `assetsMapping` : Supplies resources in plugin classpath as web assets
|
||||||
|
- `suggestionProvider` : Provides suggestion in the Markdown editing textarea
|
||||||
|
- `textDecorator` : Decorate text nodes in HTML which is converted from Markdown
|
||||||
|
|
||||||
|
### 4.2.1 - 3 Jul 2016
|
||||||
|
- Fix migration bug
|
||||||
|
|
||||||
|
This is hotfix for a critical bug in migration. If you are new installation, use 4.2.0. But if you have an exisiting installation and it had been updated to 4.0 from 3.x, you must update to 4.2.1.
|
||||||
|
|
||||||
|
### 4.2 - 2 Jul 2016
|
||||||
|
- New UI based on [AdminLTE](https://github.com/almasaeed2010/AdminLTE)
|
||||||
|
- git gc
|
||||||
|
- Issues and Wiki have been possible to be disabled
|
||||||
|
- SMTP configuration test mail
|
||||||
|
|
||||||
|
### 4.1 - 4 Jun 2016
|
||||||
|
- Generic ssh user
|
||||||
|
- Improve branch protection UI
|
||||||
|
- Default value of pull request title
|
||||||
|
|
||||||
|
### 4.0 - 30 Apr 2016
|
||||||
|
- MySQL and PostgreSQL support
|
||||||
|
- Data export and import
|
||||||
|
- Migration system has been switched to [solidbase](https://github.com/gitbucket/solidbase)
|
||||||
|
|
||||||
|
**Note:** You can upgrade to GitBucket 4.0 from 3.14. If your GitBucket is 3.13 or before, you have to upgrade 3.14 at first.
|
||||||
|
|
||||||
|
### 3.14 - 30 Apr 2016
|
||||||
|
- File attachment and search for wiki pages
|
||||||
|
- New extension points to add menus
|
||||||
|
- Content-Type of webhooks has been choosable
|
||||||
|
|
||||||
|
### 3.13 - 1 Apr 2016
|
||||||
|
- Refresh user interface for wide screen
|
||||||
|
- Add `pull_request` key in list issues API for pull requests
|
||||||
|
- Add `X-Hub-Signature` security to webhooks
|
||||||
|
- Provide SHA-256 checksum for `gitbucket.war`
|
||||||
|
|
||||||
|
### 3.12 - 27 Feb 2016
|
||||||
|
- New GitHub UI
|
||||||
|
- Improve mobile view
|
||||||
|
- Improve printing style
|
||||||
|
- Individual URL for pull request tabs
|
||||||
|
- SSH host configuration is separated from HTTP base URL
|
||||||
|
|
||||||
|
### 3.11 - 30 Jan 2016
|
||||||
|
- Upgrade Scalatra to 2.4
|
||||||
|
- Sidebar and Footer for Wiki
|
||||||
|
- Branch protection and receive hook extension point for plug-in
|
||||||
|
- Limit recent updated repositories list
|
||||||
|
- Issue actions look-alike GitHub
|
||||||
|
- Web API for labels
|
||||||
|
- Requires Java 8
|
||||||
|
|
||||||
|
### 3.10 - 30 Dec 2015
|
||||||
|
- Move to Bootstrap3
|
||||||
|
- New URL for raw contents (`raw/master/doc/activity.md` instead of `blob/master/doc/activity.md?raw=true`)
|
||||||
|
- Update xsbt-web-plugin
|
||||||
|
- Update H2 database
|
||||||
|
|
||||||
### 3.9 - 5 Dec 2015
|
### 3.9 - 5 Dec 2015
|
||||||
- GFM inline breaks support in Markdown
|
- GFM inline breaks support in Markdown
|
||||||
- WebHook on create review comment is available
|
- WebHook on create review comment is available
|
||||||
@@ -336,7 +453,3 @@ Release Notes
|
|||||||
|
|
||||||
### 1.0 - 04 Jul 2013
|
### 1.0 - 04 Jul 2013
|
||||||
- This is a first public release
|
- This is a first public release
|
||||||
|
|
||||||
Sponsors
|
|
||||||
--------
|
|
||||||
[](https://www.jetbrains.com/idea/)
|
|
||||||
|
|||||||
221
build.sbt
Normal file
221
build.sbt
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
val Organization = "io.github.gitbucket"
|
||||||
|
val Name = "gitbucket"
|
||||||
|
val GitBucketVersion = "4.13.0"
|
||||||
|
val ScalatraVersion = "2.5.0"
|
||||||
|
val JettyVersion = "9.3.19.v20170502"
|
||||||
|
|
||||||
|
lazy val root = (project in file(".")).enablePlugins(SbtTwirl, JettyPlugin)
|
||||||
|
|
||||||
|
sourcesInBase := false
|
||||||
|
organization := Organization
|
||||||
|
name := Name
|
||||||
|
version := GitBucketVersion
|
||||||
|
scalaVersion := "2.12.2"
|
||||||
|
|
||||||
|
// dependency settings
|
||||||
|
resolvers ++= Seq(
|
||||||
|
Classpaths.typesafeReleases,
|
||||||
|
Resolver.jcenterRepo,
|
||||||
|
"amateras" at "http://amateras.sourceforge.jp/mvn/",
|
||||||
|
"sonatype-snapshot" at "https://oss.sonatype.org/content/repositories/snapshots/",
|
||||||
|
"amateras-snapshot" at "http://amateras.sourceforge.jp/mvn-snapshot/"
|
||||||
|
)
|
||||||
|
libraryDependencies ++= Seq(
|
||||||
|
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.7.0.201704051617-r",
|
||||||
|
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.7.0.201704051617-r",
|
||||||
|
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
||||||
|
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
||||||
|
"org.json4s" %% "json4s-jackson" % "3.5.1",
|
||||||
|
"io.github.gitbucket" %% "scalatra-forms" % "1.1.0",
|
||||||
|
"commons-io" % "commons-io" % "2.5",
|
||||||
|
"io.github.gitbucket" % "solidbase" % "1.0.2",
|
||||||
|
"io.github.gitbucket" % "markedj" % "1.0.12",
|
||||||
|
"org.apache.commons" % "commons-compress" % "1.13",
|
||||||
|
"org.apache.commons" % "commons-email" % "1.4",
|
||||||
|
"org.apache.httpcomponents" % "httpclient" % "4.5.3",
|
||||||
|
"org.apache.sshd" % "apache-sshd" % "1.4.0" exclude("org.slf4j","slf4j-jdk14"),
|
||||||
|
"org.apache.tika" % "tika-core" % "1.14",
|
||||||
|
"com.github.takezoe" %% "blocking-slick-32" % "0.0.8",
|
||||||
|
"joda-time" % "joda-time" % "2.9.9",
|
||||||
|
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||||
|
"com.h2database" % "h2" % "1.4.195",
|
||||||
|
"mysql" % "mysql-connector-java" % "6.0.6",
|
||||||
|
"org.postgresql" % "postgresql" % "42.0.0",
|
||||||
|
"ch.qos.logback" % "logback-classic" % "1.2.3",
|
||||||
|
"com.zaxxer" % "HikariCP" % "2.6.1",
|
||||||
|
"com.typesafe" % "config" % "1.3.1",
|
||||||
|
"com.typesafe.akka" %% "akka-actor" % "2.5.0",
|
||||||
|
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.0.0",
|
||||||
|
"com.github.bkromhout" % "java-diff-utils" % "2.1.1",
|
||||||
|
"org.cache2k" % "cache2k-all" % "1.0.0.CR1",
|
||||||
|
"com.enragedginger" %% "akka-quartz-scheduler" % "1.6.0-akka-2.4.x" exclude("c3p0","c3p0"),
|
||||||
|
"net.coobird" % "thumbnailator" % "0.4.8",
|
||||||
|
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
||||||
|
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
||||||
|
"junit" % "junit" % "4.12" % "test",
|
||||||
|
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test",
|
||||||
|
"org.mockito" % "mockito-core" % "2.7.22" % "test",
|
||||||
|
"com.wix" % "wix-embedded-mysql" % "2.1.4" % "test",
|
||||||
|
"ru.yandex.qatools.embed" % "postgresql-embedded" % "2.0" % "test"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Compiler settings
|
||||||
|
scalacOptions := Seq("-deprecation", "-language:postfixOps", "-opt:l:method")
|
||||||
|
javacOptions in compile ++= Seq("-target", "8", "-source", "8")
|
||||||
|
javaOptions in Jetty += "-Dlogback.configurationFile=/logback-dev.xml"
|
||||||
|
|
||||||
|
// Test settings
|
||||||
|
//testOptions in Test += Tests.Argument("-l", "ExternalDBTest")
|
||||||
|
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
|
||||||
|
|
||||||
|
// Packaging options
|
||||||
|
packageOptions += Package.MainClass("JettyLauncher")
|
||||||
|
|
||||||
|
// Assembly settings
|
||||||
|
test in assembly := {}
|
||||||
|
assemblyMergeStrategy in assembly := {
|
||||||
|
case PathList("META-INF", xs @ _*) =>
|
||||||
|
(xs map {_.toLowerCase}) match {
|
||||||
|
case ("manifest.mf" :: Nil) => MergeStrategy.discard
|
||||||
|
case _ => MergeStrategy.discard
|
||||||
|
}
|
||||||
|
case x => MergeStrategy.first
|
||||||
|
}
|
||||||
|
|
||||||
|
// JRebel
|
||||||
|
Seq(jrebelSettings: _*)
|
||||||
|
|
||||||
|
jrebel.webLinks += (target in webappPrepare).value
|
||||||
|
jrebel.enabled := System.getenv().get("JREBEL") != null
|
||||||
|
javaOptions in Jetty ++= Option(System.getenv().get("JREBEL")).toSeq.flatMap { path =>
|
||||||
|
Seq("-noverify", "-XX:+UseConcMarkSweepGC", "-XX:+CMSClassUnloadingEnabled", s"-javaagent:${path}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create executable war file
|
||||||
|
val executableConfig = config("executable").hide
|
||||||
|
Keys.ivyConfigurations += executableConfig
|
||||||
|
libraryDependencies ++= Seq(
|
||||||
|
"org.eclipse.jetty" % "jetty-security" % JettyVersion % "executable",
|
||||||
|
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "executable",
|
||||||
|
"org.eclipse.jetty" % "jetty-continuation" % JettyVersion % "executable",
|
||||||
|
"org.eclipse.jetty" % "jetty-server" % JettyVersion % "executable",
|
||||||
|
"org.eclipse.jetty" % "jetty-xml" % JettyVersion % "executable",
|
||||||
|
"org.eclipse.jetty" % "jetty-http" % JettyVersion % "executable",
|
||||||
|
"org.eclipse.jetty" % "jetty-servlet" % JettyVersion % "executable",
|
||||||
|
"org.eclipse.jetty" % "jetty-io" % JettyVersion % "executable",
|
||||||
|
"org.eclipse.jetty" % "jetty-util" % JettyVersion % "executable"
|
||||||
|
)
|
||||||
|
|
||||||
|
val executableKey = TaskKey[File]("executable")
|
||||||
|
executableKey := {
|
||||||
|
import java.util.jar.{ Manifest => JarManifest }
|
||||||
|
import java.util.jar.Attributes.{ Name => AttrName }
|
||||||
|
|
||||||
|
val workDir = Keys.target.value / "executable"
|
||||||
|
val warName = Keys.name.value + ".war"
|
||||||
|
|
||||||
|
val log = streams.value.log
|
||||||
|
log info s"building executable webapp in ${workDir}"
|
||||||
|
|
||||||
|
// initialize temp directory
|
||||||
|
val temp = workDir / "webapp"
|
||||||
|
IO delete temp
|
||||||
|
|
||||||
|
// include jetty classes
|
||||||
|
val jettyJars = Keys.update.value select configurationFilter(name = executableConfig.name)
|
||||||
|
jettyJars foreach { jar =>
|
||||||
|
IO unzip (jar, temp, (name:String) =>
|
||||||
|
(name startsWith "javax/") ||
|
||||||
|
(name startsWith "org/")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// include original war file
|
||||||
|
val warFile = (Keys.`package`).value
|
||||||
|
IO unzip (warFile, temp)
|
||||||
|
|
||||||
|
// include launcher classes
|
||||||
|
val classDir = (Keys.classDirectory in Compile).value
|
||||||
|
val launchClasses = Seq("JettyLauncher.class" /*, "HttpsSupportConnector.class" */)
|
||||||
|
launchClasses foreach { name =>
|
||||||
|
IO copyFile (classDir / name, temp / name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// zip it up
|
||||||
|
IO delete (temp / "META-INF" / "MANIFEST.MF")
|
||||||
|
val contentMappings = (temp.*** --- PathFinder(temp)).get pair relativeTo(temp)
|
||||||
|
val manifest = new JarManifest
|
||||||
|
manifest.getMainAttributes put (AttrName.MANIFEST_VERSION, "1.0")
|
||||||
|
manifest.getMainAttributes put (AttrName.MAIN_CLASS, "JettyLauncher")
|
||||||
|
val outputFile = workDir / warName
|
||||||
|
IO jar (contentMappings, outputFile, manifest)
|
||||||
|
|
||||||
|
// generate checksums
|
||||||
|
Seq(
|
||||||
|
"md5" -> "MD5",
|
||||||
|
"sha1" -> "SHA-1",
|
||||||
|
"sha256" -> "SHA-256"
|
||||||
|
)
|
||||||
|
.foreach { case (extension, algorithm) =>
|
||||||
|
val checksumFile = workDir / (warName + "." + extension)
|
||||||
|
Checksums generate (outputFile, checksumFile, algorithm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// done
|
||||||
|
log info s"built executable webapp ${outputFile}"
|
||||||
|
outputFile
|
||||||
|
}
|
||||||
|
publishTo := {
|
||||||
|
val nexus = "https://oss.sonatype.org/"
|
||||||
|
if (version.value.trim.endsWith("SNAPSHOT")) Some("snapshots" at nexus + "content/repositories/snapshots")
|
||||||
|
else Some("releases" at nexus + "service/local/staging/deploy/maven2")
|
||||||
|
}
|
||||||
|
publishMavenStyle := true
|
||||||
|
pomIncludeRepository := { _ => false }
|
||||||
|
pomExtra := (
|
||||||
|
<url>https://github.com/gitbucket/gitbucket</url>
|
||||||
|
<licenses>
|
||||||
|
<license>
|
||||||
|
<name>The Apache Software License, Version 2.0</name>
|
||||||
|
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||||
|
</license>
|
||||||
|
</licenses>
|
||||||
|
<scm>
|
||||||
|
<url>https://github.com/gitbucket/gitbucket</url>
|
||||||
|
<connection>scm:git:https://github.com/gitbucket/gitbucket.git</connection>
|
||||||
|
</scm>
|
||||||
|
<developers>
|
||||||
|
<developer>
|
||||||
|
<id>takezoe</id>
|
||||||
|
<name>Naoki Takezoe</name>
|
||||||
|
<url>https://github.com/takezoe</url>
|
||||||
|
</developer>
|
||||||
|
<developer>
|
||||||
|
<id>shimamoto</id>
|
||||||
|
<name>Takako Shimamoto</name>
|
||||||
|
<url>https://github.com/shimamoto</url>
|
||||||
|
</developer>
|
||||||
|
<developer>
|
||||||
|
<id>tanacasino</id>
|
||||||
|
<name>Tomofumi Tanaka</name>
|
||||||
|
<url>https://github.com/tanacasino</url>
|
||||||
|
</developer>
|
||||||
|
<developer>
|
||||||
|
<id>mrkm4ntr</id>
|
||||||
|
<name>Shintaro Murakami</name>
|
||||||
|
<url>https://github.com/mrkm4ntr</url>
|
||||||
|
</developer>
|
||||||
|
<developer>
|
||||||
|
<id>nazoking</id>
|
||||||
|
<name>nazoking</name>
|
||||||
|
<url>https://github.com/nazoking</url>
|
||||||
|
</developer>
|
||||||
|
<developer>
|
||||||
|
<id>McFoggy</id>
|
||||||
|
<name>Matthieu Brouillard</name>
|
||||||
|
<url>https://github.com/McFoggy</url>
|
||||||
|
</developer>
|
||||||
|
</developers>
|
||||||
|
)
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
%~d0
|
|
||||||
cmd /k cd %~p0
|
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
RPM spec file and init script for Red Hat Enterprise Linux 6.x.
|
RPM spec file and init script for Red Hat Enterprise Linux 6.x.
|
||||||
|
|
||||||
To create RPM:
|
To create RPM:
|
||||||
|
|
||||||
1. Edit `../../gitbucket.conf` to suit.
|
1. Edit `../../gitbucket.conf` to suit.
|
||||||
2. Edit `gitbucket.init` to suit.
|
2. Edit `gitbucket.init` to suit.
|
||||||
3. Edit `gitbucket.spec` to suit.
|
3. Edit `gitbucket.spec` to suit.
|
||||||
|
|||||||
@@ -1,37 +1,54 @@
|
|||||||
Automatic Schema Updating
|
Automatic Schema Updating
|
||||||
========
|
========
|
||||||
GitBucket uses H2 database to manage project and account data. GitBucket updates database schema automatically in the first run after the upgrading.
|
GitBucket updates database schema automatically using [Solidbase](https://github.com/gitbucket/solidbase) in the first run after the upgrading.
|
||||||
|
|
||||||
To release a new version of GitBucket, add the version definition to the [gitbucket.core.servlet.AutoUpdate](https://github.com/gitbucket/gitbucket/blob/master/src/main/scala/gitbucket/core/servlet/AutoUpdate.scala) at first.
|
To release a new version of GitBucket, add the version definition to the [gitbucket.core.GitBucketCoreModule](https://github.com/gitbucket/gitbucket/blob/master/src/main/scala/gitbucket/core/GitBucketCoreModule.scala) at first.
|
||||||
|
|
||||||
```scala
|
```scala
|
||||||
object AutoUpdate {
|
object GitBucketCoreModule extends Module("gitbucket-core",
|
||||||
...
|
new Version("4.0.0",
|
||||||
/**
|
new LiquibaseMigration("update/gitbucket-core_4.0.xml"),
|
||||||
* The history of versions. A head of this sequence is the current BitBucket version.
|
new SqlMigration("update/gitbucket-core_4.0.sql")
|
||||||
*/
|
),
|
||||||
val versions = Seq(
|
new Version("4.1.0"),
|
||||||
Version(1, 0)
|
new Version("4.2.0",
|
||||||
|
new LiquibaseMigration("update/gitbucket-core_4.2.xml")
|
||||||
)
|
)
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
Next, add a SQL file which updates database schema into [/src/main/resources/update/](https://github.com/gitbucket/gitbucket/tree/master/src/main/resources/update) as ```MAJOR_MINOR.sql```.
|
|
||||||
|
|
||||||
GitBucket stores the current version to ```GITBUCKET_HOME/version``` and checks it at start-up. If the stored version differs from the actual version, it executes differences of SQL files between the stored version and the actual version. And ```GITBUCKET_HOME/version``` is updated by the actual version.
|
|
||||||
|
|
||||||
We can also add any Scala code for upgrade GitBucket which modifies resources other than database. Override ```Version.update``` like below:
|
|
||||||
|
|
||||||
```scala
|
|
||||||
val versions = Seq(
|
|
||||||
new Version(1, 3){
|
|
||||||
override def update(conn: Connection): Unit = {
|
|
||||||
super.update(conn)
|
|
||||||
// Add any code here!
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Version(1, 2),
|
|
||||||
Version(1, 1),
|
|
||||||
Version(1, 0)
|
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Next, add a XML file which updates database schema into [/src/main/resources/update/](https://github.com/gitbucket/gitbucket/tree/master/src/main/resources/update) with a filenane defined in `GitBucketCoreModule`.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<changeSet>
|
||||||
|
<addColumn tableName="REPOSITORY">
|
||||||
|
<column name="ENABLE_WIKI" type="boolean" nullable="false" defaultValueBoolean="true"/>
|
||||||
|
<column name="ENABLE_ISSUES" type="boolean" nullable="false" defaultValueBoolean="true"/>
|
||||||
|
<column name="EXTERNAL_WIKI_URL" type="varchar(200)" nullable="true"/>
|
||||||
|
<column name="EXTERNAL_ISSUES_URL" type="varchar(200)" nullable="true"/>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
```
|
||||||
|
|
||||||
|
Solidbase stores the current version to `VERSIONS` table and checks it at start-up. If the stored version differs from the actual version, it executes differences between the stored version and the actual version.
|
||||||
|
|
||||||
|
We can add the SQL file instead of the XML file using `SqlMigration`. It try to load a SQL file from classpath as following order:
|
||||||
|
|
||||||
|
1. Specified path (if specified)
|
||||||
|
2. `${moduleId}_${version}_${database}.sql`
|
||||||
|
3. `${moduleId}_${version}.sql`
|
||||||
|
|
||||||
|
Also we can add any code by extending `Migration`:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
object GitBucketCoreModule extends Module("gitbucket-core",
|
||||||
|
new Version("4.0.0", new Migration(){
|
||||||
|
override def migrate(moduleId: String, version: String, context: java.util.Map[String, String]): Unit = {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
See more details [README of Solidbase](https://github.com/gitbucket/solidbase).
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ This directory has following structure:
|
|||||||
* /HOME/gitbucket
|
* /HOME/gitbucket
|
||||||
* /repositories
|
* /repositories
|
||||||
* /USER_NAME
|
* /USER_NAME
|
||||||
* / REPO_NAME.git (substance of repository. GitServlet sees this directory)
|
* /REPO_NAME.git (substance of repository. GitServlet sees this directory)
|
||||||
* / REPO_NAME
|
* /REPO_NAME
|
||||||
* /issues (files which are attached to issue)
|
* /issues (files which are attached to issue)
|
||||||
* / REPO_NAME.wiki.git (wiki repository)
|
* /REPO_NAME.wiki.git (wiki repository)
|
||||||
* /data
|
* /data
|
||||||
* /USER_NAME
|
* /USER_NAME
|
||||||
* /files
|
* /files
|
||||||
|
|||||||
@@ -1,18 +1,24 @@
|
|||||||
How to run from the source tree
|
How to run from the source tree
|
||||||
========
|
========
|
||||||
|
|
||||||
|
Install [sbt](http://www.scala-sbt.org/index.html) at first.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ brew install sbt
|
||||||
|
```
|
||||||
|
|
||||||
Run for Development
|
Run for Development
|
||||||
--------
|
--------
|
||||||
|
|
||||||
If you want to test GitBucket, input following command at the root directory of the source tree.
|
If you want to test GitBucket, type the following command in the root directory of the source tree.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ sbt ~jetty:start
|
$ sbt ~jetty:start
|
||||||
```
|
```
|
||||||
|
|
||||||
Then access to `http://localhost:8080/` by your browser. The default administrator account is `root` and password is `root`.
|
Then access `http://localhost:8080/` in your browser. The default administrator account is `root` and password is `root`.
|
||||||
|
|
||||||
Source code modification is detected and reloaded automatically. You can modify logging configuration by editing `src/main/resources/logback-dev.xml`.
|
Source code modifications are detected and a reloaded happens automatically. You can modify the logging configuration by editing `src/main/resources/logback-dev.xml`.
|
||||||
|
|
||||||
Build war file
|
Build war file
|
||||||
--------
|
--------
|
||||||
@@ -23,11 +29,20 @@ To build war file, run the following command:
|
|||||||
$ sbt package
|
$ sbt package
|
||||||
```
|
```
|
||||||
|
|
||||||
`gitbucket_2.11-x.x.x.war` is generated into `target/scala-2.11`.
|
`gitbucket_2.12-x.x.x.war` is generated into `target/scala-2.12`.
|
||||||
|
|
||||||
To build executable war file, run
|
To build an executable war file, run
|
||||||
|
|
||||||
* Windows: Not available
|
```
|
||||||
* Linux: `./release/make-release-war.sh`
|
$ sbt executable
|
||||||
|
```
|
||||||
|
|
||||||
at the top of the source tree. It generates executable `gitbucket.war` into `target/scala-2.11`. We release this war file as release artifact.
|
at the top of the source tree. It generates executable `gitbucket.war` into `target/executable`. We release this war file as release artifact.
|
||||||
|
|
||||||
|
Run tests spec
|
||||||
|
---------
|
||||||
|
To run the full series of tests, run the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sbt test
|
||||||
|
```
|
||||||
|
|||||||
148
doc/jrebel.md
Normal file
148
doc/jrebel.md
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
JRebel integration (optional)
|
||||||
|
=============================
|
||||||
|
|
||||||
|
[JRebel](http://zeroturnaround.com/software/jrebel/) is a JVM plugin that makes developing web apps much faster.
|
||||||
|
JRebel is generally able to eliminate the need for the following slow "app restart" in sbt following a code change:
|
||||||
|
|
||||||
|
```
|
||||||
|
> jetty:start
|
||||||
|
```
|
||||||
|
|
||||||
|
While JRebel is not open source, it does reload your code faster than the `~;copy-resources;aux-compile` way of doing things using `sbt`.
|
||||||
|
|
||||||
|
It's only used during development, and doesn't change your deployed app in any way.
|
||||||
|
|
||||||
|
JRebel used to be free for Scala developers, but that changed recently, and now there's a cost associated with usage for Scala. There are trial plans and free non-commercial licenses available if you just want to try it out.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## 1. Get a JRebel license
|
||||||
|
|
||||||
|
Sign up for a [usage plan](https://my.jrebel.com/). You will need to create an account.
|
||||||
|
|
||||||
|
## 2. Download JRebel
|
||||||
|
|
||||||
|
Download the most recent ["nosetup" JRebel zip](http://zeroturnaround.com/software/jrebel/download/prev-releases/).
|
||||||
|
Next, unzip the downloaded file.
|
||||||
|
|
||||||
|
## 3. Activate
|
||||||
|
|
||||||
|
Follow the [instructions on the JRebel website](http://zeroturnaround.com/software/jrebel/download/prev-releases/) to activate your downloaded JRebel.
|
||||||
|
|
||||||
|
You can use the default settings for all the configurations.
|
||||||
|
|
||||||
|
You don't need to integrate with your IDE, since we're using sbt to do the servlet deployment.
|
||||||
|
|
||||||
|
## 4. Tell jvm where JRebel is
|
||||||
|
|
||||||
|
Fortunately, the gitbucket project is already set up to use JRebel.
|
||||||
|
You only need to tell jvm where to find the jrebel jar.
|
||||||
|
|
||||||
|
To do so, edit your shell resource file (usually `~/.bash_profile` on Mac, and `~/.bashrc` on Linux), and add the following line:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export JREBEL=/path/to/jrebel/jrebel.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
For example, if you unzipped your JRebel download in your home directory, you whould use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export JREBEL=~/jrebel/jrebel.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
Now reload your shell:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ source ~/.bash_profile # on Mac
|
||||||
|
$ source ~/.bashrc # on Linux
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. See it in action!
|
||||||
|
|
||||||
|
Now you're ready to use JRebel with the gitbucket.
|
||||||
|
When you run sbt as normal, you will see a long message from JRebel, indicating it has loaded.
|
||||||
|
Here's an abbreviated version of what you will see:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./sbt
|
||||||
|
[info] Loading project definition from /git/gitbucket/project
|
||||||
|
[info] Set current project to gitbucket (in build file:/git/gitbucket/)
|
||||||
|
>
|
||||||
|
```
|
||||||
|
|
||||||
|
You will start the servlet container slightly differently now that you're using sbt.
|
||||||
|
|
||||||
|
```
|
||||||
|
> jetty:start
|
||||||
|
:
|
||||||
|
[info] starting server ...
|
||||||
|
[success] Total time: 3 s, completed Jan 3, 2016 9:47:55 PM
|
||||||
|
2016-01-03 21:47:57 JRebel:
|
||||||
|
2016-01-03 21:47:57 JRebel: A newer version '6.3.1' is available for download
|
||||||
|
2016-01-03 21:47:57 JRebel: from http://zeroturnaround.com/software/jrebel/download/
|
||||||
|
2016-01-03 21:47:57 JRebel:
|
||||||
|
2016-01-03 21:47:58 JRebel: Contacting myJRebel server ..
|
||||||
|
2016-01-03 21:47:59 JRebel: Directory '/git/gitbucket/target/scala-2.11/classes' will be monitored for changes.
|
||||||
|
2016-01-03 21:47:59 JRebel: Directory '/git/gitbucket/target/scala-2.11/test-classes' will be monitored for changes.
|
||||||
|
2016-01-03 21:47:59 JRebel: Directory '/git/gitbucket/target/webapp' will be monitored for changes.
|
||||||
|
2016-01-03 21:48:00 JRebel:
|
||||||
|
2016-01-03 21:48:00 JRebel: #############################################################
|
||||||
|
2016-01-03 21:48:00 JRebel:
|
||||||
|
2016-01-03 21:48:00 JRebel: JRebel Legacy Agent 6.2.5 (201509291538)
|
||||||
|
2016-01-03 21:48:00 JRebel: (c) Copyright ZeroTurnaround AS, Estonia, Tartu.
|
||||||
|
2016-01-03 21:48:00 JRebel:
|
||||||
|
2016-01-03 21:48:00 JRebel: Over the last 30 days JRebel prevented
|
||||||
|
2016-01-03 21:48:00 JRebel: at least 182 redeploys/restarts saving you about 7.4 hours.
|
||||||
|
2016-01-03 21:48:00 JRebel:
|
||||||
|
2016-01-03 21:48:00 JRebel: Over the last 324 days JRebel prevented
|
||||||
|
2016-01-03 21:48:00 JRebel: at least 1538 redeploys/restarts saving you about 62.4 hours.
|
||||||
|
2016-01-03 21:48:00 JRebel:
|
||||||
|
2016-01-03 21:48:00 JRebel: Licensed to nazo king (using myJRebel).
|
||||||
|
2016-01-03 21:48:00 JRebel:
|
||||||
|
2016-01-03 21:48:00 JRebel:
|
||||||
|
2016-01-03 21:48:00 JRebel: #############################################################
|
||||||
|
2016-01-03 21:48:00 JRebel:
|
||||||
|
:
|
||||||
|
|
||||||
|
> ~ copy-resources
|
||||||
|
[success] Total time: 0 s, completed Jan 3, 2016 9:13:54 PM
|
||||||
|
1. Waiting for source changes... (press enter to interrupt)
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, change your code.
|
||||||
|
For example, you can change the title on `src/main/twirl/gitbucket/core/main.scala.html` like this:
|
||||||
|
|
||||||
|
```html
|
||||||
|
:
|
||||||
|
<a class="navbar-brand" href="@path/">
|
||||||
|
<img src="@assets/common/images/gitbucket.png" style="width: 24px; height: 24px;"/>GitBucket
|
||||||
|
@defining(AutoUpdate.getCurrentVersion){ version =>
|
||||||
|
<span class="header-version">@version.majorVersion.@version.minorVersion</span>
|
||||||
|
}
|
||||||
|
change code !!!!!!!!!!!!!!!!
|
||||||
|
</a>
|
||||||
|
:
|
||||||
|
```
|
||||||
|
|
||||||
|
If JRebel is doing is correctly installed you will see a notice for you:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Waiting for source changes... (press enter to interrupt)
|
||||||
|
2016-01-03 21:48:42 JRebel: Reloading class 'gitbucket.core.html.main$'.
|
||||||
|
[info] Wrote rebel.xml to /git/gitbucket/target/scala-2.11/resource_managed/main/rebel.xml
|
||||||
|
[info] Compiling 1 Scala source to /git/gitbucket/target/scala-2.11/classes...
|
||||||
|
[success] Total time: 3 s, completed Jan 3, 2016 9:48:55 PM
|
||||||
|
2. Waiting for source changes... (press enter to interrupt)
|
||||||
|
```
|
||||||
|
|
||||||
|
And you reload browser, JRebel give notice of that it has reloaded classes:
|
||||||
|
|
||||||
|
```
|
||||||
|
[success] Total time: 3 s, completed Jan 3, 2016 9:48:55 PM
|
||||||
|
2. Waiting for source changes... (press enter to interrupt)
|
||||||
|
2016-01-03 21:49:13 JRebel: Reloading class 'gitbucket.core.html.main$'.
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Limitations
|
||||||
|
|
||||||
|
JRebel is nearly always able to eliminate the need to explicitly reload your container after a code change. However, if you change any of your routes patterns, there is nothing JRebel can do, you will have to run `jetty:start`.
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
Notification Email
|
Notification Email
|
||||||
========
|
========
|
||||||
|
|
||||||
GitBucket sends email to target users by enabling the notification email by an administrator.
|
GitBucket can send email notification to users if this feature is enabled by an administrator.
|
||||||
|
|
||||||
The timing of the notification are as follows:
|
The timing of the notification are as follows:
|
||||||
|
|
||||||
@@ -20,4 +20,4 @@ Notified users are as follows:
|
|||||||
* collaborators
|
* collaborators
|
||||||
* participants
|
* participants
|
||||||
|
|
||||||
However, the operation in person is excluded from the target.
|
However, the person performing the operation is excluded from the notification.
|
||||||
|
|||||||
@@ -9,3 +9,4 @@ Developer's Guide
|
|||||||
* [Notification Email](notification.md)
|
* [Notification Email](notification.md)
|
||||||
* [Automatic Schema Updating](auto_update.md)
|
* [Automatic Schema Updating](auto_update.md)
|
||||||
* [Release Operation](release.md)
|
* [Release Operation](release.md)
|
||||||
|
* [JRebel integration (optional)](jrebel.md)
|
||||||
|
|||||||
@@ -6,49 +6,56 @@ Update version number
|
|||||||
|
|
||||||
Note to update version number in files below:
|
Note to update version number in files below:
|
||||||
|
|
||||||
### project/build.scala
|
### build.sbt
|
||||||
|
|
||||||
```scala
|
```scala
|
||||||
object MyBuild extends Build {
|
val Organization = "gitbucket"
|
||||||
val Organization = "gitbucket"
|
val Name = "gitbucket"
|
||||||
val Name = "gitbucket"
|
val GitBucketVersion = "4.0.0" // <---- update version!!
|
||||||
val Version = "3.3.0" // <---- update version!!
|
val ScalatraVersion = "2.4.0"
|
||||||
val ScalaVersion = "2.11.6"
|
val JettyVersion = "9.3.6.v20151106"
|
||||||
val ScalatraVersion = "2.3.1"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### src/main/scala/gitbucket/core/servlet/AutoUpdate.scala
|
### src/main/scala/gitbucket/core/GitBucketCoreModule.scala
|
||||||
|
|
||||||
```scala
|
```scala
|
||||||
object AutoUpdate {
|
object GitBucketCoreModule extends Module("gitbucket-core",
|
||||||
|
new Version("4.0.0",
|
||||||
/**
|
new LiquibaseMigration("update/gitbucket-core_4.0.xml"),
|
||||||
* The history of versions. A head of this sequence is the current BitBucket version.
|
new SqlMigration("update/gitbucket-core_4.0.sql")
|
||||||
*/
|
),
|
||||||
val versions = Seq(
|
// add new version definition
|
||||||
new Version(3, 3), // <---- add this line!!
|
new Version("4.1.0",
|
||||||
new Version(3, 2),
|
new LiquibaseMigration("update/gitbucket-core_4.1.xml")
|
||||||
|
)
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
Generate release files
|
Generate release files
|
||||||
--------
|
--------
|
||||||
|
|
||||||
Note: Release operation requires [Ant](http://ant.apache.org/) and [Maven](https://maven.apache.org/).
|
|
||||||
|
|
||||||
### Make release war file
|
### Make release war file
|
||||||
|
|
||||||
Run `release/make-release-war.sh`. The release war file is generated into `target/scala-2.11/gitbucket.war`.
|
Run `sbt executable`. The release war file and fingerprint are generated into `target/executable/gitbucket.war`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ cd release
|
$ sbt executable
|
||||||
$ ./make-release-war.sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Deploy assembly jar file
|
### Deploy assembly jar file
|
||||||
|
|
||||||
For plug-in development, we have to publish the assembly jar file to the public Maven repository by `release/deploy-assembly-jar.sh`.
|
For plug-in development, we have to publish the GitBucket jar file to the Maven central repository as well. At first, hit following command to publish artifacts to the sonatype OSS repository:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ cd release/
|
$ sbt publish-signed
|
||||||
$ ./deploy-assembly-jar.sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Then logged-in https://oss.sonatype.org/ and delete following files from the staging repository:
|
||||||
|
|
||||||
|
- gitbucket_2.12-x.x.x.war
|
||||||
|
- gitbucket_2.12-x.x.x.war.asc
|
||||||
|
- gitbucket_2.12-x.x.x.war.asc.md5
|
||||||
|
- gitbucket_2.12-x.x.x.war.asc.sha1
|
||||||
|
- gitbucket_2.12-x.x.x.war.md5
|
||||||
|
|
||||||
|
At last, close and release the repository.
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,11 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
version=$1
|
|
||||||
output_dir=`dirname $0`
|
|
||||||
git rm -f ${output_dir}/jetty-*.jar
|
|
||||||
for name in 'io' 'servlet' 'xml' 'continuation' 'security' 'util' 'http' 'server' 'webapp'
|
|
||||||
do
|
|
||||||
jar_filename="jetty-${name}-${version}.jar"
|
|
||||||
wget "http://repo1.maven.org/maven2/org/eclipse/jetty/jetty-${name}/${version}/${jar_filename}" -O ${output_dir}/${jar_filename}
|
|
||||||
done
|
|
||||||
git add ${output_dir}/*.jar
|
|
||||||
git commit
|
|
||||||
34
project/Checksums.scala
Normal file
34
project/Checksums.scala
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import java.security.MessageDigest;
|
||||||
|
import scala.annotation._
|
||||||
|
import sbt._
|
||||||
|
import sbt.Using._
|
||||||
|
|
||||||
|
object Checksums {
|
||||||
|
private val bufferSize = 2048
|
||||||
|
|
||||||
|
def generate(source:File, target:File, algorithm:String):Unit =
|
||||||
|
IO write (target, compute(source, algorithm))
|
||||||
|
|
||||||
|
def compute(file:File, algorithm:String):String =
|
||||||
|
hex(raw(file, algorithm))
|
||||||
|
|
||||||
|
def raw(file:File, algorithm:String):Array[Byte] =
|
||||||
|
(Using fileInputStream file) { is =>
|
||||||
|
val md = MessageDigest getInstance algorithm
|
||||||
|
val buf = new Array[Byte](bufferSize)
|
||||||
|
md.reset()
|
||||||
|
@tailrec
|
||||||
|
def loop() {
|
||||||
|
val len = is read buf
|
||||||
|
if (len != -1) {
|
||||||
|
md update (buf, 0, len)
|
||||||
|
loop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loop()
|
||||||
|
md.digest()
|
||||||
|
}
|
||||||
|
|
||||||
|
def hex(bytes:Array[Byte]):String =
|
||||||
|
bytes map { it => "%02x" format (it.toInt & 0xff) } mkString ""
|
||||||
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
sbt.version=0.13.8
|
sbt.version=0.13.15
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
import com.earldouglas.xwp.JettyPlugin
|
|
||||||
import play.twirl.sbt.SbtTwirl
|
|
||||||
import sbt.Keys._
|
|
||||||
import sbt._
|
|
||||||
import sbtassembly.AssemblyKeys._
|
|
||||||
import sbtassembly._
|
|
||||||
import JettyPlugin.autoImport._
|
|
||||||
|
|
||||||
object MyBuild extends Build {
|
|
||||||
val Organization = "gitbucket"
|
|
||||||
val Name = "gitbucket"
|
|
||||||
val Version = "3.9.1"
|
|
||||||
val ScalaVersion = "2.11.6"
|
|
||||||
val ScalatraVersion = "2.3.1"
|
|
||||||
|
|
||||||
lazy val project = Project (
|
|
||||||
"gitbucket",
|
|
||||||
file(".")
|
|
||||||
)
|
|
||||||
// .settings(ScalatraPlugin.scalatraWithJRebel: _*)
|
|
||||||
.settings(
|
|
||||||
test in assembly := {},
|
|
||||||
assemblyMergeStrategy in assembly := {
|
|
||||||
case PathList("META-INF", xs @ _*) =>
|
|
||||||
(xs map {_.toLowerCase}) match {
|
|
||||||
case ("manifest.mf" :: Nil) => MergeStrategy.discard
|
|
||||||
case _ => MergeStrategy.discard
|
|
||||||
}
|
|
||||||
case x => MergeStrategy.first
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.settings(
|
|
||||||
sourcesInBase := false,
|
|
||||||
organization := Organization,
|
|
||||||
name := Name,
|
|
||||||
version := Version,
|
|
||||||
scalaVersion := ScalaVersion,
|
|
||||||
resolvers ++= Seq(
|
|
||||||
Classpaths.typesafeReleases,
|
|
||||||
"amateras-repo" at "http://amateras.sourceforge.jp/mvn/",
|
|
||||||
"amateras-snapshot-repo" at "http://amateras.sourceforge.jp/mvn-snapshot/"
|
|
||||||
),
|
|
||||||
scalacOptions := Seq("-deprecation", "-language:postfixOps"),
|
|
||||||
libraryDependencies ++= Seq(
|
|
||||||
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "3.4.2.201412180340-r",
|
|
||||||
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "3.4.2.201412180340-r",
|
|
||||||
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
|
||||||
"org.scalatra" %% "scalatra-specs2" % ScalatraVersion % "test",
|
|
||||||
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
|
||||||
"org.json4s" %% "json4s-jackson" % "3.2.11",
|
|
||||||
"jp.sf.amateras" %% "scalatra-forms" % "0.2.0",
|
|
||||||
"commons-io" % "commons-io" % "2.4",
|
|
||||||
"io.github.gitbucket" % "markedj" % "1.0.6-SNAPSHOT",
|
|
||||||
"org.apache.commons" % "commons-compress" % "1.9",
|
|
||||||
"org.apache.commons" % "commons-email" % "1.3.3",
|
|
||||||
"org.apache.httpcomponents" % "httpclient" % "4.3.6",
|
|
||||||
"org.apache.sshd" % "apache-sshd" % "1.0.0",
|
|
||||||
"org.apache.tika" % "tika-core" % "1.10",
|
|
||||||
"com.typesafe.slick" %% "slick" % "2.1.0",
|
|
||||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
|
||||||
"com.h2database" % "h2" % "1.4.180",
|
|
||||||
"ch.qos.logback" % "logback-classic" % "1.1.1",
|
|
||||||
"org.eclipse.jetty" % "jetty-webapp" % "8.1.16.v20140903" % "provided",
|
|
||||||
"javax.servlet" % "javax.servlet-api" % "3.0.1" % "provided",
|
|
||||||
"junit" % "junit" % "4.12" % "test",
|
|
||||||
"com.mchange" % "c3p0" % "0.9.5.2",
|
|
||||||
"com.typesafe" % "config" % "1.2.1",
|
|
||||||
"com.typesafe.akka" %% "akka-actor" % "2.3.10",
|
|
||||||
"com.enragedginger" %% "akka-quartz-scheduler" % "1.3.0-akka-2.3.x" exclude("c3p0","c3p0")
|
|
||||||
),
|
|
||||||
play.twirl.sbt.Import.TwirlKeys.templateImports += "gitbucket.core._",
|
|
||||||
javacOptions in compile ++= Seq("-target", "7", "-source", "7"),
|
|
||||||
javaOptions in Jetty += "-Dlogback.configurationFile=/logback-dev.xml",
|
|
||||||
testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "junitxml", "console"),
|
|
||||||
javaOptions in Test += "-Dgitbucket.home=target/gitbucket_home_for_test",
|
|
||||||
testOptions in Test += Tests.Setup( () => new java.io.File("target/gitbucket_home_for_test").mkdir() ),
|
|
||||||
fork in Test := true,
|
|
||||||
packageOptions += Package.MainClass("JettyLauncher")
|
|
||||||
).enablePlugins(SbtTwirl, JettyPlugin)
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
||||||
|
|
||||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.4")
|
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.3.0")
|
||||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.12.0")
|
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.3")
|
||||||
addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "2.1.0")
|
addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "2.1.1")
|
||||||
|
addSbtPlugin("fi.gekkio.sbtplugins" % "sbt-jrebel-plugin" % "0.10.0")
|
||||||
|
addSbtPlugin("com.typesafe.sbt" % "sbt-pgp" % "0.8.3")
|
||||||
|
addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-M15")
|
||||||
|
|||||||
1
project/project/plugins.sbt
Normal file
1
project/project/plugins.sbt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-M15")
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<project name="gitbucket" default="all" basedir="..">
|
|
||||||
|
|
||||||
<property environment="env"/>
|
|
||||||
<property name="target.dir" value="target"/>
|
|
||||||
<property name="embed.classes.dir" value="${target.dir}/embed-classes"/>
|
|
||||||
<property name="jetty.dir" value="embed-jetty"/>
|
|
||||||
<property name="scala.version" value="2.11"/>
|
|
||||||
<property name="gitbucket.version" value="${env.GITBUCKET_VERSION}"/>
|
|
||||||
<property name="jetty.version" value="8.1.16.v20140903"/>
|
|
||||||
<property name="servlet.version" value="3.0.0.v201112011016"/>
|
|
||||||
|
|
||||||
<condition property="sbt.exec" value="sbt.bat" else="sbt.sh">
|
|
||||||
<os family="windows" />
|
|
||||||
</condition>
|
|
||||||
|
|
||||||
<target name="clean">
|
|
||||||
<delete dir="${embed.classes.dir}"/>
|
|
||||||
<delete file="${target.dir}/scala-${scala.version}/gitbucket.war"/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="war" depends="clean">
|
|
||||||
<exec executable="${sbt.exec}" resolveexecutable="true" failonerror="true">
|
|
||||||
<arg line="clean compile test package" />
|
|
||||||
</exec>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="embed" depends="war">
|
|
||||||
<mkdir dir="${embed.classes.dir}"/>
|
|
||||||
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/javax.servlet-${servlet.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-continuation-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-http-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-io-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-security-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-server-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-servlet-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-util-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-webapp-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-xml-${jetty.version}.jar" />
|
|
||||||
|
|
||||||
<zip destfile="${target.dir}/scala-${scala.version}/gitbucket_${scala.version}-${gitbucket.version}.war"
|
|
||||||
basedir="${embed.classes.dir}"
|
|
||||||
update = "true"
|
|
||||||
includes="javax/**,org/**"/>
|
|
||||||
|
|
||||||
<zip destfile="${target.dir}/scala-${scala.version}/gitbucket_${scala.version}-${gitbucket.version}.war"
|
|
||||||
basedir="${target.dir}/scala-${scala.version}/classes"
|
|
||||||
update = "true"
|
|
||||||
includes="JettyLauncher.class,HttpsSupportConnector.class"/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="rename" depends="embed">
|
|
||||||
<move file="${target.dir}/scala-${scala.version}/gitbucket_${scala.version}-${gitbucket.version}.war"
|
|
||||||
tofile="${target.dir}/scala-${scala.version}/gitbucket.war"/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="checksum" depends="rename">
|
|
||||||
<checksum file="${target.dir}/scala-${scala.version}/gitbucket.war" algorithm="MD5" format="MD5SUM" forceOverwrite="yes" fileext=".md5"/>
|
|
||||||
<checksum file="${target.dir}/scala-${scala.version}/gitbucket.war" algorithm="SHA" format="MD5SUM" forceOverwrite="yes" fileext=".sha1"/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="all" depends="checksum">
|
|
||||||
</target>
|
|
||||||
|
|
||||||
|
|
||||||
</project>
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
. ./env.sh
|
|
||||||
|
|
||||||
cd ../
|
|
||||||
./sbt.sh clean assembly
|
|
||||||
|
|
||||||
cd release
|
|
||||||
mvn deploy:deploy-file \
|
|
||||||
-DgroupId=gitbucket\
|
|
||||||
-DartifactId=gitbucket-assembly\
|
|
||||||
-Dversion=$GITBUCKET_VERSION\
|
|
||||||
-Dpackaging=jar\
|
|
||||||
-Dfile=../target/scala-2.11/gitbucket-assembly-$GITBUCKET_VERSION.jar\
|
|
||||||
-DrepositoryId=sourceforge.jp\
|
|
||||||
-Durl=scp://shell.sourceforge.jp/home/groups/a/am/amateras/htdocs/mvn/
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
export GITBUCKET_VERSION=`cat ../project/build.scala | grep 'val Version' | cut -d \" -f 2`
|
|
||||||
echo "GITBUCKET_VERSION: $GITBUCKET_VERSION"
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
D="$(dirname "$0")"
|
|
||||||
D="$(cd "${D}"; pwd)"
|
|
||||||
DD="$(dirname "${D}")"
|
|
||||||
(
|
|
||||||
for f in "${D}/env.sh" "${D}/build.xml"; do
|
|
||||||
if [ ! -s "${f}" ]; then
|
|
||||||
echo >&2 "$0: Unable to access file '${f}'"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
. "${D}/env.sh"
|
|
||||||
cd "${DD}"
|
|
||||||
ant -f "${D}/build.xml" all
|
|
||||||
)
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<?xml version="1.0"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<groupId>jp.sf.amateras</groupId>
|
|
||||||
<artifactId>gitbucket-assembly</artifactId>
|
|
||||||
<version>0.0.1</version>
|
|
||||||
<build>
|
|
||||||
<extensions>
|
|
||||||
<extension>
|
|
||||||
<groupId>org.apache.maven.wagon</groupId>
|
|
||||||
<artifactId>wagon-ssh</artifactId>
|
|
||||||
<version>1.0-beta-6</version>
|
|
||||||
</extension>
|
|
||||||
</extensions>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
||||||
Binary file not shown.
2
sbt.bat
2
sbt.bat
@@ -1,2 +0,0 @@
|
|||||||
set SCRIPT_DIR=%~dp0
|
|
||||||
java %JAVA_OPTS% -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar "%SCRIPT_DIR%\sbt-launch-0.13.8.jar" %*
|
|
||||||
2
sbt.sh
2
sbt.sh
@@ -1,2 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
java $JAVA_OPTS -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar `dirname $0`/sbt-launch-0.13.8.jar "$@"
|
|
||||||
@@ -1,55 +1,104 @@
|
|||||||
|
import org.eclipse.jetty.server.ConnectionFactory;
|
||||||
|
import org.eclipse.jetty.server.Connector;
|
||||||
|
import org.eclipse.jetty.server.Handler;
|
||||||
|
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.nio.SelectChannelConnector;
|
import org.eclipse.jetty.server.handler.StatisticsHandler;
|
||||||
import org.eclipse.jetty.webapp.WebAppContext;
|
import org.eclipse.jetty.webapp.WebAppContext;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
import java.security.ProtectionDomain;
|
import java.security.ProtectionDomain;
|
||||||
|
|
||||||
public class JettyLauncher {
|
public class JettyLauncher {
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
|
System.setProperty("java.awt.headless", "true");
|
||||||
|
|
||||||
String host = null;
|
String host = null;
|
||||||
int port = 8080;
|
int port = 8080;
|
||||||
|
InetSocketAddress address = null;
|
||||||
String contextPath = "/";
|
String contextPath = "/";
|
||||||
|
String tmpDirPath="";
|
||||||
boolean forceHttps = false;
|
boolean forceHttps = false;
|
||||||
|
|
||||||
for(String arg: args) {
|
for(String arg: args) {
|
||||||
if(arg.startsWith("--") && arg.contains("=")) {
|
if(arg.startsWith("--") && arg.contains("=")) {
|
||||||
String[] dim = arg.split("=");
|
String[] dim = arg.split("=");
|
||||||
if(dim.length >= 2) {
|
if(dim.length >= 2) {
|
||||||
if(dim[0].equals("--host")) {
|
switch (dim[0]) {
|
||||||
|
case "--host":
|
||||||
host = dim[1];
|
host = dim[1];
|
||||||
} else if(dim[0].equals("--port")) {
|
break;
|
||||||
|
case "--port":
|
||||||
port = Integer.parseInt(dim[1]);
|
port = Integer.parseInt(dim[1]);
|
||||||
} else if(dim[0].equals("--prefix")) {
|
break;
|
||||||
|
case "--prefix":
|
||||||
contextPath = dim[1];
|
contextPath = dim[1];
|
||||||
} else if(dim[0].equals("--gitbucket.home")){
|
if (!contextPath.startsWith("/")) {
|
||||||
|
contextPath = "/" + contextPath;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "--gitbucket.home":
|
||||||
System.setProperty("gitbucket.home", dim[1]);
|
System.setProperty("gitbucket.home", dim[1]);
|
||||||
|
break;
|
||||||
|
case "--temp_dir":
|
||||||
|
tmpDirPath = dim[1];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Server server = new Server();
|
|
||||||
|
|
||||||
SelectChannelConnector connector = new SelectChannelConnector();
|
|
||||||
if(host != null) {
|
if(host != null) {
|
||||||
connector.setHost(host);
|
address = new InetSocketAddress(host, port);
|
||||||
|
} else {
|
||||||
|
address = new InetSocketAddress(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
Server server = new Server(address);
|
||||||
|
|
||||||
|
// SelectChannelConnector connector = new SelectChannelConnector();
|
||||||
|
// if(host != null) {
|
||||||
|
// connector.setHost(host);
|
||||||
|
// }
|
||||||
|
// connector.setMaxIdleTime(1000 * 60 * 60);
|
||||||
|
// connector.setSoLingerTime(-1);
|
||||||
|
// connector.setPort(port);
|
||||||
|
// server.addConnector(connector);
|
||||||
|
|
||||||
|
// Disabling Server header
|
||||||
|
for (Connector connector : server.getConnectors()) {
|
||||||
|
for (ConnectionFactory factory : connector.getConnectionFactories()) {
|
||||||
|
if (factory instanceof HttpConnectionFactory) {
|
||||||
|
((HttpConnectionFactory) factory).getHttpConfiguration().setSendServerVersion(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
connector.setMaxIdleTime(1000 * 60 * 60);
|
|
||||||
connector.setSoLingerTime(-1);
|
|
||||||
connector.setPort(port);
|
|
||||||
server.addConnector(connector);
|
|
||||||
|
|
||||||
WebAppContext context = new WebAppContext();
|
WebAppContext context = new WebAppContext();
|
||||||
|
|
||||||
File tmpDir = new File(getGitBucketHome(), "tmp");
|
File tmpDir;
|
||||||
if(tmpDir.exists()){
|
if(tmpDirPath.equals("")){
|
||||||
deleteDirectory(tmpDir);
|
tmpDir = new File(getGitBucketHome(), "tmp");
|
||||||
}
|
if(!tmpDir.exists()){
|
||||||
tmpDir.mkdirs();
|
tmpDir.mkdirs();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tmpDir = new File(tmpDirPath);
|
||||||
|
if(!tmpDir.exists()){
|
||||||
|
throw new java.io.FileNotFoundException(
|
||||||
|
String.format("temp_dir \"%s\" not found", tmpDirPath));
|
||||||
|
} else if(!tmpDir.isDirectory()) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("temp_dir \"%s\" is not a directory", tmpDirPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
context.setTempDirectory(tmpDir);
|
context.setTempDirectory(tmpDir);
|
||||||
|
|
||||||
|
// Disabling the directory listing feature.
|
||||||
|
context.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "false");
|
||||||
|
|
||||||
ProtectionDomain domain = JettyLauncher.class.getProtectionDomain();
|
ProtectionDomain domain = JettyLauncher.class.getProtectionDomain();
|
||||||
URL location = domain.getCodeSource().getLocation();
|
URL location = domain.getCodeSource().getLocation();
|
||||||
|
|
||||||
@@ -61,7 +110,11 @@ public class JettyLauncher {
|
|||||||
context.setInitParameter("org.scalatra.ForceHttps", "true");
|
context.setInitParameter("org.scalatra.ForceHttps", "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
server.setHandler(context);
|
Handler handler = addStatisticsHandler(context);
|
||||||
|
|
||||||
|
server.setHandler(handler);
|
||||||
|
server.setStopAtShutdown(true);
|
||||||
|
server.setStopTimeout(7_000);
|
||||||
server.start();
|
server.start();
|
||||||
server.join();
|
server.join();
|
||||||
}
|
}
|
||||||
@@ -88,4 +141,12 @@ public class JettyLauncher {
|
|||||||
}
|
}
|
||||||
dir.delete();
|
dir.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Handler addStatisticsHandler(Handler handler) {
|
||||||
|
// The graceful shutdown is implemented via the statistics handler.
|
||||||
|
// See the following: https://bugs.eclipse.org/bugs/show_bug.cgi?id=420142
|
||||||
|
final StatisticsHandler statisticsHandler = new StatisticsHandler();
|
||||||
|
statisticsHandler.setHandler(handler);
|
||||||
|
return statisticsHandler;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
52
src/main/java/org/postgresql/Driver2.java
Normal file
52
src/main/java/org/postgresql/Driver2.java
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package org.postgresql;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationHandler;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps the PostgreSQL JDBC driver to convert the returning column names to lower case.
|
||||||
|
*/
|
||||||
|
public class Driver2 extends Driver {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public java.sql.Connection connect(String url, Properties info) throws SQLException {
|
||||||
|
Connection conn = super.connect(url, info);
|
||||||
|
|
||||||
|
Object proxy = Proxy.newProxyInstance(
|
||||||
|
conn.getClass().getClassLoader(),
|
||||||
|
new Class[]{ Connection.class },
|
||||||
|
new ConnectionProxyHandler(conn)
|
||||||
|
);
|
||||||
|
|
||||||
|
return Connection.class.cast(proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static class ConnectionProxyHandler implements InvocationHandler {
|
||||||
|
|
||||||
|
private Connection conn;
|
||||||
|
|
||||||
|
public ConnectionProxyHandler(Connection conn){
|
||||||
|
this.conn = conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||||
|
if(method.getName().equals("prepareStatement")){
|
||||||
|
if(args != null && args.length == 2 && args[1].getClass().isArray()){
|
||||||
|
String[] keys = (String[]) args[1];
|
||||||
|
for(int i = 0; i < keys.length; i++){
|
||||||
|
keys[i] = keys[i].toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return method.invoke(conn, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
db {
|
|
||||||
driver = "org.h2.Driver"
|
|
||||||
url = "jdbc:h2:${DatabaseHome};MVCC=true"
|
|
||||||
user = "sa"
|
|
||||||
password = "sa"
|
|
||||||
}
|
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<!--
|
<!--
|
||||||
<logger name="service.WebHookService" level="DEBUG" />
|
<logger name="service.WebHookService" level="DEBUG" />
|
||||||
<logger name="servlet" level="DEBUG" />
|
<logger name="servlet" level="DEBUG" />
|
||||||
-->
|
|
||||||
<logger name="scala.slick.jdbc.JdbcBackend.statement" level="DEBUG" />
|
<logger name="scala.slick.jdbc.JdbcBackend.statement" level="DEBUG" />
|
||||||
|
-->
|
||||||
|
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -6,12 +6,14 @@
|
|||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
|
<!--
|
||||||
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
||||||
<file>gitbucket.log</file>
|
<file>gitbucket.log</file>
|
||||||
<encoder>
|
<encoder>
|
||||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
-->
|
||||||
|
|
||||||
<root level="INFO">
|
<root level="INFO">
|
||||||
<appender-ref ref="STDOUT" />
|
<appender-ref ref="STDOUT" />
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
c3p0 {
|
|
||||||
privilegeSpawnedThreads=true
|
|
||||||
contextClassLoaderSource=library
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
CREATE TABLE ACCOUNT(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
MAIL_ADDRESS VARCHAR(100) NOT NULL,
|
|
||||||
PASSWORD VARCHAR(40) NOT NULL,
|
|
||||||
ADMINISTRATOR BOOLEAN NOT NULL,
|
|
||||||
URL VARCHAR(200),
|
|
||||||
REGISTERED_DATE TIMESTAMP NOT NULL,
|
|
||||||
UPDATED_DATE TIMESTAMP NOT NULL,
|
|
||||||
LAST_LOGIN_DATE TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE REPOSITORY(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
PRIVATE BOOLEAN NOT NULL,
|
|
||||||
DESCRIPTION TEXT,
|
|
||||||
DEFAULT_BRANCH VARCHAR(100),
|
|
||||||
REGISTERED_DATE TIMESTAMP NOT NULL,
|
|
||||||
UPDATED_DATE TIMESTAMP NOT NULL,
|
|
||||||
LAST_ACTIVITY_DATE TIMESTAMP NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE COLLABORATOR(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
COLLABORATOR_NAME VARCHAR(100) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE ISSUE(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
ISSUE_ID INT NOT NULL,
|
|
||||||
OPENED_USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
MILESTONE_ID INT,
|
|
||||||
ASSIGNED_USER_NAME VARCHAR(100),
|
|
||||||
TITLE TEXT NOT NULL,
|
|
||||||
CONTENT TEXT,
|
|
||||||
CLOSED BOOLEAN NOT NULL,
|
|
||||||
REGISTERED_DATE TIMESTAMP NOT NULL,
|
|
||||||
UPDATED_DATE TIMESTAMP NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE ISSUE_ID(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
ISSUE_ID INT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE ISSUE_COMMENT(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
ISSUE_ID INT NOT NULL,
|
|
||||||
COMMENT_ID INT AUTO_INCREMENT,
|
|
||||||
ACTION VARCHAR(10),
|
|
||||||
COMMENTED_USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
CONTENT TEXT NOT NULL,
|
|
||||||
REGISTERED_DATE TIMESTAMP NOT NULL,
|
|
||||||
UPDATED_DATE TIMESTAMP NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE LABEL(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
LABEL_ID INT AUTO_INCREMENT,
|
|
||||||
LABEL_NAME VARCHAR(100) NOT NULL,
|
|
||||||
COLOR CHAR(6) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE ISSUE_LABEL(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
ISSUE_ID INT NOT NULL,
|
|
||||||
LABEL_ID INT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE MILESTONE(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
MILESTONE_ID INT AUTO_INCREMENT,
|
|
||||||
TITLE VARCHAR(100) NOT NULL,
|
|
||||||
DESCRIPTION TEXT,
|
|
||||||
DUE_DATE TIMESTAMP,
|
|
||||||
CLOSED_DATE TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE ACCOUNT ADD CONSTRAINT IDX_ACCOUNT_PK PRIMARY KEY (USER_NAME);
|
|
||||||
ALTER TABLE ACCOUNT ADD CONSTRAINT IDX_ACCOUNT_1 UNIQUE (MAIL_ADDRESS);
|
|
||||||
|
|
||||||
ALTER TABLE REPOSITORY ADD CONSTRAINT IDX_REPOSITORY_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE REPOSITORY ADD CONSTRAINT IDX_REPOSITORY_FK0 FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME);
|
|
||||||
|
|
||||||
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_FK1 FOREIGN KEY (COLLABORATOR_NAME) REFERENCES ACCOUNT (USER_NAME);
|
|
||||||
|
|
||||||
ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_PK PRIMARY KEY (ISSUE_ID, USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK1 FOREIGN KEY (OPENED_USER_NAME) REFERENCES ACCOUNT (USER_NAME);
|
|
||||||
ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK2 FOREIGN KEY (MILESTONE_ID) REFERENCES MILESTONE (MILESTONE_ID);
|
|
||||||
|
|
||||||
ALTER TABLE ISSUE_ID ADD CONSTRAINT IDX_ISSUE_ID_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE ISSUE_ID ADD CONSTRAINT IDX_ISSUE_ID_FK1 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
|
|
||||||
ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_PK PRIMARY KEY (COMMENT_ID);
|
|
||||||
ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_1 UNIQUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID, COMMENT_ID);
|
|
||||||
ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID) REFERENCES ISSUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID);
|
|
||||||
|
|
||||||
ALTER TABLE LABEL ADD CONSTRAINT IDX_LABEL_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, LABEL_ID);
|
|
||||||
ALTER TABLE LABEL ADD CONSTRAINT IDX_LABEL_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
|
|
||||||
ALTER TABLE ISSUE_LABEL ADD CONSTRAINT IDX_ISSUE_LABEL_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID, LABEL_ID);
|
|
||||||
ALTER TABLE ISSUE_LABEL ADD CONSTRAINT IDX_ISSUE_LABEL_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID) REFERENCES ISSUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID);
|
|
||||||
|
|
||||||
ALTER TABLE MILESTONE ADD CONSTRAINT IDX_MILESTONE_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, MILESTONE_ID);
|
|
||||||
ALTER TABLE MILESTONE ADD CONSTRAINT IDX_MILESTONE_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
|
|
||||||
INSERT INTO ACCOUNT (
|
|
||||||
USER_NAME,
|
|
||||||
MAIL_ADDRESS,
|
|
||||||
PASSWORD,
|
|
||||||
ADMINISTRATOR,
|
|
||||||
URL,
|
|
||||||
REGISTERED_DATE,
|
|
||||||
UPDATED_DATE,
|
|
||||||
LAST_LOGIN_DATE
|
|
||||||
) VALUES (
|
|
||||||
'root',
|
|
||||||
'root@localhost',
|
|
||||||
'dc76e9f0c0006e8f919e0c515c66dbba3982f785',
|
|
||||||
true,
|
|
||||||
'https://github.com/gitbucket/gitbucket',
|
|
||||||
SYSDATE,
|
|
||||||
SYSDATE,
|
|
||||||
NULL
|
|
||||||
);
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
-- Fix COLLABORATOR constraints
|
|
||||||
ALTER TABLE COLLABORATOR DROP CONSTRAINT IDX_COLLABORATOR_FK1 IF EXISTS;
|
|
||||||
ALTER TABLE COLLABORATOR DROP CONSTRAINT IDX_COLLABORATOR_FK0 IF EXISTS;
|
|
||||||
ALTER TABLE COLLABORATOR DROP CONSTRAINT IDX_COLLABORATOR_PK IF EXISTS;
|
|
||||||
|
|
||||||
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, COLLABORATOR_NAME);
|
|
||||||
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_FK1 FOREIGN KEY (COLLABORATOR_NAME) REFERENCES ACCOUNT (USER_NAME);
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
ALTER TABLE GROUP_MEMBER ADD COLUMN MANAGER BOOLEAN DEFAULT FALSE;
|
|
||||||
|
|
||||||
CREATE TABLE SSH_KEY (
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
SSH_KEY_ID INT AUTO_INCREMENT,
|
|
||||||
TITLE VARCHAR(100) NOT NULL,
|
|
||||||
PUBLIC_KEY TEXT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE SSH_KEY ADD CONSTRAINT IDX_SSH_KEY_PK PRIMARY KEY (USER_NAME, SSH_KEY_ID);
|
|
||||||
ALTER TABLE SSH_KEY ADD CONSTRAINT IDX_SSH_KEY_FK0 FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME);
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
DROP TABLE COMMIT_LOG;
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
CREATE TABLE ACTIVITY(
|
|
||||||
ACTIVITY_ID INT AUTO_INCREMENT,
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
ACTIVITY_USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
ACTIVITY_TYPE VARCHAR(100) NOT NULL,
|
|
||||||
MESSAGE TEXT NOT NULL,
|
|
||||||
ADDITIONAL_INFO TEXT,
|
|
||||||
ACTIVITY_DATE TIMESTAMP NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE COMMIT_LOG (
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
COMMIT_ID VARCHAR(40) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE ACTIVITY ADD CONSTRAINT IDX_ACTIVITY_PK PRIMARY KEY (ACTIVITY_ID);
|
|
||||||
ALTER TABLE ACTIVITY ADD CONSTRAINT IDX_ACTIVITY_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE ACTIVITY ADD CONSTRAINT IDX_ACTIVITY_FK1 FOREIGN KEY (ACTIVITY_USER_NAME) REFERENCES ACCOUNT (USER_NAME);
|
|
||||||
|
|
||||||
ALTER TABLE COMMIT_LOG ADD CONSTRAINT IDX_COMMIT_LOG_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, COMMIT_ID);
|
|
||||||
ALTER TABLE COMMIT_LOG ADD CONSTRAINT IDX_COMMIT_LOG_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
ALTER TABLE ACCOUNT ADD COLUMN IMAGE VARCHAR(100);
|
|
||||||
|
|
||||||
UPDATE ISSUE_COMMENT SET ACTION = 'comment' WHERE ACTION IS NULL;
|
|
||||||
|
|
||||||
ALTER TABLE ISSUE_COMMENT ALTER COLUMN ACTION VARCHAR(20) NOT NULL;
|
|
||||||
|
|
||||||
UPDATE ISSUE_COMMENT SET ACTION = 'close_comment' WHERE ACTION = 'close';
|
|
||||||
UPDATE ISSUE_COMMENT SET ACTION = 'reopen_comment' WHERE ACTION = 'reopen';
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
CREATE TABLE GROUP_MEMBER(
|
|
||||||
GROUP_NAME VARCHAR(100) NOT NULL,
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE GROUP_MEMBER ADD CONSTRAINT IDX_GROUP_MEMBER_PK PRIMARY KEY (GROUP_NAME, USER_NAME);
|
|
||||||
ALTER TABLE GROUP_MEMBER ADD CONSTRAINT IDX_GROUP_MEMBER_FK0 FOREIGN KEY (GROUP_NAME) REFERENCES ACCOUNT (USER_NAME);
|
|
||||||
ALTER TABLE GROUP_MEMBER ADD CONSTRAINT IDX_GROUP_MEMBER_FK1 FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME);
|
|
||||||
|
|
||||||
ALTER TABLE ACCOUNT ADD COLUMN GROUP_ACCOUNT BOOLEAN NOT NULL DEFAULT FALSE;
|
|
||||||
|
|
||||||
CREATE OR REPLACE VIEW ISSUE_OUTLINE_VIEW AS
|
|
||||||
SELECT
|
|
||||||
A.USER_NAME,
|
|
||||||
A.REPOSITORY_NAME,
|
|
||||||
A.ISSUE_ID,
|
|
||||||
NVL(B.COMMENT_COUNT, 0) AS COMMENT_COUNT
|
|
||||||
FROM ISSUE A
|
|
||||||
LEFT OUTER JOIN (
|
|
||||||
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM ISSUE_COMMENT
|
|
||||||
WHERE ACTION IN ('comment', 'close_comment', 'reopen_comment')
|
|
||||||
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
|
|
||||||
) B
|
|
||||||
ON (A.USER_NAME = B.USER_NAME AND A.REPOSITORY_NAME = B.REPOSITORY_NAME AND A.ISSUE_ID = B.ISSUE_ID);
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
ALTER TABLE REPOSITORY ADD COLUMN ORIGIN_USER_NAME VARCHAR(100);
|
|
||||||
ALTER TABLE REPOSITORY ADD COLUMN ORIGIN_REPOSITORY_NAME VARCHAR(100);
|
|
||||||
ALTER TABLE REPOSITORY ADD COLUMN PARENT_USER_NAME VARCHAR(100);
|
|
||||||
ALTER TABLE REPOSITORY ADD COLUMN PARENT_REPOSITORY_NAME VARCHAR(100);
|
|
||||||
|
|
||||||
CREATE TABLE PULL_REQUEST(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
ISSUE_ID INT NOT NULL,
|
|
||||||
BRANCH VARCHAR(100) NOT NULL,
|
|
||||||
REQUEST_USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REQUEST_REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REQUEST_BRANCH VARCHAR(100) NOT NULL,
|
|
||||||
COMMIT_ID_FROM VARCHAR(40) NOT NULL,
|
|
||||||
COMMIT_ID_TO VARCHAR(40) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE PULL_REQUEST ADD CONSTRAINT IDX_PULL_REQUEST_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID);
|
|
||||||
ALTER TABLE PULL_REQUEST ADD CONSTRAINT IDX_PULL_REQUEST_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID) REFERENCES ISSUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID);
|
|
||||||
|
|
||||||
ALTER TABLE ISSUE ADD COLUMN PULL_REQUEST BOOLEAN NOT NULL DEFAULT FALSE;
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
CREATE TABLE WEB_HOOK (
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
URL VARCHAR(200) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE WEB_HOOK ADD CONSTRAINT IDX_WEB_HOOK_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, URL);
|
|
||||||
ALTER TABLE WEB_HOOK ADD CONSTRAINT IDX_WEB_HOOK_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
ALTER TABLE ACCOUNT ADD COLUMN FULL_NAME VARCHAR(100);
|
|
||||||
|
|
||||||
UPDATE ACCOUNT SET FULL_NAME = USER_NAME WHERE FULL_NAME IS NULL;
|
|
||||||
|
|
||||||
ALTER TABLE ACCOUNT ALTER COLUMN FULL_NAME SET NOT NULL;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE ACCOUNT ADD COLUMN REMOVED BOOLEAN DEFAULT FALSE;
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
CREATE TABLE PLUGIN (
|
|
||||||
PLUGIN_ID VARCHAR(100) NOT NULL,
|
|
||||||
VERSION VARCHAR(100) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE PLUGIN ADD CONSTRAINT IDX_PLUGIN_PK PRIMARY KEY (PLUGIN_ID);
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
CREATE TABLE COMMIT_COMMENT (
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
COMMIT_ID VARCHAR(100) NOT NULL,
|
|
||||||
COMMENT_ID INT AUTO_INCREMENT,
|
|
||||||
COMMENTED_USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
CONTENT TEXT NOT NULL,
|
|
||||||
FILE_NAME NVARCHAR(100),
|
|
||||||
OLD_LINE_NUMBER INT,
|
|
||||||
NEW_LINE_NUMBER INT,
|
|
||||||
REGISTERED_DATE TIMESTAMP NOT NULL,
|
|
||||||
UPDATED_DATE TIMESTAMP NOT NULL,
|
|
||||||
PULL_REQUEST BOOLEAN NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE COMMIT_COMMENT ADD CONSTRAINT IDX_COMMIT_COMMENT_PK PRIMARY KEY (COMMENT_ID);
|
|
||||||
ALTER TABLE COMMIT_COMMENT ADD CONSTRAINT IDX_COMMIT_COMMENT_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE COMMIT_COMMENT ADD CONSTRAINT IDX_COMMIT_COMMENT_1 UNIQUE (USER_NAME, REPOSITORY_NAME, COMMIT_ID, COMMENT_ID);
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE COMMIT_COMMENT ALTER COLUMN FILE_NAME NVARCHAR(260);
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
DROP TABLE IF EXISTS ACCESS_TOKEN;
|
|
||||||
|
|
||||||
CREATE TABLE ACCESS_TOKEN (
|
|
||||||
ACCESS_TOKEN_ID INT NOT NULL AUTO_INCREMENT,
|
|
||||||
TOKEN_HASH VARCHAR(40) NOT NULL,
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
NOTE TEXT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE ACCESS_TOKEN ADD CONSTRAINT IDX_ACCESS_TOKEN_PK PRIMARY KEY (ACCESS_TOKEN_ID);
|
|
||||||
ALTER TABLE ACCESS_TOKEN ADD CONSTRAINT IDX_ACCESS_TOKEN_FK0 FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME)
|
|
||||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
ALTER TABLE ACCESS_TOKEN ADD CONSTRAINT IDX_ACCESS_TOKEN_TOKEN_HASH UNIQUE(TOKEN_HASH);
|
|
||||||
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS COMMIT_STATUS;
|
|
||||||
CREATE TABLE COMMIT_STATUS(
|
|
||||||
COMMIT_STATUS_ID INT AUTO_INCREMENT,
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
COMMIT_ID VARCHAR(40) NOT NULL,
|
|
||||||
CONTEXT VARCHAR(255) NOT NULL, -- context is too long (maximum is 255 characters)
|
|
||||||
STATE VARCHAR(10) NOT NULL, -- pending, success, error, or failure
|
|
||||||
TARGET_URL VARCHAR(200),
|
|
||||||
DESCRIPTION TEXT,
|
|
||||||
CREATOR VARCHAR(100) NOT NULL,
|
|
||||||
REGISTERED_DATE TIMESTAMP NOT NULL, -- CREATED_AT
|
|
||||||
UPDATED_DATE TIMESTAMP NOT NULL -- UPDATED_AT
|
|
||||||
);
|
|
||||||
ALTER TABLE COMMIT_STATUS ADD CONSTRAINT IDX_COMMIT_STATUS_PK PRIMARY KEY (COMMIT_STATUS_ID);
|
|
||||||
ALTER TABLE COMMIT_STATUS ADD CONSTRAINT IDX_COMMIT_STATUS_1
|
|
||||||
UNIQUE (USER_NAME, REPOSITORY_NAME, COMMIT_ID, CONTEXT);
|
|
||||||
ALTER TABLE COMMIT_STATUS ADD CONSTRAINT IDX_COMMIT_STATUS_FK1
|
|
||||||
FOREIGN KEY (USER_NAME, REPOSITORY_NAME)
|
|
||||||
REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME)
|
|
||||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
ALTER TABLE COMMIT_STATUS ADD CONSTRAINT IDX_COMMIT_STATUS_FK2
|
|
||||||
FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME)
|
|
||||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
ALTER TABLE COMMIT_STATUS ADD CONSTRAINT IDX_COMMIT_STATUS_FK3
|
|
||||||
FOREIGN KEY (CREATOR) REFERENCES ACCOUNT (USER_NAME)
|
|
||||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
DROP TABLE IF EXISTS WEB_HOOK_EVENT;
|
|
||||||
|
|
||||||
CREATE TABLE WEB_HOOK_EVENT(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
URL VARCHAR(200) NOT NULL,
|
|
||||||
EVENT VARCHAR(30) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE WEB_HOOK_EVENT ADD CONSTRAINT IDX_WEB_HOOK_EVENT_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, URL, EVENT);
|
|
||||||
ALTER TABLE WEB_HOOK_EVENT ADD CONSTRAINT IDX_WEB_HOOK_EVENT_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, URL) REFERENCES WEB_HOOK (USER_NAME, REPOSITORY_NAME, URL)
|
|
||||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
CREATE TEMPORARY TABLE TMP_EVENTS (EVENT VARCHAR(30));
|
|
||||||
|
|
||||||
INSERT INTO TMP_EVENTS VALUES ('push'),('issue_comment'),('issues'),('pull_request');
|
|
||||||
|
|
||||||
INSERT INTO WEB_HOOK_EVENT (USER_NAME, REPOSITORY_NAME, URL, EVENT)
|
|
||||||
SELECT USER_NAME, REPOSITORY_NAME, URL, EVENT
|
|
||||||
FROM WEB_HOOK, TMP_EVENTS;
|
|
||||||
|
|
||||||
DROP TABLE TMP_EVENTS;
|
|
||||||
|
|
||||||
ALTER TABLE COMMIT_COMMENT ADD COLUMN ISSUE_ID INT;
|
|
||||||
|
|
||||||
CREATE OR REPLACE VIEW ISSUE_OUTLINE_VIEW AS
|
|
||||||
SELECT
|
|
||||||
A.USER_NAME,
|
|
||||||
A.REPOSITORY_NAME,
|
|
||||||
A.ISSUE_ID,
|
|
||||||
NVL(B.COMMENT_COUNT, 0) + NVL(C.COMMENT_COUNT, 0) AS COMMENT_COUNT
|
|
||||||
FROM ISSUE A
|
|
||||||
LEFT OUTER JOIN (
|
|
||||||
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM ISSUE_COMMENT
|
|
||||||
WHERE ACTION IN ('comment', 'close_comment', 'reopen_comment')
|
|
||||||
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
|
|
||||||
) B
|
|
||||||
ON (A.USER_NAME = B.USER_NAME AND A.REPOSITORY_NAME = B.REPOSITORY_NAME AND A.ISSUE_ID = B.ISSUE_ID)
|
|
||||||
LEFT OUTER JOIN (
|
|
||||||
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM COMMIT_COMMENT
|
|
||||||
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
|
|
||||||
) C
|
|
||||||
ON (A.USER_NAME = C.USER_NAME AND A.REPOSITORY_NAME = C.REPOSITORY_NAME AND A.ISSUE_ID = C.ISSUE_ID);
|
|
||||||
|
|
||||||
|
|
||||||
UPDATE COMMIT_COMMENT C SET (ISSUE_ID) = (
|
|
||||||
SELECT MAX(P.ISSUE_ID)
|
|
||||||
FROM PULL_REQUEST P
|
|
||||||
WHERE
|
|
||||||
C.USER_NAME = P.USER_NAME AND
|
|
||||||
C.REPOSITORY_NAME = P.REPOSITORY_NAME AND
|
|
||||||
C.COMMIT_ID = P.COMMIT_ID_TO
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE COMMIT_COMMENT DROP COLUMN PULL_REQUEST;
|
|
||||||
18
src/main/resources/update/gitbucket-core_4.0.sql
Normal file
18
src/main/resources/update/gitbucket-core_4.0.sql
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
CREATE OR REPLACE VIEW ISSUE_OUTLINE_VIEW AS
|
||||||
|
SELECT
|
||||||
|
A.USER_NAME,
|
||||||
|
A.REPOSITORY_NAME,
|
||||||
|
A.ISSUE_ID,
|
||||||
|
COALESCE(B.COMMENT_COUNT, 0) + COALESCE(C.COMMENT_COUNT, 0) AS COMMENT_COUNT
|
||||||
|
FROM ISSUE A
|
||||||
|
LEFT OUTER JOIN (
|
||||||
|
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM ISSUE_COMMENT
|
||||||
|
WHERE ACTION IN ('comment', 'close_comment', 'reopen_comment')
|
||||||
|
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
|
||||||
|
) B
|
||||||
|
ON (A.USER_NAME = B.USER_NAME AND A.REPOSITORY_NAME = B.REPOSITORY_NAME AND A.ISSUE_ID = B.ISSUE_ID)
|
||||||
|
LEFT OUTER JOIN (
|
||||||
|
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM COMMIT_COMMENT
|
||||||
|
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
|
||||||
|
) C
|
||||||
|
ON (A.USER_NAME = C.USER_NAME AND A.REPOSITORY_NAME = C.REPOSITORY_NAME AND A.ISSUE_ID = C.ISSUE_ID);
|
||||||
351
src/main/resources/update/gitbucket-core_4.0.xml
Normal file
351
src/main/resources/update/gitbucket-core_4.0.xml
Normal file
@@ -0,0 +1,351 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<changeSet>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- ACCOUNT -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="ACCOUNT">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="MAIL_ADDRESS" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="PASSWORD" type="varchar(40)" nullable="false"/>
|
||||||
|
<column name="ADMINISTRATOR" type="boolean" nullable="false"/>
|
||||||
|
<column name="URL" type="varchar(200)" nullable="true"/>
|
||||||
|
<column name="REGISTERED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="LAST_LOGIN_DATE" type="datetime" nullable="true"/>
|
||||||
|
<column name="IMAGE" type="varchar(100)" nullable="true"/>
|
||||||
|
<column name="GROUP_ACCOUNT" type="boolean" nullable="false"/>
|
||||||
|
<column name="FULL_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REMOVED" type="boolean" nullable="true"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_ACCOUNT_PK" tableName="ACCOUNT" columnNames="USER_NAME"/>
|
||||||
|
<addUniqueConstraint constraintName="IDX_ACCOUNT_1" tableName="ACCOUNT" columnNames="MAIL_ADDRESS"/>
|
||||||
|
|
||||||
|
<insert tableName="ACCOUNT">
|
||||||
|
<column name="USER_NAME" value="root"/>
|
||||||
|
<column name="FULL_NAME" value="root"/>
|
||||||
|
<column name="MAIL_ADDRESS" value="root@localhost"/>
|
||||||
|
<column name="PASSWORD" value="dc76e9f0c0006e8f919e0c515c66dbba3982f785"/>
|
||||||
|
<column name="ADMINISTRATOR" valueBoolean="true"/>
|
||||||
|
<column name="URL" value="https://github.com/gitbucket/gitbucket"/>
|
||||||
|
<column name="GROUP_ACCOUNT" valueBoolean="false"/>
|
||||||
|
<column name="REMOVED" valueBoolean="false"/>
|
||||||
|
<column name="REGISTERED_DATE" valueDate="${currentDateTime}"/>
|
||||||
|
<column name="UPDATED_DATE" valueDate="${currentDateTime}"/>
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- REPOSITORY -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="REPOSITORY">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="PRIVATE" type="boolean" nullable="false"/>
|
||||||
|
<column name="DESCRIPTION" type="text" nullable="true"/>
|
||||||
|
<column name="DEFAULT_BRANCH" type="varchar(100)" nullable="true"/>
|
||||||
|
<column name="REGISTERED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="LAST_ACTIVITY_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="ORIGIN_USER_NAME" type="varchar(100)" nullable="true"/>
|
||||||
|
<column name="ORIGIN_REPOSITORY_NAME" type="varchar(100)" nullable="true"/>
|
||||||
|
<column name="PARENT_USER_NAME" type="varchar(100)" nullable="true"/>
|
||||||
|
<column name="PARENT_REPOSITORY_NAME" type="varchar(100)" nullable="true"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_REPOSITORY_PK" tableName="REPOSITORY" columnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_REPOSITORY_FK0" baseTableName="REPOSITORY" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- ACCESS_TOKEN -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="ACCESS_TOKEN">
|
||||||
|
<column name="ACCESS_TOKEN_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="TOKEN_HASH" type="varchar(40)" nullable="false"/>
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="NOTE" type="text" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_ACCESS_TOKEN_PK" tableName="ACCESS_TOKEN" columnNames="ACCESS_TOKEN_ID"/>
|
||||||
|
<addUniqueConstraint constraintName="IDX_ACCESS_TOKEN_TOKEN_HASH" tableName="ACCESS_TOKEN" columnNames="TOKEN_HASH"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ACCESS_TOKEN_FK0" baseTableName="ACCESS_TOKEN" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- ACTIVITY -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="ACTIVITY">
|
||||||
|
<column name="ACTIVITY_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="ACTIVITY_USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="ACTIVITY_TYPE" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="MESSAGE" type="text" nullable="false"/>
|
||||||
|
<column name="ADDITIONAL_INFO" type="text" nullable="true"/>
|
||||||
|
<column name="ACTIVITY_DATE" type="datetime" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_ACTIVITY_PK" tableName="ACTIVITY" columnNames="ACTIVITY_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ACTIVITY_FK1" baseTableName="ACTIVITY" baseColumnNames="ACTIVITY_USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ACTIVITY_FK0" baseTableName="ACTIVITY" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- COLLABORATOR -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="COLLABORATOR">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="COLLABORATOR_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_COLLABORATOR_PK" tableName="COLLABORATOR" columnNames="USER_NAME, REPOSITORY_NAME, COLLABORATOR_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_COLLABORATOR_FK1" baseTableName="COLLABORATOR" baseColumnNames="COLLABORATOR_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_COLLABORATOR_FK0" baseTableName="COLLABORATOR" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- COMMIT_COMMENT -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="COMMIT_COMMENT">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="COMMIT_ID" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="COMMENT_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="COMMENTED_USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="CONTENT" type="text" nullable="false"/>
|
||||||
|
<column name="FILE_NAME" type="varchar(260)" nullable="true"/>
|
||||||
|
<column name="OLD_LINE_NUMBER" type="int" nullable="true"/>
|
||||||
|
<column name="NEW_LINE_NUMBER" type="int" nullable="true"/>
|
||||||
|
<column name="REGISTERED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="ISSUE_ID" type="int" nullable="true"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_COMMIT_COMMENT_PK" tableName="COMMIT_COMMENT" columnNames="COMMENT_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_COMMIT_COMMENT_FK0" baseTableName="COMMIT_COMMENT" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- COMMIT_STATUS -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="COMMIT_STATUS">
|
||||||
|
<column name="COMMIT_STATUS_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="COMMIT_ID" type="varchar(40)" nullable="false"/>
|
||||||
|
<column name="CONTEXT" type="varchar(255)" nullable="false"/>
|
||||||
|
<column name="STATE" type="varchar(10)" nullable="false"/>
|
||||||
|
<column name="TARGET_URL" type="varchar(200)" nullable="true"/>
|
||||||
|
<column name="DESCRIPTION" type="text" nullable="true"/>
|
||||||
|
<column name="CREATOR" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REGISTERED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_COMMIT_STATUS_PK" tableName="COMMIT_STATUS" columnNames="COMMIT_STATUS_ID"/>
|
||||||
|
<addUniqueConstraint constraintName="IDX_COMMIT_STATUS_1" tableName="COMMIT_STATUS" columnNames="USER_NAME, REPOSITORY_NAME, COMMIT_ID, CONTEXT"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_COMMIT_STATUS_FK3" baseTableName="COMMIT_STATUS" baseColumnNames="CREATOR" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_COMMIT_STATUS_FK2" baseTableName="COMMIT_STATUS" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_COMMIT_STATUS_FK1" baseTableName="COMMIT_STATUS" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- GROUP_MEMBER -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="GROUP_MEMBER">
|
||||||
|
<column name="GROUP_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="MANAGER" type="boolean" nullable="true"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_GROUP_MEMBER_PK" tableName="GROUP_MEMBER" columnNames="GROUP_NAME, USER_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_GROUP_MEMBER_FK1" baseTableName="GROUP_MEMBER" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_GROUP_MEMBER_FK0" baseTableName="GROUP_MEMBER" baseColumnNames="GROUP_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- LABEL -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="LABEL">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="LABEL_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="LABEL_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="COLOR" type="char(6)" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_LABEL_PK" tableName="LABEL" columnNames="USER_NAME, REPOSITORY_NAME, LABEL_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_LABEL_FK0" baseTableName="LABEL" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- MILESTONE -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="MILESTONE">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="MILESTONE_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="TITLE" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="DESCRIPTION" type="text" nullable="true"/>
|
||||||
|
<column name="DUE_DATE" type="datetime" nullable="true"/>
|
||||||
|
<column name="CLOSED_DATE" type="datetime" nullable="true"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_MILESTONE_PK" tableName="MILESTONE" columnNames="USER_NAME, REPOSITORY_NAME, MILESTONE_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_MILESTONE_FK0" baseTableName="MILESTONE" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- ISSUE -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="ISSUE">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="ISSUE_ID" type="int" nullable="false"/>
|
||||||
|
<column name="OPENED_USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="MILESTONE_ID" type="int" nullable="true"/>
|
||||||
|
<column name="ASSIGNED_USER_NAME" type="varchar(100)" nullable="true"/>
|
||||||
|
<column name="TITLE" type="text" nullable="false"/>
|
||||||
|
<column name="CONTENT" type="text" nullable="true"/>
|
||||||
|
<column name="CLOSED" type="boolean" nullable="false"/>
|
||||||
|
<column name="REGISTERED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="PULL_REQUEST" type="boolean" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_ISSUE_PK" tableName="ISSUE" columnNames="ISSUE_ID, USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ISSUE_FK0" baseTableName="ISSUE" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ISSUE_FK2" baseTableName="ISSUE" baseColumnNames="MILESTONE_ID" referencedTableName="MILESTONE" referencedColumnNames="MILESTONE_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ISSUE_FK1" baseTableName="ISSUE" baseColumnNames="OPENED_USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- ISSUE_COMMENT -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="ISSUE_COMMENT">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="ISSUE_ID" type="int" nullable="false"/>
|
||||||
|
<column name="COMMENT_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="ACTION" type="varchar(20)" nullable="false"/>
|
||||||
|
<column name="COMMENTED_USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="CONTENT" type="text" nullable="false"/>
|
||||||
|
<column name="REGISTERED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_ISSUE_COMMENT_PK" tableName="ISSUE_COMMENT" columnNames="COMMENT_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ISSUE_COMMENT_FK0" baseTableName="ISSUE_COMMENT" baseColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID" referencedTableName="ISSUE" referencedColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- ISSUE_ID -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="ISSUE_ID">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="ISSUE_ID" type="int" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_ISSUE_ID_PK" tableName="ISSUE_ID" columnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ISSUE_ID_FK1" baseTableName="ISSUE_ID" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- ISSUE_ID -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="ISSUE_LABEL">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="ISSUE_ID" type="int" nullable="false"/>
|
||||||
|
<column name="LABEL_ID" type="int" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_ISSUE_LABEL_PK" tableName="ISSUE_LABEL" columnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID, LABEL_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ISSUE_LABEL_FK0" baseTableName="ISSUE_LABEL" baseColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID" referencedTableName="ISSUE" referencedColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- PLUGIN -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="PLUGIN">
|
||||||
|
<column name="PLUGIN_ID" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="VERSION" type="varchar(100)" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_PLUGIN_PK" tableName="PLUGIN" columnNames="PLUGIN_ID"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- PULL_REQUEST -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="PULL_REQUEST">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="ISSUE_ID" type="int" nullable="false"/>
|
||||||
|
<column name="BRANCH" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REQUEST_USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REQUEST_REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REQUEST_BRANCH" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="COMMIT_ID_FROM" type="varchar(40)" nullable="false"/>
|
||||||
|
<column name="COMMIT_ID_TO" type="varchar(40)" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_PULL_REQUEST_PK" tableName="PULL_REQUEST" columnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_PULL_REQUEST_FK0" baseTableName="PULL_REQUEST" baseColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID" referencedTableName="ISSUE" referencedColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- SSH_KEY -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="SSH_KEY">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="SSH_KEY_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="TITLE" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="PUBLIC_KEY" type="text" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_SSH_KEY_PK" tableName="SSH_KEY" columnNames="USER_NAME, SSH_KEY_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_SSH_KEY_FK0" baseTableName="SSH_KEY" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- WEB_HOOK -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="WEB_HOOK">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="URL" type="varchar(200)" nullable="false"/>
|
||||||
|
<column name="TOKEN" type="varchar(100)" nullable="true"/>
|
||||||
|
<column name="CTYPE" type="varchar(10)" nullable="true"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_WEB_HOOK_PK" tableName="WEB_HOOK" columnNames="USER_NAME, REPOSITORY_NAME, URL"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_WEB_HOOK_FK0" baseTableName="WEB_HOOK" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- WEB_HOOK_EVENT -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="WEB_HOOK_EVENT">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="URL" type="varchar(200)" nullable="false"/>
|
||||||
|
<column name="EVENT" type="varchar(30)" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_WEB_HOOK_EVENT_PK" tableName="WEB_HOOK_EVENT" columnNames="USER_NAME, REPOSITORY_NAME, URL, EVENT"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_WEB_HOOK_EVENT_FK0" baseTableName="WEB_HOOK_EVENT" baseColumnNames="USER_NAME, REPOSITORY_NAME, URL" referencedTableName="WEB_HOOK" referencedColumnNames="USER_NAME, REPOSITORY_NAME, URL" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- PROTECTED_BRANCH -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="PROTECTED_BRANCH">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="BRANCH" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="STATUS_CHECK_ADMIN" type="boolean" nullable="false" defaultValueBoolean="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_PROTECTED_BRANCH_PK" tableName="PROTECTED_BRANCH" columnNames="USER_NAME, REPOSITORY_NAME, BRANCH"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_PROTECTED_BRANCH_FK0" baseTableName="PROTECTED_BRANCH" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- PROTECTED_BRANCH_REQUIRE_CONTEXT -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="PROTECTED_BRANCH_REQUIRE_CONTEXT">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="BRANCH" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="CONTEXT" type="varchar(255)" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_PROTECTED_BRANCH_REQUIRE_CONTEXT_PK" tableName="PROTECTED_BRANCH_REQUIRE_CONTEXT" columnNames="USER_NAME, REPOSITORY_NAME, BRANCH, CONTEXT"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_PROTECTED_BRANCH_REQUIRE_CONTEXT_FK0" baseTableName="PROTECTED_BRANCH_REQUIRE_CONTEXT" baseColumnNames="USER_NAME, REPOSITORY_NAME, BRANCH" referencedTableName="PROTECTED_BRANCH" referencedColumnNames="USER_NAME, REPOSITORY_NAME, BRANCH" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||||
|
|
||||||
|
</changeSet>
|
||||||
14
src/main/resources/update/gitbucket-core_4.11.xml
Normal file
14
src/main/resources/update/gitbucket-core_4.11.xml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<changeSet>
|
||||||
|
<createTable tableName="DEPLOY_KEY">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="DEPLOY_KEY_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="TITLE" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="PUBLIC_KEY" type="text" nullable="false"/>
|
||||||
|
<column name="ALLOW_WRITE" type="boolean" nullable="false" defaultValueBoolean="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_DEPLOY_KEY_PK" tableName="DEPLOY_KEY" columnNames="USER_NAME, REPOSITORY_NAME, DEPLOY_KEY_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_DEPLOY_KEY_FK0" baseTableName="DEPLOY_KEY" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
</changeSet>
|
||||||
10
src/main/resources/update/gitbucket-core_4.2.xml
Normal file
10
src/main/resources/update/gitbucket-core_4.2.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<changeSet>
|
||||||
|
<addColumn tableName="REPOSITORY">
|
||||||
|
<column name="ENABLE_ISSUES" type="boolean" nullable="false" defaultValueBoolean="true"/>
|
||||||
|
<column name="EXTERNAL_ISSUES_URL" type="varchar(200)" nullable="true"/>
|
||||||
|
<column name="ENABLE_WIKI" type="boolean" nullable="false" defaultValueBoolean="true"/>
|
||||||
|
<column name="ALLOW_WIKI_EDITING" type="boolean" nullable="false" defaultValueBoolean="false"/>
|
||||||
|
<column name="EXTERNAL_WIKI_URL" type="varchar(200)" nullable="true"/>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
6
src/main/resources/update/gitbucket-core_4.6.xml
Normal file
6
src/main/resources/update/gitbucket-core_4.6.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<changeSet>
|
||||||
|
<addColumn tableName="REPOSITORY">
|
||||||
|
<column name="ALLOW_FORK" type="boolean" nullable="false" defaultValueBoolean="true"/>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
2
src/main/resources/update/gitbucket-core_4.7.sql
Normal file
2
src/main/resources/update/gitbucket-core_4.7.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
-- DELETE COLLABORATORS IN GROUP REPOSITORIES
|
||||||
|
DELETE FROM COLLABORATOR WHERE USER_NAME IN (SELECT USER_NAME FROM ACCOUNT WHERE GROUP_ACCOUNT = TRUE)
|
||||||
33
src/main/resources/update/gitbucket-core_4.7.xml
Normal file
33
src/main/resources/update/gitbucket-core_4.7.xml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<changeSet>
|
||||||
|
<addColumn tableName="COLLABORATOR">
|
||||||
|
<column name="ROLE" type="varchar(10)" nullable="false" defaultValue="ADMIN"/>
|
||||||
|
</addColumn>
|
||||||
|
<addColumn tableName="REPOSITORY">
|
||||||
|
<column name="WIKI_OPTION" type="varchar(10)" nullable="false" defaultValue="DISABLE"/>
|
||||||
|
<column name="ISSUES_OPTION" type="varchar(10)" nullable="false" defaultValue="DISABLE"/>
|
||||||
|
</addColumn>
|
||||||
|
<update tableName="REPOSITORY">
|
||||||
|
<column name="WIKI_OPTION" value="DISABLE"/>
|
||||||
|
<where>ENABLE_WIKI = FALSE</where>
|
||||||
|
</update>
|
||||||
|
<update tableName="REPOSITORY">
|
||||||
|
<column name="WIKI_OPTION" value="PRIVATE"/>
|
||||||
|
<where>ENABLE_WIKI = TRUE AND ALLOW_WIKI_EDITING = FALSE</where>
|
||||||
|
</update>
|
||||||
|
<update tableName="REPOSITORY">
|
||||||
|
<column name="WIKI_OPTION" value="PUBLIC"/>
|
||||||
|
<where>ENABLE_WIKI = TRUE AND ALLOW_WIKI_EDITING = TRUE</where>
|
||||||
|
</update>
|
||||||
|
<update tableName="REPOSITORY">
|
||||||
|
<column name="ISSUES_OPTION" value="DISABLE"/>
|
||||||
|
<where>ENABLE_ISSUES = FALSE</where>
|
||||||
|
</update>
|
||||||
|
<update tableName="REPOSITORY">
|
||||||
|
<column name="ISSUES_OPTION" value="PUBLIC"/>
|
||||||
|
<where>ENABLE_ISSUES = TRUE</where>
|
||||||
|
</update>
|
||||||
|
<dropColumn tableName="REPOSITORY" columnName="ENABLE_WIKI"/>
|
||||||
|
<dropColumn tableName="REPOSITORY" columnName="ALLOW_WIKI_EDITING"/>
|
||||||
|
<dropColumn tableName="REPOSITORY" columnName="ENABLE_ISSUES"/>
|
||||||
|
</changeSet>
|
||||||
6
src/main/resources/update/gitbucket-core_4.9.xml
Normal file
6
src/main/resources/update/gitbucket-core_4.9.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<changeSet>
|
||||||
|
<addColumn tableName="ACCOUNT">
|
||||||
|
<column name="DESCRIPTION" type="text" nullable="true" />
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
@@ -1,24 +1,33 @@
|
|||||||
|
|
||||||
import gitbucket.core.controller._
|
|
||||||
import gitbucket.core.plugin.PluginRegistry
|
|
||||||
import gitbucket.core.servlet.{AccessTokenAuthenticationFilter, BasicAuthenticationFilter, Database, TransactionFilter}
|
|
||||||
import gitbucket.core.util.Directory
|
|
||||||
|
|
||||||
import java.util.EnumSet
|
import java.util.EnumSet
|
||||||
import javax.servlet._
|
import javax.servlet._
|
||||||
|
|
||||||
|
import gitbucket.core.controller._
|
||||||
|
import gitbucket.core.plugin.PluginRegistry
|
||||||
|
import gitbucket.core.service.SystemSettingsService
|
||||||
|
import gitbucket.core.servlet._
|
||||||
|
import gitbucket.core.util.Directory
|
||||||
import org.scalatra._
|
import org.scalatra._
|
||||||
|
|
||||||
|
|
||||||
class ScalatraBootstrap extends LifeCycle {
|
class ScalatraBootstrap extends LifeCycle with SystemSettingsService {
|
||||||
override def init(context: ServletContext) {
|
override def init(context: ServletContext) {
|
||||||
|
|
||||||
|
val settings = loadSystemSettings()
|
||||||
|
if(settings.baseUrl.exists(_.startsWith("https://"))) {
|
||||||
|
context.getSessionCookieConfig.setSecure(true)
|
||||||
|
}
|
||||||
|
|
||||||
// Register TransactionFilter and BasicAuthenticationFilter at first
|
// Register TransactionFilter and BasicAuthenticationFilter at first
|
||||||
context.addFilter("transactionFilter", new TransactionFilter)
|
context.addFilter("transactionFilter", new TransactionFilter)
|
||||||
context.getFilterRegistration("transactionFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
context.getFilterRegistration("transactionFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
||||||
context.addFilter("basicAuthenticationFilter", new BasicAuthenticationFilter)
|
context.addFilter("gitAuthenticationFilter", new GitAuthenticationFilter)
|
||||||
context.getFilterRegistration("basicAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*")
|
context.getFilterRegistration("gitAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*")
|
||||||
context.addFilter("accessTokenAuthenticationFilter", new AccessTokenAuthenticationFilter)
|
context.addFilter("apiAuthenticationFilter", new ApiAuthenticationFilter)
|
||||||
context.getFilterRegistration("accessTokenAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/api/v3/*")
|
context.getFilterRegistration("apiAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/api/v3/*")
|
||||||
|
context.addFilter("ghCompatRepositoryAccessFilter", new GHCompatRepositoryAccessFilter)
|
||||||
|
context.getFilterRegistration("ghCompatRepositoryAccessFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
||||||
|
|
||||||
// Register controllers
|
// Register controllers
|
||||||
context.mount(new AnonymousAccessController, "/*")
|
context.mount(new AnonymousAccessController, "/*")
|
||||||
|
|
||||||
@@ -27,12 +36,10 @@ class ScalatraBootstrap extends LifeCycle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
context.mount(new IndexController, "/")
|
context.mount(new IndexController, "/")
|
||||||
context.mount(new SearchController, "/")
|
context.mount(new ApiController, "/api/v3")
|
||||||
context.mount(new FileUploadController, "/upload")
|
context.mount(new FileUploadController, "/upload")
|
||||||
|
context.mount(new SystemSettingsController, "/admin")
|
||||||
context.mount(new DashboardController, "/*")
|
context.mount(new DashboardController, "/*")
|
||||||
context.mount(new UserManagementController, "/*")
|
|
||||||
context.mount(new SystemSettingsController, "/*")
|
|
||||||
context.mount(new PluginsController, "/*")
|
|
||||||
context.mount(new AccountController, "/*")
|
context.mount(new AccountController, "/*")
|
||||||
context.mount(new RepositoryViewerController, "/*")
|
context.mount(new RepositoryViewerController, "/*")
|
||||||
context.mount(new WikiController, "/*")
|
context.mount(new WikiController, "/*")
|
||||||
|
|||||||
38
src/main/scala/gitbucket/core/GitBucketCoreModule.scala
Normal file
38
src/main/scala/gitbucket/core/GitBucketCoreModule.scala
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package gitbucket.core
|
||||||
|
|
||||||
|
import io.github.gitbucket.solidbase.migration.{SqlMigration, LiquibaseMigration}
|
||||||
|
import io.github.gitbucket.solidbase.model.{Version, Module}
|
||||||
|
|
||||||
|
object GitBucketCoreModule extends Module("gitbucket-core",
|
||||||
|
new Version("4.0.0",
|
||||||
|
new LiquibaseMigration("update/gitbucket-core_4.0.xml"),
|
||||||
|
new SqlMigration("update/gitbucket-core_4.0.sql")
|
||||||
|
),
|
||||||
|
new Version("4.1.0"),
|
||||||
|
new Version("4.2.0",
|
||||||
|
new LiquibaseMigration("update/gitbucket-core_4.2.xml")
|
||||||
|
),
|
||||||
|
new Version("4.2.1"),
|
||||||
|
new Version("4.3.0"),
|
||||||
|
new Version("4.4.0"),
|
||||||
|
new Version("4.5.0"),
|
||||||
|
new Version("4.6.0",
|
||||||
|
new LiquibaseMigration("update/gitbucket-core_4.6.xml")
|
||||||
|
),
|
||||||
|
new Version("4.7.0",
|
||||||
|
new LiquibaseMigration("update/gitbucket-core_4.7.xml"),
|
||||||
|
new SqlMigration("update/gitbucket-core_4.7.sql")
|
||||||
|
),
|
||||||
|
new Version("4.7.1"),
|
||||||
|
new Version("4.8"),
|
||||||
|
new Version("4.9.0",
|
||||||
|
new LiquibaseMigration("update/gitbucket-core_4.9.xml")
|
||||||
|
),
|
||||||
|
new Version("4.10.0"),
|
||||||
|
new Version("4.11.0",
|
||||||
|
new LiquibaseMigration("update/gitbucket-core_4.11.xml")
|
||||||
|
),
|
||||||
|
new Version("4.12.0"),
|
||||||
|
new Version("4.12.1"),
|
||||||
|
new Version("4.13.0")
|
||||||
|
)
|
||||||
23
src/main/scala/gitbucket/core/api/ApiBranch.scala
Normal file
23
src/main/scala/gitbucket/core/api/ApiBranch.scala
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
import gitbucket.core.util.RepositoryName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/#get-branch
|
||||||
|
* https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection
|
||||||
|
*/
|
||||||
|
case class ApiBranch(
|
||||||
|
name: String,
|
||||||
|
commit: ApiBranchCommit,
|
||||||
|
protection: ApiBranchProtection)(repositoryName:RepositoryName) extends FieldSerializable {
|
||||||
|
def _links = Map(
|
||||||
|
"self" -> ApiPath(s"/api/v3/repos/${repositoryName.fullName}/branches/${name}"),
|
||||||
|
"html" -> ApiPath(s"/${repositoryName.fullName}/tree/${name}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
case class ApiBranchCommit(sha: String)
|
||||||
|
|
||||||
|
case class ApiBranchForList(
|
||||||
|
name: String,
|
||||||
|
commit: ApiBranchCommit
|
||||||
|
)
|
||||||
46
src/main/scala/gitbucket/core/api/ApiBranchProtection.scala
Normal file
46
src/main/scala/gitbucket/core/api/ApiBranchProtection.scala
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
import gitbucket.core.service.ProtectedBranchService
|
||||||
|
import org.json4s._
|
||||||
|
|
||||||
|
/** https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */
|
||||||
|
case class ApiBranchProtection(enabled: Boolean, required_status_checks: Option[ApiBranchProtection.Status]){
|
||||||
|
def status: ApiBranchProtection.Status = required_status_checks.getOrElse(ApiBranchProtection.statusNone)
|
||||||
|
}
|
||||||
|
|
||||||
|
object ApiBranchProtection{
|
||||||
|
/** form for enabling-and-disabling-branch-protection */
|
||||||
|
case class EnablingAndDisabling(protection: ApiBranchProtection)
|
||||||
|
|
||||||
|
def apply(info: ProtectedBranchService.ProtectedBranchInfo): ApiBranchProtection = ApiBranchProtection(
|
||||||
|
enabled = info.enabled,
|
||||||
|
required_status_checks = Some(Status(EnforcementLevel(info.enabled && info.contexts.nonEmpty, info.includeAdministrators), info.contexts)))
|
||||||
|
val statusNone = Status(Off, Seq.empty)
|
||||||
|
case class Status(enforcement_level: EnforcementLevel, contexts: Seq[String])
|
||||||
|
sealed class EnforcementLevel(val name: String)
|
||||||
|
case object Off extends EnforcementLevel("off")
|
||||||
|
case object NonAdmins extends EnforcementLevel("non_admins")
|
||||||
|
case object Everyone extends EnforcementLevel("everyone")
|
||||||
|
object EnforcementLevel {
|
||||||
|
def apply(enabled: Boolean, includeAdministrators: Boolean): EnforcementLevel = if(enabled){
|
||||||
|
if(includeAdministrators){
|
||||||
|
Everyone
|
||||||
|
}else{
|
||||||
|
NonAdmins
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
Off
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit val enforcementLevelSerializer = new CustomSerializer[EnforcementLevel](format => (
|
||||||
|
{
|
||||||
|
case JString("off") => Off
|
||||||
|
case JString("non_admins") => NonAdmins
|
||||||
|
case JString("everyone") => Everyone
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case x: EnforcementLevel => JString(x.name)
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
28
src/main/scala/gitbucket/core/api/ApiContents.scala
Normal file
28
src/main/scala/gitbucket/core/api/ApiContents.scala
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
import java.util.Base64
|
||||||
|
|
||||||
|
import gitbucket.core.util.JGitUtil.FileInfo
|
||||||
|
import gitbucket.core.util.RepositoryName
|
||||||
|
|
||||||
|
case class ApiContents(
|
||||||
|
`type`: String,
|
||||||
|
name: String,
|
||||||
|
path: String,
|
||||||
|
sha: String,
|
||||||
|
content: Option[String],
|
||||||
|
encoding: Option[String])(repositoryName: RepositoryName){
|
||||||
|
val download_url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/raw/${sha}/${path}")
|
||||||
|
}
|
||||||
|
|
||||||
|
object ApiContents{
|
||||||
|
def apply(fileInfo: FileInfo, repositoryName: RepositoryName, content: Option[Array[Byte]]): ApiContents = {
|
||||||
|
if(fileInfo.isDirectory) {
|
||||||
|
ApiContents("dir", fileInfo.name, fileInfo.path, fileInfo.commitId, None, None)(repositoryName)
|
||||||
|
} else {
|
||||||
|
content.map(arr =>
|
||||||
|
ApiContents("file", fileInfo.name, fileInfo.path, fileInfo.commitId, Some(Base64.getEncoder.encodeToString(arr)), Some("base64"))(repositoryName)
|
||||||
|
).getOrElse(ApiContents("file", fileInfo.name, fileInfo.path, fileInfo.commitId, None, None)(repositoryName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/main/scala/gitbucket/core/api/ApiEndPoint.scala
Normal file
3
src/main/scala/gitbucket/core/api/ApiEndPoint.scala
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
case class ApiEndPoint(rate_limit_url: ApiPath = ApiPath("/api/v3/rate_limit"))
|
||||||
@@ -20,6 +20,16 @@ case class ApiIssue(
|
|||||||
body: String)(repositoryName: RepositoryName, isPullRequest: Boolean){
|
body: String)(repositoryName: RepositoryName, isPullRequest: Boolean){
|
||||||
val comments_url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/issues/${number}/comments")
|
val comments_url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/issues/${number}/comments")
|
||||||
val html_url = ApiPath(s"/${repositoryName.fullName}/${if(isPullRequest){ "pull" }else{ "issues" }}/${number}")
|
val html_url = ApiPath(s"/${repositoryName.fullName}/${if(isPullRequest){ "pull" }else{ "issues" }}/${number}")
|
||||||
|
val pull_request = if (isPullRequest) {
|
||||||
|
Some(Map(
|
||||||
|
"url" -> ApiPath(s"/api/v3/repos/${repositoryName.fullName}/pulls/${number}"),
|
||||||
|
"html_url" -> ApiPath(s"/${repositoryName.fullName}/pull/${number}")
|
||||||
|
// "diff_url" -> ApiPath(s"/${repositoryName.fullName}/pull/${number}.diff"),
|
||||||
|
// "patch_url" -> ApiPath(s"/${repositoryName.fullName}/pull/${number}.patch")
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object ApiIssue{
|
object ApiIssue{
|
||||||
|
|||||||
21
src/main/scala/gitbucket/core/api/ApiLabel.scala
Normal file
21
src/main/scala/gitbucket/core/api/ApiLabel.scala
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
import gitbucket.core.model.Label
|
||||||
|
import gitbucket.core.util.RepositoryName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/issues/labels/
|
||||||
|
*/
|
||||||
|
case class ApiLabel(
|
||||||
|
name: String,
|
||||||
|
color: String)(repositoryName: RepositoryName){
|
||||||
|
var url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/labels/${name}")
|
||||||
|
}
|
||||||
|
|
||||||
|
object ApiLabel{
|
||||||
|
def apply(label:Label, repositoryName: RepositoryName): ApiLabel =
|
||||||
|
ApiLabel(
|
||||||
|
name = label.labelName,
|
||||||
|
color = label.color
|
||||||
|
)(repositoryName)
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package gitbucket.core.api
|
package gitbucket.core.api
|
||||||
|
|
||||||
import gitbucket.core.model.{Issue, PullRequest}
|
import gitbucket.core.model.{Account, Issue, IssueComment, PullRequest}
|
||||||
|
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
|
|
||||||
@@ -15,6 +14,9 @@ case class ApiPullRequest(
|
|||||||
head: ApiPullRequest.Commit,
|
head: ApiPullRequest.Commit,
|
||||||
base: ApiPullRequest.Commit,
|
base: ApiPullRequest.Commit,
|
||||||
mergeable: Option[Boolean],
|
mergeable: Option[Boolean],
|
||||||
|
merged: Boolean,
|
||||||
|
merged_at: Option[Date],
|
||||||
|
merged_by: Option[ApiUser],
|
||||||
title: String,
|
title: String,
|
||||||
body: String,
|
body: String,
|
||||||
user: ApiUser) {
|
user: ApiUser) {
|
||||||
@@ -31,7 +33,15 @@ case class ApiPullRequest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
object ApiPullRequest{
|
object ApiPullRequest{
|
||||||
def apply(issue: Issue, pullRequest: PullRequest, headRepo: ApiRepository, baseRepo: ApiRepository, user: ApiUser): ApiPullRequest = ApiPullRequest(
|
def apply(
|
||||||
|
issue: Issue,
|
||||||
|
pullRequest: PullRequest,
|
||||||
|
headRepo: ApiRepository,
|
||||||
|
baseRepo: ApiRepository,
|
||||||
|
user: ApiUser,
|
||||||
|
mergedComment: Option[(IssueComment, Account)]
|
||||||
|
): ApiPullRequest =
|
||||||
|
ApiPullRequest(
|
||||||
number = issue.issueId,
|
number = issue.issueId,
|
||||||
updated_at = issue.updatedDate,
|
updated_at = issue.updatedDate,
|
||||||
created_at = issue.registeredDate,
|
created_at = issue.registeredDate,
|
||||||
@@ -44,6 +54,9 @@ object ApiPullRequest{
|
|||||||
ref = pullRequest.branch,
|
ref = pullRequest.branch,
|
||||||
repo = baseRepo)(issue.userName),
|
repo = baseRepo)(issue.userName),
|
||||||
mergeable = None, // TODO: need check mergeable.
|
mergeable = None, // TODO: need check mergeable.
|
||||||
|
merged = mergedComment.isDefined,
|
||||||
|
merged_at = mergedComment.map { case (comment, _) => comment.registeredDate },
|
||||||
|
merged_by = mergedComment.map { case (_, account) => ApiUser(account) },
|
||||||
title = issue.title,
|
title = issue.title,
|
||||||
body = issue.content.getOrElse(""),
|
body = issue.content.getOrElse(""),
|
||||||
user = user
|
user = user
|
||||||
|
|||||||
5
src/main/scala/gitbucket/core/api/ApiRef.scala
Normal file
5
src/main/scala/gitbucket/core/api/ApiRef.scala
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
case class ApiObject(sha: String)
|
||||||
|
|
||||||
|
case class ApiRef(ref: String, `object`: ApiObject)
|
||||||
@@ -18,7 +18,7 @@ case class ApiRepository(
|
|||||||
val watchers_count = watchers
|
val watchers_count = watchers
|
||||||
val url = if(urlIsHtmlUrl){
|
val url = if(urlIsHtmlUrl){
|
||||||
ApiPath(s"/${full_name}")
|
ApiPath(s"/${full_name}")
|
||||||
}else{
|
} else {
|
||||||
ApiPath(s"/api/v3/repos/${full_name}")
|
ApiPath(s"/api/v3/repos/${full_name}")
|
||||||
}
|
}
|
||||||
val http_url = ApiPath(s"/git/${full_name}.git")
|
val http_url = ApiPath(s"/git/${full_name}.git")
|
||||||
@@ -37,7 +37,7 @@ object ApiRepository{
|
|||||||
name = repository.repositoryName,
|
name = repository.repositoryName,
|
||||||
full_name = s"${repository.userName}/${repository.repositoryName}",
|
full_name = s"${repository.userName}/${repository.repositoryName}",
|
||||||
description = repository.description.getOrElse(""),
|
description = repository.description.getOrElse(""),
|
||||||
watchers = 0,
|
watchers = watchers,
|
||||||
forks = forkedCount,
|
forks = forkedCount,
|
||||||
`private` = repository.isPrivate,
|
`private` = repository.isPrivate,
|
||||||
default_branch = repository.defaultBranch,
|
default_branch = repository.defaultBranch,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ case class ApiUser(
|
|||||||
created_at: Date) {
|
created_at: Date) {
|
||||||
val url = ApiPath(s"/api/v3/users/${login}")
|
val url = ApiPath(s"/api/v3/users/${login}")
|
||||||
val html_url = ApiPath(s"/${login}")
|
val html_url = ApiPath(s"/${login}")
|
||||||
|
val avatar_url = ApiPath(s"/${login}/_avatar")
|
||||||
// val followers_url = ApiPath(s"/api/v3/users/${login}/followers")
|
// val followers_url = ApiPath(s"/api/v3/users/${login}/followers")
|
||||||
// val following_url = ApiPath(s"/api/v3/users/${login}/following{/other_user}")
|
// val following_url = ApiPath(s"/api/v3/users/${login}/following{/other_user}")
|
||||||
// val gists_url = ApiPath(s"/api/v3/users/${login}/gists{/gist_id}")
|
// val gists_url = ApiPath(s"/api/v3/users/${login}/gists{/gist_id}")
|
||||||
@@ -29,7 +30,7 @@ object ApiUser{
|
|||||||
def apply(user: Account): ApiUser = ApiUser(
|
def apply(user: Account): ApiUser = ApiUser(
|
||||||
login = user.userName,
|
login = user.userName,
|
||||||
email = user.mailAddress,
|
email = user.mailAddress,
|
||||||
`type` = if(user.isGroupAccount){ "Organization" }else{ "User" },
|
`type` = if(user.isGroupAccount){ "Organization" } else { "User" },
|
||||||
site_admin = user.isAdmin,
|
site_admin = user.isAdmin,
|
||||||
created_at = user.registeredDate
|
created_at = user.registeredDate
|
||||||
)
|
)
|
||||||
|
|||||||
18
src/main/scala/gitbucket/core/api/CreateALabel.scala
Normal file
18
src/main/scala/gitbucket/core/api/CreateALabel.scala
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/issues/labels/#create-a-label
|
||||||
|
* api form
|
||||||
|
*/
|
||||||
|
case class CreateALabel(
|
||||||
|
name: String,
|
||||||
|
color: String
|
||||||
|
) {
|
||||||
|
def isValid: Boolean = {
|
||||||
|
name.length<=100 &&
|
||||||
|
!name.startsWith("_") &&
|
||||||
|
!name.startsWith("-") &&
|
||||||
|
color.length==6 &&
|
||||||
|
color.matches("[a-fA-F0-9+_.]+")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ case class CreateARepository(
|
|||||||
auto_init: Boolean = false
|
auto_init: Boolean = false
|
||||||
) {
|
) {
|
||||||
def isValid: Boolean = {
|
def isValid: Boolean = {
|
||||||
name.length<=40 &&
|
name.length <= 100 &&
|
||||||
name.matches("[a-zA-Z0-9\\-\\+_.]+") &&
|
name.matches("[a-zA-Z0-9\\-\\+_.]+") &&
|
||||||
!name.startsWith("_") &&
|
!name.startsWith("_") &&
|
||||||
!name.startsWith("-")
|
!name.startsWith("-")
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ case class CreateAStatus(
|
|||||||
def isValid: Boolean = {
|
def isValid: Boolean = {
|
||||||
CommitState.valueOf(state).isDefined &&
|
CommitState.valueOf(state).isDefined &&
|
||||||
// only http
|
// only http
|
||||||
target_url.filterNot(f => "\\Ahttps?://".r.findPrefixOf(f).isDefined && f.length<255).isEmpty &&
|
target_url.forall(f => "\\Ahttps?://".r.findPrefixOf(f).isDefined && f.length < 255) &&
|
||||||
context.filterNot(f => f.length<255).isEmpty &&
|
context.forall(f => f.length < 255) &&
|
||||||
description.filterNot(f => f.length<1000).isEmpty
|
description.forall(f => f.length < 1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/main/scala/gitbucket/core/api/CreateAnIssue.scala
Normal file
11
src/main/scala/gitbucket/core/api/CreateAnIssue.scala
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/issues/#create-an-issue
|
||||||
|
*/
|
||||||
|
case class CreateAnIssue(
|
||||||
|
title: String,
|
||||||
|
body: Option[String],
|
||||||
|
assignees: List[String],
|
||||||
|
milestone: Option[Int],
|
||||||
|
labels: List[String])
|
||||||
@@ -30,7 +30,10 @@ object JsonFormat {
|
|||||||
FieldSerializer[ApiCombinedCommitStatus]() +
|
FieldSerializer[ApiCombinedCommitStatus]() +
|
||||||
FieldSerializer[ApiPullRequest.Commit]() +
|
FieldSerializer[ApiPullRequest.Commit]() +
|
||||||
FieldSerializer[ApiIssue]() +
|
FieldSerializer[ApiIssue]() +
|
||||||
FieldSerializer[ApiComment]()
|
FieldSerializer[ApiComment]() +
|
||||||
|
FieldSerializer[ApiContents]() +
|
||||||
|
FieldSerializer[ApiLabel]() +
|
||||||
|
ApiBranchProtection.enforcementLevelSerializer
|
||||||
|
|
||||||
def apiPathSerializer(c: Context) = new CustomSerializer[ApiPath](format =>
|
def apiPathSerializer(c: Context) = new CustomSerializer[ApiPath](format =>
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -1,51 +1,49 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
import gitbucket.core.account.html
|
import gitbucket.core.account.html
|
||||||
import gitbucket.core.api._
|
|
||||||
import gitbucket.core.helper
|
import gitbucket.core.helper
|
||||||
import gitbucket.core.model.GroupMember
|
import gitbucket.core.model.{GroupMember, Role}
|
||||||
|
import gitbucket.core.plugin.PluginRegistry
|
||||||
import gitbucket.core.service._
|
import gitbucket.core.service._
|
||||||
import gitbucket.core.ssh.SshUtil
|
import gitbucket.core.ssh.SshUtil
|
||||||
import gitbucket.core.util.ControlUtil._
|
import gitbucket.core.util.SyntaxSugars._
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util.StringUtil._
|
import gitbucket.core.util.StringUtil._
|
||||||
import gitbucket.core.util._
|
import gitbucket.core.util._
|
||||||
|
import io.github.gitbucket.scalatra.forms._
|
||||||
import jp.sf.amateras.scalatra.forms._
|
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import org.eclipse.jgit.api.Git
|
|
||||||
import org.eclipse.jgit.dircache.DirCache
|
|
||||||
import org.eclipse.jgit.lib.{FileMode, Constants}
|
|
||||||
import org.scalatra.i18n.Messages
|
import org.scalatra.i18n.Messages
|
||||||
|
import org.scalatra.BadRequest
|
||||||
|
|
||||||
|
|
||||||
class AccountController extends AccountControllerBase
|
class AccountController extends AccountControllerBase
|
||||||
with AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
|
with AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
|
||||||
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
|
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
|
||||||
with AccessTokenService with WebHookService
|
with AccessTokenService with WebHookService with RepositoryCreationService
|
||||||
|
|
||||||
|
|
||||||
trait AccountControllerBase extends AccountManagementControllerBase {
|
trait AccountControllerBase extends AccountManagementControllerBase {
|
||||||
self: AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
|
self: AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
|
||||||
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
|
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
|
||||||
with AccessTokenService with WebHookService =>
|
with AccessTokenService with WebHookService with RepositoryCreationService =>
|
||||||
|
|
||||||
case class AccountNewForm(userName: String, password: String, fullName: String, mailAddress: String,
|
case class AccountNewForm(userName: String, password: String, fullName: String, mailAddress: String,
|
||||||
url: Option[String], fileId: Option[String])
|
description: Option[String], url: Option[String], fileId: Option[String])
|
||||||
|
|
||||||
case class AccountEditForm(password: Option[String], fullName: String, mailAddress: String,
|
case class AccountEditForm(password: Option[String], fullName: String, mailAddress: String,
|
||||||
url: Option[String], fileId: Option[String], clearImage: Boolean)
|
description: Option[String], url: Option[String], fileId: Option[String], clearImage: Boolean)
|
||||||
|
|
||||||
case class SshKeyForm(title: String, publicKey: String)
|
case class SshKeyForm(title: String, publicKey: String)
|
||||||
|
|
||||||
case class PersonalTokenForm(note: String)
|
case class PersonalTokenForm(note: String)
|
||||||
|
|
||||||
val newForm = mapping(
|
val newForm = mapping(
|
||||||
"userName" -> trim(label("User name" , text(required, maxlength(100), identifier, uniqueUserName))),
|
"userName" -> trim(label("User name" , text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||||
"password" -> trim(label("Password" , text(required, maxlength(20)))),
|
"password" -> trim(label("Password" , text(required, maxlength(20)))),
|
||||||
"fullName" -> trim(label("Full Name" , text(required, maxlength(100)))),
|
"fullName" -> trim(label("Full Name" , text(required, maxlength(100)))),
|
||||||
"mailAddress" -> trim(label("Mail Address" , text(required, maxlength(100), uniqueMailAddress()))),
|
"mailAddress" -> trim(label("Mail Address" , text(required, maxlength(100), uniqueMailAddress()))),
|
||||||
|
"description" -> trim(label("bio" , optional(text()))),
|
||||||
"url" -> trim(label("URL" , optional(text(maxlength(200))))),
|
"url" -> trim(label("URL" , optional(text(maxlength(200))))),
|
||||||
"fileId" -> trim(label("File ID" , optional(text())))
|
"fileId" -> trim(label("File ID" , optional(text())))
|
||||||
)(AccountNewForm.apply)
|
)(AccountNewForm.apply)
|
||||||
@@ -54,6 +52,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
"password" -> trim(label("Password" , optional(text(maxlength(20))))),
|
"password" -> trim(label("Password" , optional(text(maxlength(20))))),
|
||||||
"fullName" -> trim(label("Full Name" , text(required, maxlength(100)))),
|
"fullName" -> trim(label("Full Name" , text(required, maxlength(100)))),
|
||||||
"mailAddress" -> trim(label("Mail Address" , text(required, maxlength(100), uniqueMailAddress("userName")))),
|
"mailAddress" -> trim(label("Mail Address" , text(required, maxlength(100), uniqueMailAddress("userName")))),
|
||||||
|
"description" -> trim(label("bio" , optional(text()))),
|
||||||
"url" -> trim(label("URL" , optional(text(maxlength(200))))),
|
"url" -> trim(label("URL" , optional(text(maxlength(200))))),
|
||||||
"fileId" -> trim(label("File ID" , optional(text()))),
|
"fileId" -> trim(label("File ID" , optional(text()))),
|
||||||
"clearImage" -> trim(label("Clear image" , boolean()))
|
"clearImage" -> trim(label("Clear image" , boolean()))
|
||||||
@@ -61,18 +60,19 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
|
|
||||||
val sshKeyForm = mapping(
|
val sshKeyForm = mapping(
|
||||||
"title" -> trim(label("Title", text(required, maxlength(100)))),
|
"title" -> trim(label("Title", text(required, maxlength(100)))),
|
||||||
"publicKey" -> trim(label("Key" , text(required, validPublicKey)))
|
"publicKey" -> trim2(label("Key" , text(required, validPublicKey)))
|
||||||
)(SshKeyForm.apply)
|
)(SshKeyForm.apply)
|
||||||
|
|
||||||
val personalTokenForm = mapping(
|
val personalTokenForm = mapping(
|
||||||
"note" -> trim(label("Token", text(required, maxlength(100))))
|
"note" -> trim(label("Token", text(required, maxlength(100))))
|
||||||
)(PersonalTokenForm.apply)
|
)(PersonalTokenForm.apply)
|
||||||
|
|
||||||
case class NewGroupForm(groupName: String, url: Option[String], fileId: Option[String], members: String)
|
case class NewGroupForm(groupName: String, description: Option[String], url: Option[String], fileId: Option[String], members: String)
|
||||||
case class EditGroupForm(groupName: String, url: Option[String], fileId: Option[String], members: String, clearImage: Boolean)
|
case class EditGroupForm(groupName: String, description: Option[String], url: Option[String], fileId: Option[String], members: String, clearImage: Boolean)
|
||||||
|
|
||||||
val newGroupForm = mapping(
|
val newGroupForm = mapping(
|
||||||
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier, uniqueUserName))),
|
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||||
|
"description" -> trim(label("Group description", optional(text()))),
|
||||||
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
||||||
"fileId" -> trim(label("File ID" ,optional(text()))),
|
"fileId" -> trim(label("File ID" ,optional(text()))),
|
||||||
"members" -> trim(label("Members" ,text(required, members)))
|
"members" -> trim(label("Members" ,text(required, members)))
|
||||||
@@ -80,6 +80,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
|
|
||||||
val editGroupForm = mapping(
|
val editGroupForm = mapping(
|
||||||
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier))),
|
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier))),
|
||||||
|
"description" -> trim(label("Group description", optional(text()))),
|
||||||
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
||||||
"fileId" -> trim(label("File ID" ,optional(text()))),
|
"fileId" -> trim(label("File ID" ,optional(text()))),
|
||||||
"members" -> trim(label("Members" ,text(required, members))),
|
"members" -> trim(label("Members" ,text(required, members))),
|
||||||
@@ -124,7 +125,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
// Members
|
// Members
|
||||||
case "members" if(account.isGroupAccount) => {
|
case "members" if(account.isGroupAccount) => {
|
||||||
val members = getGroupMembers(account.userName)
|
val members = getGroupMembers(account.userName)
|
||||||
gitbucket.core.account.html.members(account, members.map(_.userName),
|
gitbucket.core.account.html.members(account, members,
|
||||||
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }))
|
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,11 +134,11 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
val members = getGroupMembers(account.userName)
|
val members = getGroupMembers(account.userName)
|
||||||
gitbucket.core.account.html.repositories(account,
|
gitbucket.core.account.html.repositories(account,
|
||||||
if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
||||||
getVisibleRepositories(context.loginAccount, context.baseUrl, Some(userName)),
|
getVisibleRepositories(context.loginAccount, Some(userName)),
|
||||||
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }))
|
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
get("/:userName.atom") {
|
get("/:userName.atom") {
|
||||||
@@ -148,38 +149,29 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
|
|
||||||
get("/:userName/_avatar"){
|
get("/:userName/_avatar"){
|
||||||
val userName = params("userName")
|
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"
|
contentType = "image/png"
|
||||||
|
getAccountByUserName(userName).flatMap{ account =>
|
||||||
|
response.setDateHeader("Last-Modified", account.updatedDate.getTime)
|
||||||
|
account.image.map{ image =>
|
||||||
|
Some(RawData(FileUtil.getMimeType(image), new java.io.File(getUserUploadDir(userName), image)))
|
||||||
|
}.getOrElse{
|
||||||
|
if (account.isGroupAccount) {
|
||||||
|
TextAvatarUtil.textGroupAvatar(account.fullName)
|
||||||
|
} else {
|
||||||
|
TextAvatarUtil.textAvatar(account.fullName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.getOrElse{
|
||||||
|
response.setHeader("Cache-Control", "max-age=3600")
|
||||||
Thread.currentThread.getContextClassLoader.getResourceAsStream("noimage.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 {
|
get("/:userName/_edit")(oneselfOnly {
|
||||||
val userName = params("userName")
|
val userName = params("userName")
|
||||||
getAccountByUserName(userName).map { x =>
|
getAccountByUserName(userName).map { x =>
|
||||||
html.edit(x, flash.get("info"))
|
html.edit(x, flash.get("info"), flash.get("error"))
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound()
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:userName/_edit", editForm)(oneselfOnly { form =>
|
post("/:userName/_edit", editForm)(oneselfOnly { form =>
|
||||||
@@ -189,19 +181,24 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
password = form.password.map(sha1).getOrElse(account.password),
|
password = form.password.map(sha1).getOrElse(account.password),
|
||||||
fullName = form.fullName,
|
fullName = form.fullName,
|
||||||
mailAddress = form.mailAddress,
|
mailAddress = form.mailAddress,
|
||||||
|
description = form.description,
|
||||||
url = form.url))
|
url = form.url))
|
||||||
|
|
||||||
updateImage(userName, form.fileId, form.clearImage)
|
updateImage(userName, form.fileId, form.clearImage)
|
||||||
flash += "info" -> "Account information has been updated."
|
flash += "info" -> "Account information has been updated."
|
||||||
redirect(s"/${userName}/_edit")
|
redirect(s"/${userName}/_edit")
|
||||||
|
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound()
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:userName/_delete")(oneselfOnly {
|
get("/:userName/_delete")(oneselfOnly {
|
||||||
val userName = params("userName")
|
val userName = params("userName")
|
||||||
|
|
||||||
getAccountByUserName(userName, true).foreach { account =>
|
getAccountByUserName(userName, true).map { account =>
|
||||||
|
if(isLastAdministrator(account)){
|
||||||
|
flash += "error" -> "Account can't be removed because this is last one administrator."
|
||||||
|
redirect(s"/${userName}/_edit")
|
||||||
|
} else {
|
||||||
// // Remove repositories
|
// // Remove repositories
|
||||||
// getRepositoryNamesOfUser(userName).foreach { repositoryName =>
|
// getRepositoryNamesOfUser(userName).foreach { repositoryName =>
|
||||||
// deleteRepository(userName, repositoryName)
|
// deleteRepository(userName, repositoryName)
|
||||||
@@ -210,21 +207,19 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
|
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
|
||||||
// }
|
// }
|
||||||
// // Remove from GROUP_MEMBER, COLLABORATOR and REPOSITORY
|
// // Remove from GROUP_MEMBER, COLLABORATOR and REPOSITORY
|
||||||
// removeUserRelatedData(userName)
|
|
||||||
|
|
||||||
removeUserRelatedData(userName)
|
removeUserRelatedData(userName)
|
||||||
updateAccount(account.copy(isRemoved = true))
|
updateAccount(account.copy(isRemoved = true))
|
||||||
}
|
|
||||||
|
|
||||||
session.invalidate
|
session.invalidate
|
||||||
redirect("/")
|
redirect("/")
|
||||||
|
}
|
||||||
|
} getOrElse NotFound()
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:userName/_ssh")(oneselfOnly {
|
get("/:userName/_ssh")(oneselfOnly {
|
||||||
val userName = params("userName")
|
val userName = params("userName")
|
||||||
getAccountByUserName(userName).map { x =>
|
getAccountByUserName(userName).map { x =>
|
||||||
html.ssh(x, getPublicKeys(x.userName))
|
html.ssh(x, getPublicKeys(x.userName))
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound()
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:userName/_ssh", sshKeyForm)(oneselfOnly { form =>
|
post("/:userName/_ssh", sshKeyForm)(oneselfOnly { form =>
|
||||||
@@ -255,7 +250,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
case _ => None
|
case _ => None
|
||||||
}
|
}
|
||||||
html.application(x, tokens, generatedToken)
|
html.application(x, tokens, generatedToken)
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound()
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:userName/_personalToken", personalTokenForm)(oneselfOnly { form =>
|
post("/:userName/_personalToken", personalTokenForm)(oneselfOnly { form =>
|
||||||
@@ -281,15 +276,15 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
} else {
|
} else {
|
||||||
html.register()
|
html.register()
|
||||||
}
|
}
|
||||||
} else NotFound
|
} else NotFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
post("/register", newForm){ form =>
|
post("/register", newForm){ form =>
|
||||||
if(context.settings.allowAccountRegistration){
|
if(context.settings.allowAccountRegistration){
|
||||||
createAccount(form.userName, sha1(form.password), form.fullName, form.mailAddress, false, form.url)
|
createAccount(form.userName, sha1(form.password), form.fullName, form.mailAddress, false, form.description, form.url)
|
||||||
updateImage(form.userName, form.fileId, false)
|
updateImage(form.userName, form.fileId, false)
|
||||||
redirect("/signin")
|
redirect("/signin")
|
||||||
} else NotFound
|
} else NotFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
get("/groups/new")(usersOnly {
|
get("/groups/new")(usersOnly {
|
||||||
@@ -297,7 +292,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
post("/groups/new", newGroupForm)(usersOnly { form =>
|
post("/groups/new", newGroupForm)(usersOnly { form =>
|
||||||
createGroup(form.groupName, form.url)
|
createGroup(form.groupName, form.description, form.url)
|
||||||
updateGroupMembers(form.groupName, form.members.split(",").map {
|
updateGroupMembers(form.groupName, form.members.split(",").map {
|
||||||
_.split(":") match {
|
_.split(":") match {
|
||||||
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
||||||
@@ -335,22 +330,22 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
}
|
}
|
||||||
}.toList){ case (groupName, members) =>
|
}.toList){ case (groupName, members) =>
|
||||||
getAccountByUserName(groupName, true).map { account =>
|
getAccountByUserName(groupName, true).map { account =>
|
||||||
updateGroup(groupName, form.url, false)
|
updateGroup(groupName, form.description, form.url, false)
|
||||||
|
|
||||||
// Update GROUP_MEMBER
|
// Update GROUP_MEMBER
|
||||||
updateGroupMembers(form.groupName, members)
|
updateGroupMembers(form.groupName, members)
|
||||||
// Update COLLABORATOR for group repositories
|
// // Update COLLABORATOR for group repositories
|
||||||
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
||||||
removeCollaborators(form.groupName, repositoryName)
|
// removeCollaborators(form.groupName, repositoryName)
|
||||||
members.foreach { case (userName, isManager) =>
|
// members.foreach { case (userName, isManager) =>
|
||||||
addCollaborator(form.groupName, repositoryName, userName)
|
// addCollaborator(form.groupName, repositoryName, userName)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
updateImage(form.groupName, form.fileId, form.clearImage)
|
updateImage(form.groupName, form.fileId, form.clearImage)
|
||||||
redirect(s"/${form.groupName}")
|
redirect(s"/${form.groupName}")
|
||||||
|
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -366,64 +361,21 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
*/
|
*/
|
||||||
post("/new", newRepositoryForm)(usersOnly { form =>
|
post("/new", newRepositoryForm)(usersOnly { form =>
|
||||||
LockUtil.lock(s"${form.owner}/${form.name}"){
|
LockUtil.lock(s"${form.owner}/${form.name}"){
|
||||||
if(getRepository(form.owner, form.name, context.baseUrl).isEmpty){
|
if(getRepository(form.owner, form.name).isEmpty){
|
||||||
createRepository(form.owner, form.name, form.description, form.isPrivate, form.createReadme)
|
// Create the repository
|
||||||
|
createRepository(context.loginAccount.get, form.owner, form.name, form.description, form.isPrivate, form.createReadme)
|
||||||
|
|
||||||
|
// Call hooks
|
||||||
|
PluginRegistry().getRepositoryHooks.foreach(_.created(form.owner, form.name))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// redirect to the repository
|
// redirect to the repository
|
||||||
redirect(s"/${form.owner}/${form.name}")
|
redirect(s"/${form.owner}/${form.name}")
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create user repository
|
|
||||||
* https://developer.github.com/v3/repos/#create
|
|
||||||
*/
|
|
||||||
post("/api/v3/user/repos")(usersOnly {
|
|
||||||
val owner = context.loginAccount.get.userName
|
|
||||||
(for {
|
|
||||||
data <- extractFromJsonBody[CreateARepository] if data.isValid
|
|
||||||
} yield {
|
|
||||||
LockUtil.lock(s"${owner}/${data.name}") {
|
|
||||||
if(getRepository(owner, data.name, context.baseUrl).isEmpty){
|
|
||||||
createRepository(owner, data.name, data.description, data.`private`, data.auto_init)
|
|
||||||
val repository = getRepository(owner, data.name, context.baseUrl).get
|
|
||||||
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(owner).get)))
|
|
||||||
} else {
|
|
||||||
ApiError(
|
|
||||||
"A repository with this name already exists on this account",
|
|
||||||
Some("https://developer.github.com/v3/repos/#create")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create group repository
|
|
||||||
* https://developer.github.com/v3/repos/#create
|
|
||||||
*/
|
|
||||||
post("/api/v3/orgs/:org/repos")(managersOnly {
|
|
||||||
val groupName = params("org")
|
|
||||||
(for {
|
|
||||||
data <- extractFromJsonBody[CreateARepository] if data.isValid
|
|
||||||
} yield {
|
|
||||||
LockUtil.lock(s"${groupName}/${data.name}") {
|
|
||||||
if(getRepository(groupName, data.name, context.baseUrl).isEmpty){
|
|
||||||
createRepository(groupName, data.name, data.description, data.`private`, data.auto_init)
|
|
||||||
val repository = getRepository(groupName, data.name, context.baseUrl).get
|
|
||||||
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(groupName).get)))
|
|
||||||
} else {
|
|
||||||
ApiError(
|
|
||||||
"A repository with this name already exists for this group",
|
|
||||||
Some("https://developer.github.com/v3/repos/#create")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) getOrElse NotFound
|
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/fork")(readableUsersOnly { repository =>
|
get("/:owner/:repository/fork")(readableUsersOnly { repository =>
|
||||||
|
if(repository.repository.options.allowFork){
|
||||||
val loginAccount = context.loginAccount.get
|
val loginAccount = context.loginAccount.get
|
||||||
val loginUserName = loginAccount.userName
|
val loginUserName = loginAccount.userName
|
||||||
val groups = getGroupsByUserName(loginUserName)
|
val groups = getGroupsByUserName(loginUserName)
|
||||||
@@ -439,15 +391,17 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
)
|
)
|
||||||
case _ => redirect(s"/${loginUserName}")
|
case _ => redirect(s"/${loginUserName}")
|
||||||
}
|
}
|
||||||
|
} else BadRequest()
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/fork", accountForm)(readableUsersOnly { (form, repository) =>
|
post("/:owner/:repository/fork", accountForm)(readableUsersOnly { (form, repository) =>
|
||||||
|
if(repository.repository.options.allowFork){
|
||||||
val loginAccount = context.loginAccount.get
|
val loginAccount = context.loginAccount.get
|
||||||
val loginUserName = loginAccount.userName
|
val loginUserName = loginAccount.userName
|
||||||
val accountName = form.accountName
|
val accountName = form.accountName
|
||||||
|
|
||||||
LockUtil.lock(s"${accountName}/${repository.name}"){
|
LockUtil.lock(s"${accountName}/${repository.name}"){
|
||||||
if(getRepository(accountName, repository.name, baseUrl).isDefined ||
|
if(getRepository(accountName, repository.name).isDefined ||
|
||||||
(accountName != loginUserName && !getGroupsByUserName(loginUserName).contains(accountName))){
|
(accountName != loginUserName && !getGroupsByUserName(loginUserName).contains(accountName))){
|
||||||
// redirect to the repository if repository already exists
|
// redirect to the repository if repository already exists
|
||||||
redirect(s"/${accountName}/${repository.name}")
|
redirect(s"/${accountName}/${repository.name}")
|
||||||
@@ -456,7 +410,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
val originUserName = repository.repository.originUserName.getOrElse(repository.owner)
|
val originUserName = repository.repository.originUserName.getOrElse(repository.owner)
|
||||||
val originRepositoryName = repository.repository.originRepositoryName.getOrElse(repository.name)
|
val originRepositoryName = repository.repository.originRepositoryName.getOrElse(repository.name)
|
||||||
|
|
||||||
createRepository(
|
insertRepository(
|
||||||
repositoryName = repository.name,
|
repositoryName = repository.name,
|
||||||
userName = accountName,
|
userName = accountName,
|
||||||
description = repository.repository.description,
|
description = repository.repository.description,
|
||||||
@@ -467,12 +421,14 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
parentUserName = Some(repository.owner)
|
parentUserName = Some(repository.owner)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Add collaborators for group repository
|
// Set default collaborators for the private fork
|
||||||
val ownerAccount = getAccountByUserName(accountName).get
|
if(repository.repository.isPrivate){
|
||||||
if(ownerAccount.isGroupAccount){
|
// Copy collaborators from the source repository
|
||||||
getGroupMembers(accountName).foreach { member =>
|
getCollaborators(repository.owner, repository.name).foreach { case (collaborator, _) =>
|
||||||
addCollaborator(accountName, repository.name, member.userName)
|
addCollaborator(accountName, repository.name, collaborator.collaboratorName, collaborator.role)
|
||||||
}
|
}
|
||||||
|
// Register an owner of the source repository as a collaborator
|
||||||
|
addCollaborator(accountName, repository.name, repository.owner, Role.ADMIN.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert default labels
|
// Insert default labels
|
||||||
@@ -490,74 +446,17 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
|
|
||||||
// Record activity
|
// Record activity
|
||||||
recordForkActivity(repository.owner, repository.name, loginUserName, accountName)
|
recordForkActivity(repository.owner, repository.name, loginUserName, accountName)
|
||||||
|
|
||||||
|
// Call hooks
|
||||||
|
PluginRegistry().getRepositoryHooks.foreach(_.forked(repository.owner, accountName, repository.name))
|
||||||
|
|
||||||
// redirect to the repository
|
// redirect to the repository
|
||||||
redirect(s"/${accountName}/${repository.name}")
|
redirect(s"/${accountName}/${repository.name}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else BadRequest()
|
||||||
})
|
})
|
||||||
|
|
||||||
private def createRepository(owner: String, name: String, description: Option[String], isPrivate: Boolean, createReadme: Boolean) {
|
|
||||||
val ownerAccount = getAccountByUserName(owner).get
|
|
||||||
val loginAccount = context.loginAccount.get
|
|
||||||
val loginUserName = loginAccount.userName
|
|
||||||
|
|
||||||
// Insert to the database at first
|
|
||||||
createRepository(name, owner, description, isPrivate)
|
|
||||||
|
|
||||||
// Add collaborators for group repository
|
|
||||||
if(ownerAccount.isGroupAccount){
|
|
||||||
getGroupMembers(owner).foreach { member =>
|
|
||||||
addCollaborator(owner, name, member.userName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert default labels
|
|
||||||
insertDefaultLabels(owner, name)
|
|
||||||
|
|
||||||
// Create the actual repository
|
|
||||||
val gitdir = getRepositoryDir(owner, name)
|
|
||||||
JGitUtil.initRepository(gitdir)
|
|
||||||
|
|
||||||
if(createReadme){
|
|
||||||
using(Git.open(gitdir)){ git =>
|
|
||||||
val builder = DirCache.newInCore.builder()
|
|
||||||
val inserter = git.getRepository.newObjectInserter()
|
|
||||||
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
|
|
||||||
val content = if(description.nonEmpty){
|
|
||||||
name + "\n" +
|
|
||||||
"===============\n" +
|
|
||||||
"\n" +
|
|
||||||
description.get
|
|
||||||
} else {
|
|
||||||
name + "\n" +
|
|
||||||
"===============\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.add(JGitUtil.createDirCacheEntry("README.md", FileMode.REGULAR_FILE,
|
|
||||||
inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8"))))
|
|
||||||
builder.finish()
|
|
||||||
|
|
||||||
JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
|
|
||||||
Constants.HEAD, loginAccount.fullName, loginAccount.mailAddress, "Initial commit")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Wiki repository
|
|
||||||
createWikiRepository(loginAccount, owner, name)
|
|
||||||
|
|
||||||
// Record activity
|
|
||||||
recordCreateRepositoryActivity(owner, name, loginUserName)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def insertDefaultLabels(userName: String, repositoryName: String): Unit = {
|
|
||||||
createLabel(userName, repositoryName, "bug", "fc2929")
|
|
||||||
createLabel(userName, repositoryName, "duplicate", "cccccc")
|
|
||||||
createLabel(userName, repositoryName, "enhancement", "84b6eb")
|
|
||||||
createLabel(userName, repositoryName, "invalid", "e6e6e6")
|
|
||||||
createLabel(userName, repositoryName, "question", "cc317c")
|
|
||||||
createLabel(userName, repositoryName, "wontfix", "ffffff")
|
|
||||||
}
|
|
||||||
|
|
||||||
private def existsAccount: Constraint = new Constraint(){
|
private def existsAccount: Constraint = new Constraint(){
|
||||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||||
if(getAccountByUserName(value).isEmpty) Some("User or group does not exist.") else None
|
if(getAccountByUserName(value).isEmpty) Some("User or group does not exist.") else None
|
||||||
@@ -580,8 +479,8 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
|
|
||||||
private def validPublicKey: Constraint = new Constraint(){
|
private def validPublicKey: Constraint = new Constraint(){
|
||||||
override def validate(name: String, value: String, messages: Messages): Option[String] = SshUtil.str2PublicKey(value) match {
|
override def validate(name: String, value: String, messages: Messages): Option[String] = SshUtil.str2PublicKey(value) match {
|
||||||
case Some(_) => None
|
case Some(_) if !getAllKeys().exists(_.publicKey == value) => None
|
||||||
case None => Some("Key is invalid.")
|
case _ => Some("Key is invalid.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
645
src/main/scala/gitbucket/core/controller/ApiController.scala
Normal file
645
src/main/scala/gitbucket/core/controller/ApiController.scala
Normal file
@@ -0,0 +1,645 @@
|
|||||||
|
package gitbucket.core.controller
|
||||||
|
|
||||||
|
import gitbucket.core.api._
|
||||||
|
import gitbucket.core.model._
|
||||||
|
import gitbucket.core.service.IssuesService.IssueSearchCondition
|
||||||
|
import gitbucket.core.service.PullRequestService._
|
||||||
|
import gitbucket.core.service._
|
||||||
|
import gitbucket.core.util.SyntaxSugars._
|
||||||
|
import gitbucket.core.util.Directory._
|
||||||
|
import gitbucket.core.util.JGitUtil._
|
||||||
|
import gitbucket.core.util._
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.view.helpers.{renderMarkup, isRenderable}
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.scalatra.{NoContent, UnprocessableEntity, Created}
|
||||||
|
import scala.collection.JavaConverters._
|
||||||
|
|
||||||
|
class ApiController extends ApiControllerBase
|
||||||
|
with RepositoryService
|
||||||
|
with AccountService
|
||||||
|
with ProtectedBranchService
|
||||||
|
with IssuesService
|
||||||
|
with LabelsService
|
||||||
|
with MilestonesService
|
||||||
|
with PullRequestService
|
||||||
|
with CommitsService
|
||||||
|
with CommitStatusService
|
||||||
|
with RepositoryCreationService
|
||||||
|
with IssueCreationService
|
||||||
|
with HandleCommentService
|
||||||
|
with WebHookService
|
||||||
|
with WebHookPullRequestService
|
||||||
|
with WebHookIssueCommentService
|
||||||
|
with WikiService
|
||||||
|
with ActivityService
|
||||||
|
with OwnerAuthenticator
|
||||||
|
with UsersAuthenticator
|
||||||
|
with GroupManagerAuthenticator
|
||||||
|
with ReferrerAuthenticator
|
||||||
|
with ReadableUsersAuthenticator
|
||||||
|
with WritableUsersAuthenticator
|
||||||
|
|
||||||
|
trait ApiControllerBase extends ControllerBase {
|
||||||
|
self: RepositoryService
|
||||||
|
with AccountService
|
||||||
|
with ProtectedBranchService
|
||||||
|
with IssuesService
|
||||||
|
with LabelsService
|
||||||
|
with MilestonesService
|
||||||
|
with PullRequestService
|
||||||
|
with CommitStatusService
|
||||||
|
with RepositoryCreationService
|
||||||
|
with IssueCreationService
|
||||||
|
with HandleCommentService
|
||||||
|
with OwnerAuthenticator
|
||||||
|
with UsersAuthenticator
|
||||||
|
with GroupManagerAuthenticator
|
||||||
|
with ReferrerAuthenticator
|
||||||
|
with ReadableUsersAuthenticator
|
||||||
|
with WritableUsersAuthenticator =>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 404 for non-implemented api
|
||||||
|
*/
|
||||||
|
get("/api/v3/*") {
|
||||||
|
NotFound()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/#root-endpoint
|
||||||
|
*/
|
||||||
|
get("/api/v3/") {
|
||||||
|
JsonFormat(ApiEndPoint())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/orgs/#get-an-organization
|
||||||
|
*/
|
||||||
|
get("/api/v3/orgs/:groupName") {
|
||||||
|
getAccountByUserName(params("groupName")).filter(account => account.isGroupAccount).map { account =>
|
||||||
|
JsonFormat(ApiUser(account))
|
||||||
|
} getOrElse NotFound()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/users/#get-a-single-user
|
||||||
|
* This API also returns group information (as GitHub).
|
||||||
|
*/
|
||||||
|
get("/api/v3/users/:userName") {
|
||||||
|
getAccountByUserName(params("userName")).map { account =>
|
||||||
|
JsonFormat(ApiUser(account))
|
||||||
|
} getOrElse NotFound()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/#list-organization-repositories
|
||||||
|
*/
|
||||||
|
get("/api/v3/orgs/:orgName/repos") {
|
||||||
|
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("orgName"))).map{ r => ApiRepository(r, getAccountByUserName(r.owner).get)})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/#list-user-repositories
|
||||||
|
*/
|
||||||
|
get("/api/v3/users/:userName/repos") {
|
||||||
|
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("userName"))).map{ r => ApiRepository(r, getAccountByUserName(r.owner).get)})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* https://developer.github.com/v3/repos/branches/#list-branches
|
||||||
|
*/
|
||||||
|
get ("/api/v3/repos/:owner/:repo/branches")(referrersOnly { repository =>
|
||||||
|
JsonFormat(JGitUtil.getBranches(
|
||||||
|
owner = repository.owner,
|
||||||
|
name = repository.name,
|
||||||
|
defaultBranch = repository.repository.defaultBranch,
|
||||||
|
origin = repository.repository.originUserName.isEmpty
|
||||||
|
).map { br =>
|
||||||
|
ApiBranchForList(br.name, ApiBranchCommit(br.commitId))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/branches/#get-branch
|
||||||
|
*/
|
||||||
|
get ("/api/v3/repos/:owner/:repo/branches/:branch")(referrersOnly { repository =>
|
||||||
|
//import gitbucket.core.api._
|
||||||
|
(for{
|
||||||
|
branch <- params.get("branch") if repository.branchList.contains(branch)
|
||||||
|
br <- getBranches(repository.owner, repository.name, repository.repository.defaultBranch, repository.repository.originUserName.isEmpty).find(_.name == branch)
|
||||||
|
} yield {
|
||||||
|
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||||
|
JsonFormat(ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtection(protection))(RepositoryName(repository)))
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* https://developer.github.com/v3/repos/contents/#get-contents
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repo/contents/*")(referrersOnly { repository =>
|
||||||
|
def getFileInfo(git: Git, revision: String, pathStr: String): Option[FileInfo] = {
|
||||||
|
val path = new java.io.File(pathStr)
|
||||||
|
val dirName = path.getParent match {
|
||||||
|
case null => "."
|
||||||
|
case s => s
|
||||||
|
}
|
||||||
|
getFileList(git, revision, dirName).find(f => f.name.equals(path.getName))
|
||||||
|
}
|
||||||
|
|
||||||
|
val path = multiParams("splat").head match {
|
||||||
|
case s if s.isEmpty => "."
|
||||||
|
case s => s
|
||||||
|
}
|
||||||
|
val refStr = params.getOrElse("ref", repository.repository.defaultBranch)
|
||||||
|
|
||||||
|
using(Git.open(getRepositoryDir(params("owner"), params("repo")))){ git =>
|
||||||
|
val fileList = getFileList(git, refStr, path)
|
||||||
|
if (fileList.isEmpty) { // file or NotFound
|
||||||
|
getFileInfo(git, refStr, path).flatMap(f => {
|
||||||
|
val largeFile = params.get("large_file").exists(s => s.equals("true"))
|
||||||
|
val content = getContentFromId(git, f.id, largeFile)
|
||||||
|
request.getHeader("Accept") match {
|
||||||
|
case "application/vnd.github.v3.raw" => {
|
||||||
|
contentType = "application/vnd.github.v3.raw"
|
||||||
|
content
|
||||||
|
}
|
||||||
|
case "application/vnd.github.v3.html" if isRenderable(f.name) => {
|
||||||
|
contentType = "application/vnd.github.v3.html"
|
||||||
|
content.map(c =>
|
||||||
|
List(
|
||||||
|
"<div data-path=\"", path, "\" id=\"file\">", "<article>",
|
||||||
|
renderMarkup(path.split("/").toList, new String(c), refStr, repository, false, false, true).body,
|
||||||
|
"</article>", "</div>"
|
||||||
|
).mkString
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case "application/vnd.github.v3.html" => {
|
||||||
|
contentType = "application/vnd.github.v3.html"
|
||||||
|
content.map(c =>
|
||||||
|
List(
|
||||||
|
"<div data-path=\"", path, "\" id=\"file\">", "<div class=\"plain\">", "<pre>",
|
||||||
|
play.twirl.api.HtmlFormat.escape(new String(c)).body,
|
||||||
|
"</pre>", "</div>", "</div>"
|
||||||
|
).mkString
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case _ =>
|
||||||
|
Some(JsonFormat(ApiContents(f, RepositoryName(repository), content)))
|
||||||
|
}
|
||||||
|
}).getOrElse(NotFound())
|
||||||
|
} else { // directory
|
||||||
|
JsonFormat(fileList.map{f => ApiContents(f, RepositoryName(repository), None)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* https://developer.github.com/v3/git/refs/#get-a-reference
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repo/git/*") (referrersOnly { repository =>
|
||||||
|
val revstr = multiParams("splat").head
|
||||||
|
using(Git.open(getRepositoryDir(params("owner"), params("repo")))) { git =>
|
||||||
|
//JsonFormat( (revstr, git.getRepository().resolve(revstr)) )
|
||||||
|
// getRef is deprecated by jgit-4.2. use exactRef() or findRef()
|
||||||
|
val sha = git.getRepository().exactRef(revstr).getObjectId().name()
|
||||||
|
JsonFormat(ApiRef(revstr, ApiObject(sha)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/collaborators/#list-collaborators
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repo/collaborators") (referrersOnly { repository =>
|
||||||
|
// TODO Should ApiUser take permission? getCollaboratorUserNames does not return owner group members.
|
||||||
|
JsonFormat(getCollaboratorUserNames(params("owner"), params("repo")).map(u => ApiUser(getAccountByUserName(u).get)))
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/users/#get-the-authenticated-user
|
||||||
|
*/
|
||||||
|
get("/api/v3/user") {
|
||||||
|
context.loginAccount.map { account =>
|
||||||
|
JsonFormat(ApiUser(account))
|
||||||
|
} getOrElse Unauthorized()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List user's own repository
|
||||||
|
* https://developer.github.com/v3/repos/#list-your-repositories
|
||||||
|
*/
|
||||||
|
get("/api/v3/user/repos")(usersOnly{
|
||||||
|
JsonFormat(getVisibleRepositories(context.loginAccount, Option(context.loginAccount.get.userName)).map{
|
||||||
|
r => ApiRepository(r, getAccountByUserName(r.owner).get)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create user repository
|
||||||
|
* https://developer.github.com/v3/repos/#create
|
||||||
|
*/
|
||||||
|
post("/api/v3/user/repos")(usersOnly {
|
||||||
|
val owner = context.loginAccount.get.userName
|
||||||
|
(for {
|
||||||
|
data <- extractFromJsonBody[CreateARepository] if data.isValid
|
||||||
|
} yield {
|
||||||
|
LockUtil.lock(s"${owner}/${data.name}") {
|
||||||
|
if(getRepository(owner, data.name).isEmpty){
|
||||||
|
createRepository(context.loginAccount.get, owner, data.name, data.description, data.`private`, data.auto_init)
|
||||||
|
val repository = getRepository(owner, data.name).get
|
||||||
|
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(owner).get)))
|
||||||
|
} else {
|
||||||
|
ApiError(
|
||||||
|
"A repository with this name already exists on this account",
|
||||||
|
Some("https://developer.github.com/v3/repos/#create")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create group repository
|
||||||
|
* https://developer.github.com/v3/repos/#create
|
||||||
|
*/
|
||||||
|
post("/api/v3/orgs/:org/repos")(managersOnly {
|
||||||
|
val groupName = params("org")
|
||||||
|
(for {
|
||||||
|
data <- extractFromJsonBody[CreateARepository] if data.isValid
|
||||||
|
} yield {
|
||||||
|
LockUtil.lock(s"${groupName}/${data.name}") {
|
||||||
|
if(getRepository(groupName, data.name).isEmpty){
|
||||||
|
createRepository(context.loginAccount.get, groupName, data.name, data.description, data.`private`, data.auto_init)
|
||||||
|
val repository = getRepository(groupName, data.name).get
|
||||||
|
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(groupName).get)))
|
||||||
|
} else {
|
||||||
|
ApiError(
|
||||||
|
"A repository with this name already exists for this group",
|
||||||
|
Some("https://developer.github.com/v3/repos/#create")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection
|
||||||
|
*/
|
||||||
|
patch("/api/v3/repos/:owner/:repo/branches/:branch")(ownerOnly { repository =>
|
||||||
|
import gitbucket.core.api._
|
||||||
|
(for{
|
||||||
|
branch <- params.get("branch") if repository.branchList.contains(branch)
|
||||||
|
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
|
||||||
|
br <- getBranches(repository.owner, repository.name, repository.repository.defaultBranch, repository.repository.originUserName.isEmpty).find(_.name == branch)
|
||||||
|
} yield {
|
||||||
|
if(protection.enabled){
|
||||||
|
enableBranchProtection(repository.owner, repository.name, branch, protection.status.enforcement_level == ApiBranchProtection.Everyone, protection.status.contexts)
|
||||||
|
} else {
|
||||||
|
disableBranchProtection(repository.owner, repository.name, branch)
|
||||||
|
}
|
||||||
|
JsonFormat(ApiBranch(branch, ApiBranchCommit(br.commitId), protection)(RepositoryName(repository)))
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see https://developer.github.com/v3/rate_limit/#get-your-current-rate-limit-status
|
||||||
|
* but not enabled.
|
||||||
|
*/
|
||||||
|
get("/api/v3/rate_limit"){
|
||||||
|
contentType = formats("json")
|
||||||
|
// this message is same as github enterprise...
|
||||||
|
org.scalatra.NotFound(ApiError("Rate limiting is not enabled."))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/issues/#list-issues-for-a-repository
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/issues")(referrersOnly { repository =>
|
||||||
|
val page = IssueSearchCondition.page(request)
|
||||||
|
// TODO: more api spec condition
|
||||||
|
val condition = IssueSearchCondition(request)
|
||||||
|
val baseOwner = getAccountByUserName(repository.owner).get
|
||||||
|
|
||||||
|
val issues: List[(Issue, Account)] =
|
||||||
|
searchIssueByApi(
|
||||||
|
condition = condition,
|
||||||
|
offset = (page - 1) * PullRequestLimit,
|
||||||
|
limit = PullRequestLimit,
|
||||||
|
repos = repository.owner -> repository.name
|
||||||
|
)
|
||||||
|
|
||||||
|
JsonFormat(issues.map { case (issue, issueUser) =>
|
||||||
|
ApiIssue(
|
||||||
|
issue = issue,
|
||||||
|
repositoryName = RepositoryName(repository),
|
||||||
|
user = ApiUser(issueUser)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/issues/#get-a-single-issue
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/issues/:id")(referrersOnly { repository =>
|
||||||
|
(for{
|
||||||
|
issueId <- params("id").toIntOpt
|
||||||
|
issue <- getIssue(repository.owner, repository.name, issueId.toString)
|
||||||
|
openedUser <- getAccountByUserName(issue.openedUserName)
|
||||||
|
} yield {
|
||||||
|
JsonFormat(ApiIssue(issue, RepositoryName(repository), ApiUser(openedUser)))
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/issues/#create-an-issue
|
||||||
|
*/
|
||||||
|
post("/api/v3/repos/:owner/:repository/issues")(readableUsersOnly { repository =>
|
||||||
|
if(isIssueEditable(repository)){ // TODO Should this check is provided by authenticator?
|
||||||
|
(for{
|
||||||
|
data <- extractFromJsonBody[CreateAnIssue]
|
||||||
|
loginAccount <- context.loginAccount
|
||||||
|
} yield {
|
||||||
|
val milestone = data.milestone.flatMap(getMilestone(repository.owner, repository.name, _))
|
||||||
|
val issue = createIssue(
|
||||||
|
repository,
|
||||||
|
data.title,
|
||||||
|
data.body,
|
||||||
|
data.assignees.headOption,
|
||||||
|
milestone.map(_.milestoneId),
|
||||||
|
data.labels,
|
||||||
|
loginAccount)
|
||||||
|
JsonFormat(ApiIssue(issue, RepositoryName(repository), ApiUser(loginAccount)))
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
} else Unauthorized()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/issues/:id/comments")(referrersOnly { repository =>
|
||||||
|
(for{
|
||||||
|
issueId <- params("id").toIntOpt
|
||||||
|
comments = getCommentsForApi(repository.owner, repository.name, issueId.toInt)
|
||||||
|
} yield {
|
||||||
|
JsonFormat(comments.map{ case (issueComment, user, issue) => ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest) })
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/issues/comments/#create-a-comment
|
||||||
|
*/
|
||||||
|
post("/api/v3/repos/:owner/:repository/issues/:id/comments")(readableUsersOnly { repository =>
|
||||||
|
(for{
|
||||||
|
issueId <- params("id").toIntOpt
|
||||||
|
issue <- getIssue(repository.owner, repository.name, issueId.toString)
|
||||||
|
body <- extractFromJsonBody[CreateAComment].map(_.body) if ! body.isEmpty
|
||||||
|
action = params.get("action").filter(_ => isEditable(issue.userName, issue.repositoryName, issue.openedUserName))
|
||||||
|
(issue, id) <- handleComment(issue, Some(body), repository, action)
|
||||||
|
issueComment <- getComment(repository.owner, repository.name, id.toString())
|
||||||
|
} yield {
|
||||||
|
JsonFormat(ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(context.loginAccount.get), issue.isPullRequest))
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all labels for this repository
|
||||||
|
* https://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/labels")(referrersOnly { repository =>
|
||||||
|
JsonFormat(getLabels(repository.owner, repository.name).map { label =>
|
||||||
|
ApiLabel(label, RepositoryName(repository))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a single label
|
||||||
|
* https://developer.github.com/v3/issues/labels/#get-a-single-label
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/labels/:labelName")(referrersOnly { repository =>
|
||||||
|
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
|
||||||
|
JsonFormat(ApiLabel(label, RepositoryName(repository)))
|
||||||
|
} getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a label
|
||||||
|
* https://developer.github.com/v3/issues/labels/#create-a-label
|
||||||
|
*/
|
||||||
|
post("/api/v3/repos/:owner/:repository/labels")(writableUsersOnly { repository =>
|
||||||
|
(for{
|
||||||
|
data <- extractFromJsonBody[CreateALabel] if data.isValid
|
||||||
|
} yield {
|
||||||
|
LockUtil.lock(RepositoryName(repository).fullName) {
|
||||||
|
if (getLabel(repository.owner, repository.name, data.name).isEmpty) {
|
||||||
|
val labelId = createLabel(repository.owner, repository.name, data.name, data.color)
|
||||||
|
getLabel(repository.owner, repository.name, labelId).map { label =>
|
||||||
|
Created(JsonFormat(ApiLabel(label, RepositoryName(repository))))
|
||||||
|
} getOrElse NotFound()
|
||||||
|
} else {
|
||||||
|
// TODO ApiError should support errors field to enhance compatibility of GitHub API
|
||||||
|
UnprocessableEntity(ApiError(
|
||||||
|
"Validation Failed",
|
||||||
|
Some("https://developer.github.com/v3/issues/labels/#create-a-label")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a label
|
||||||
|
* https://developer.github.com/v3/issues/labels/#update-a-label
|
||||||
|
*/
|
||||||
|
patch("/api/v3/repos/:owner/:repository/labels/:labelName")(writableUsersOnly { repository =>
|
||||||
|
(for{
|
||||||
|
data <- extractFromJsonBody[CreateALabel] if data.isValid
|
||||||
|
} yield {
|
||||||
|
LockUtil.lock(RepositoryName(repository).fullName) {
|
||||||
|
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
|
||||||
|
if (getLabel(repository.owner, repository.name, data.name).isEmpty) {
|
||||||
|
updateLabel(repository.owner, repository.name, label.labelId, data.name, data.color)
|
||||||
|
JsonFormat(ApiLabel(
|
||||||
|
getLabel(repository.owner, repository.name, label.labelId).get,
|
||||||
|
RepositoryName(repository)
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
// TODO ApiError should support errors field to enhance compatibility of GitHub API
|
||||||
|
UnprocessableEntity(ApiError(
|
||||||
|
"Validation Failed",
|
||||||
|
Some("https://developer.github.com/v3/issues/labels/#create-a-label")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} getOrElse NotFound()
|
||||||
|
}
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a label
|
||||||
|
* https://developer.github.com/v3/issues/labels/#delete-a-label
|
||||||
|
*/
|
||||||
|
delete("/api/v3/repos/:owner/:repository/labels/:labelName")(writableUsersOnly { repository =>
|
||||||
|
LockUtil.lock(RepositoryName(repository).fullName) {
|
||||||
|
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
|
||||||
|
deleteLabel(repository.owner, repository.name, label.labelId)
|
||||||
|
NoContent()
|
||||||
|
} getOrElse NotFound()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/pulls/#list-pull-requests
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/pulls")(referrersOnly { repository =>
|
||||||
|
val page = IssueSearchCondition.page(request)
|
||||||
|
// TODO: more api spec condition
|
||||||
|
val condition = IssueSearchCondition(request)
|
||||||
|
val baseOwner = getAccountByUserName(repository.owner).get
|
||||||
|
|
||||||
|
val issues: List[(Issue, Account, Int, PullRequest, Repository, Account)] =
|
||||||
|
searchPullRequestByApi(
|
||||||
|
condition = condition,
|
||||||
|
offset = (page - 1) * PullRequestLimit,
|
||||||
|
limit = PullRequestLimit,
|
||||||
|
repos = repository.owner -> repository.name
|
||||||
|
)
|
||||||
|
|
||||||
|
JsonFormat(issues.map { case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner) =>
|
||||||
|
ApiPullRequest(
|
||||||
|
issue = issue,
|
||||||
|
pullRequest = pullRequest,
|
||||||
|
headRepo = ApiRepository(headRepo, ApiUser(headOwner)),
|
||||||
|
baseRepo = ApiRepository(repository, ApiUser(baseOwner)),
|
||||||
|
user = ApiUser(issueUser),
|
||||||
|
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/pulls/#get-a-single-pull-request
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/pulls/:id")(referrersOnly { repository =>
|
||||||
|
(for{
|
||||||
|
issueId <- params("id").toIntOpt
|
||||||
|
(issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId)
|
||||||
|
users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set.empty)
|
||||||
|
baseOwner <- users.get(repository.owner)
|
||||||
|
headOwner <- users.get(pullRequest.requestUserName)
|
||||||
|
issueUser <- users.get(issue.openedUserName)
|
||||||
|
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
|
||||||
|
} yield {
|
||||||
|
JsonFormat(ApiPullRequest(
|
||||||
|
issue = issue,
|
||||||
|
pullRequest = pullRequest,
|
||||||
|
headRepo = ApiRepository(headRepo, ApiUser(headOwner)),
|
||||||
|
baseRepo = ApiRepository(repository, ApiUser(baseOwner)),
|
||||||
|
user = ApiUser(issueUser),
|
||||||
|
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
|
||||||
|
))
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/pulls/#list-commits-on-a-pull-request
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/pulls/:id/commits")(referrersOnly { repository =>
|
||||||
|
val owner = repository.owner
|
||||||
|
val name = repository.name
|
||||||
|
params("id").toIntOpt.flatMap{ issueId =>
|
||||||
|
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
|
||||||
|
using(Git.open(getRepositoryDir(owner, name))){ git =>
|
||||||
|
val oldId = git.getRepository.resolve(pullreq.commitIdFrom)
|
||||||
|
val newId = git.getRepository.resolve(pullreq.commitIdTo)
|
||||||
|
val repoFullName = RepositoryName(repository)
|
||||||
|
val commits = git.log.addRange(oldId, newId).call.iterator.asScala.map { c => ApiCommitListItem(new CommitInfo(c), repoFullName) }.toList
|
||||||
|
JsonFormat(commits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/#get
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository")(referrersOnly { repository =>
|
||||||
|
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(repository.owner).get)))
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/statuses/#create-a-status
|
||||||
|
*/
|
||||||
|
post("/api/v3/repos/:owner/:repo/statuses/:sha")(writableUsersOnly { repository =>
|
||||||
|
(for{
|
||||||
|
ref <- params.get("sha")
|
||||||
|
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
||||||
|
data <- extractFromJsonBody[CreateAStatus] if data.isValid
|
||||||
|
creator <- context.loginAccount
|
||||||
|
state <- CommitState.valueOf(data.state)
|
||||||
|
statusId = createCommitStatus(repository.owner, repository.name, sha, data.context.getOrElse("default"),
|
||||||
|
state, data.target_url, data.description, new java.util.Date(), creator)
|
||||||
|
status <- getCommitStatus(repository.owner, repository.name, statusId)
|
||||||
|
} yield {
|
||||||
|
JsonFormat(ApiCommitStatus(status, ApiUser(creator)))
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref
|
||||||
|
*
|
||||||
|
* ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name.
|
||||||
|
*/
|
||||||
|
val listStatusesRoute = get("/api/v3/repos/:owner/:repo/commits/:ref/statuses")(referrersOnly { repository =>
|
||||||
|
(for{
|
||||||
|
ref <- params.get("ref")
|
||||||
|
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
||||||
|
} yield {
|
||||||
|
JsonFormat(getCommitStatuesWithCreator(repository.owner, repository.name, sha).map{ case(status, creator) =>
|
||||||
|
ApiCommitStatus(status, ApiUser(creator))
|
||||||
|
})
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref
|
||||||
|
*
|
||||||
|
* legacy route
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repo/statuses/:ref"){
|
||||||
|
listStatusesRoute.action()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref
|
||||||
|
*
|
||||||
|
* ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name.
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repo/commits/:ref/status")(referrersOnly { repository =>
|
||||||
|
(for{
|
||||||
|
ref <- params.get("ref")
|
||||||
|
owner <- getAccountByUserName(repository.owner)
|
||||||
|
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
||||||
|
} yield {
|
||||||
|
val statuses = getCommitStatuesWithCreator(repository.owner, repository.name, sha)
|
||||||
|
JsonFormat(ApiCombinedCommitStatus(sha, statuses, ApiRepository(repository, owner)))
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
|
||||||
|
hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* non-GitHub compatible API for Jenkins-Plugin
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repo/raw/*")(referrersOnly { repository =>
|
||||||
|
val (id, path) = repository.splitPath(multiParams("splat").head)
|
||||||
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
|
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
||||||
|
|
||||||
|
getPathObjectId(git, path, revCommit).map { objectId =>
|
||||||
|
responseRawFile(git, objectId, path, repository)
|
||||||
|
} getOrElse NotFound()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,25 +1,31 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
|
import java.io.FileInputStream
|
||||||
|
|
||||||
import gitbucket.core.api.ApiError
|
import gitbucket.core.api.ApiError
|
||||||
import gitbucket.core.model.Account
|
import gitbucket.core.model.Account
|
||||||
import gitbucket.core.service.{AccountService, SystemSettingsService}
|
import gitbucket.core.service.{AccountService, SystemSettingsService,RepositoryService}
|
||||||
import gitbucket.core.util.ControlUtil._
|
import gitbucket.core.util.SyntaxSugars._
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util._
|
import gitbucket.core.util._
|
||||||
|
import gitbucket.core.util.JGitUtil._
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import io.github.gitbucket.scalatra.forms._
|
||||||
import org.apache.commons.io.FileUtils
|
|
||||||
import org.json4s._
|
import org.json4s._
|
||||||
import org.scalatra._
|
import org.scalatra._
|
||||||
import org.scalatra.i18n._
|
import org.scalatra.i18n._
|
||||||
import org.scalatra.json._
|
import org.scalatra.json._
|
||||||
|
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
||||||
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
|
import javax.servlet.{FilterChain, ServletRequest, ServletResponse}
|
||||||
import javax.servlet.{FilterChain, ServletResponse, ServletRequest}
|
|
||||||
|
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
|
import net.coobird.thumbnailator.Thumbnails
|
||||||
|
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.eclipse.jgit.lib.ObjectId
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
|
import org.eclipse.jgit.treewalk._
|
||||||
|
import org.apache.commons.io.IOUtils
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides generic features for controller implementations.
|
* Provides generic features for controller implementations.
|
||||||
@@ -28,11 +34,11 @@ abstract class ControllerBase extends ScalatraFilter
|
|||||||
with ClientSideValidationFormSupport with JacksonJsonSupport with I18nSupport with FlashMapSupport with Validations
|
with ClientSideValidationFormSupport with JacksonJsonSupport with I18nSupport with FlashMapSupport with Validations
|
||||||
with SystemSettingsService {
|
with SystemSettingsService {
|
||||||
|
|
||||||
implicit val jsonFormats = DefaultFormats
|
implicit val jsonFormats = gitbucket.core.api.JsonFormat.jsonFormats
|
||||||
|
|
||||||
// TODO Scala 2.11
|
before("/api/v3/*") {
|
||||||
// // Don't set content type via Accept header.
|
contentType = formats("json")
|
||||||
// override def format(implicit request: HttpServletRequest) = ""
|
}
|
||||||
|
|
||||||
override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = try {
|
override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = try {
|
||||||
val httpRequest = request.asInstanceOf[HttpServletRequest]
|
val httpRequest = request.asInstanceOf[HttpServletRequest]
|
||||||
@@ -53,7 +59,7 @@ abstract class ControllerBase extends ScalatraFilter
|
|||||||
// Redirect to dashboard
|
// Redirect to dashboard
|
||||||
httpResponse.sendRedirect(baseUrl + "/")
|
httpResponse.sendRedirect(baseUrl + "/")
|
||||||
}
|
}
|
||||||
} else if(path.startsWith("/git/")){
|
} else if(path.startsWith("/git/") || path.startsWith("/git-lfs/")){
|
||||||
// Git repository
|
// Git repository
|
||||||
chain.doFilter(request, response)
|
chain.doFilter(request, response)
|
||||||
} else {
|
} else {
|
||||||
@@ -141,7 +147,6 @@ abstract class ControllerBase extends ScalatraFilter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Scala 2.11
|
|
||||||
override def url(path: String, params: Iterable[(String, Any)] = Iterable.empty,
|
override def url(path: String, params: Iterable[(String, Any)] = Iterable.empty,
|
||||||
includeContextPath: Boolean = true, includeServletPath: Boolean = true,
|
includeContextPath: Boolean = true, includeServletPath: Boolean = true,
|
||||||
absolutize: Boolean = true, withSessionId: Boolean = true)
|
absolutize: Boolean = true, withSessionId: Boolean = true)
|
||||||
@@ -149,6 +154,18 @@ abstract class ControllerBase extends ScalatraFilter
|
|||||||
if (path.startsWith("http")) path
|
if (path.startsWith("http")) path
|
||||||
else baseUrl + super.url(path, params, false, false, false)
|
else baseUrl + super.url(path, params, false, false, false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extends scalatra-form's trim rule to eliminate CR and LF.
|
||||||
|
*/
|
||||||
|
protected def trim2[T](valueType: SingleValueType[T]): SingleValueType[T] = new SingleValueType[T](){
|
||||||
|
def convert(value: String, messages: Messages): T = valueType.convert(trim(value), messages)
|
||||||
|
|
||||||
|
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Seq[(String, String)] =
|
||||||
|
valueType.validate(name, trim(value), params, messages)
|
||||||
|
|
||||||
|
private def trim(value: String): String = if(value == null) null else value.replaceAll("\r\n", "").trim
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use this method to response the raw data against XSS.
|
* Use this method to response the raw data against XSS.
|
||||||
*/
|
*/
|
||||||
@@ -170,13 +187,55 @@ abstract class ControllerBase extends ScalatraFilter
|
|||||||
case _ => Some(parse(request.body))
|
case _ => Some(parse(request.body))
|
||||||
}).filterNot(_ == JNothing).flatMap(j => Try(j.extract[A]).toOption)
|
}).filterNot(_ == JNothing).flatMap(j => Try(j.extract[A]).toOption)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected def getPathObjectId(git: Git, path: String, revCommit: RevCommit): Option[ObjectId] = {
|
||||||
|
@scala.annotation.tailrec
|
||||||
|
def _getPathObjectId(path: String, walk: TreeWalk): Option[ObjectId] = walk.next match {
|
||||||
|
case true if(walk.getPathString == path) => Some(walk.getObjectId(0))
|
||||||
|
case true => _getPathObjectId(path, walk)
|
||||||
|
case false => None
|
||||||
|
}
|
||||||
|
|
||||||
|
using(new TreeWalk(git.getRepository)){ treeWalk =>
|
||||||
|
treeWalk.addTree(revCommit.getTree)
|
||||||
|
treeWalk.setRecursive(true)
|
||||||
|
_getPathObjectId(path, treeWalk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected def responseRawFile(git: Git, objectId: ObjectId, path: String,
|
||||||
|
repository: RepositoryService.RepositoryInfo): Unit = {
|
||||||
|
JGitUtil.getObjectLoaderFromId(git, objectId){ loader =>
|
||||||
|
contentType = FileUtil.getMimeType(path)
|
||||||
|
|
||||||
|
if(loader.isLarge){
|
||||||
|
response.setContentLength(loader.getSize.toInt)
|
||||||
|
loader.copyTo(response.outputStream)
|
||||||
|
} else {
|
||||||
|
val bytes = loader.getCachedBytes
|
||||||
|
val text = new String(bytes, "UTF-8")
|
||||||
|
|
||||||
|
val attrs = JGitUtil.getLfsObjects(text)
|
||||||
|
if(attrs.nonEmpty) {
|
||||||
|
response.setContentLength(attrs("size").toInt)
|
||||||
|
val oid = attrs("oid").split(":")(1)
|
||||||
|
|
||||||
|
using(new FileInputStream(FileUtil.getLfsFilePath(repository.owner, repository.name, oid))){ in =>
|
||||||
|
IOUtils.copy(in, response.getOutputStream)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
response.setContentLength(loader.getSize.toInt)
|
||||||
|
response.getOutputStream.write(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Context object for the current request.
|
* Context object for the current request.
|
||||||
*/
|
*/
|
||||||
case class Context(settings: SystemSettingsService.SystemSettings, loginAccount: Option[Account], request: HttpServletRequest){
|
case class Context(settings: SystemSettingsService.SystemSettings, loginAccount: Option[Account], request: HttpServletRequest){
|
||||||
|
|
||||||
val path = settings.baseUrl.getOrElse(request.getContextPath)
|
val path = settings.baseUrl.getOrElse(request.getContextPath)
|
||||||
val currentPath = request.getRequestURI.substring(request.getContextPath.length)
|
val currentPath = request.getRequestURI.substring(request.getContextPath.length)
|
||||||
val baseUrl = settings.baseUrl(request)
|
val baseUrl = settings.baseUrl(request)
|
||||||
@@ -188,6 +247,7 @@ case class Context(settings: SystemSettingsService.SystemSettings, loginAccount:
|
|||||||
case agent if agent.contains("Win") => "windows"
|
case agent if agent.contains("Win") => "windows"
|
||||||
case _ => null
|
case _ => null
|
||||||
}
|
}
|
||||||
|
val sidebarCollapse = request.getSession.getAttribute("sidebar-collapse") != null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get object from cache.
|
* Get object from cache.
|
||||||
@@ -221,10 +281,13 @@ trait AccountManagementControllerBase extends ControllerBase {
|
|||||||
} else {
|
} else {
|
||||||
fileId.map { fileId =>
|
fileId.map { fileId =>
|
||||||
val filename = "avatar." + FileUtil.getExtension(session.getAndRemove(Keys.Session.Upload(fileId)).get)
|
val filename = "avatar." + FileUtil.getExtension(session.getAndRemove(Keys.Session.Upload(fileId)).get)
|
||||||
FileUtils.moveFile(
|
val uploadDir = getUserUploadDir(userName)
|
||||||
new java.io.File(getTemporaryDir(session.getId), fileId),
|
if(!uploadDir.exists){
|
||||||
new java.io.File(getUserUploadDir(userName), filename)
|
uploadDir.mkdirs()
|
||||||
)
|
}
|
||||||
|
Thumbnails.of(new java.io.File(getTemporaryDir(session.getId), fileId))
|
||||||
|
.size(324, 324)
|
||||||
|
.toFile(new java.io.File(uploadDir, filename))
|
||||||
updateAvatarImage(userName, Some(filename))
|
updateAvatarImage(userName, Some(filename))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,4 +304,13 @@ trait AccountManagementControllerBase extends ControllerBase {
|
|||||||
.map { _ => "Mail address is already registered." }
|
.map { _ => "Mail address is already registered." }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val allReservedNames = Set("git", "admin", "upload", "api")
|
||||||
|
protected def reservedNames(): Constraint = new Constraint(){
|
||||||
|
override def validate(name: String, value: String, messages: Messages): Option[String] = if(allReservedNames.contains(value)){
|
||||||
|
Some(s"${value} is reserved")
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
import gitbucket.core.dashboard.html
|
import gitbucket.core.dashboard.html
|
||||||
import gitbucket.core.service.{RepositoryService, PullRequestService, AccountService, IssuesService}
|
import gitbucket.core.service._
|
||||||
import gitbucket.core.util.{StringUtil, Keys, UsersAuthenticator}
|
import gitbucket.core.util.{Keys, UsersAuthenticator}
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.service.IssuesService._
|
import gitbucket.core.service.IssuesService._
|
||||||
|
|
||||||
class DashboardController extends DashboardControllerBase
|
class DashboardController extends DashboardControllerBase
|
||||||
with IssuesService with PullRequestService with RepositoryService with AccountService
|
with IssuesService with PullRequestService with RepositoryService with AccountService with CommitsService
|
||||||
with UsersAuthenticator
|
with UsersAuthenticator
|
||||||
|
|
||||||
trait DashboardControllerBase extends ControllerBase {
|
trait DashboardControllerBase extends ControllerBase {
|
||||||
@@ -15,20 +15,7 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
with UsersAuthenticator =>
|
with UsersAuthenticator =>
|
||||||
|
|
||||||
get("/dashboard/issues")(usersOnly {
|
get("/dashboard/issues")(usersOnly {
|
||||||
val q = request.getParameter("q")
|
|
||||||
val account = context.loginAccount.get
|
|
||||||
Option(q).map { q =>
|
|
||||||
val condition = IssueSearchCondition(q, Map[String, Int]())
|
|
||||||
q match {
|
|
||||||
case q if(q.contains("is:pr")) => redirect(s"/dashboard/pulls?q=${StringUtil.urlEncode(q)}")
|
|
||||||
case q if(q.contains(s"author:${account.userName}")) => redirect(s"/dashboard/issues/created_by${condition.toURL}")
|
|
||||||
case q if(q.contains(s"assignee:${account.userName}")) => redirect(s"/dashboard/issues/assigned${condition.toURL}")
|
|
||||||
case q if(q.contains(s"mentions:${account.userName}")) => redirect(s"/dashboard/issues/mentioned${condition.toURL}")
|
|
||||||
case _ => searchIssues("created_by")
|
|
||||||
}
|
|
||||||
} getOrElse {
|
|
||||||
searchIssues("created_by")
|
searchIssues("created_by")
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/dashboard/issues/assigned")(usersOnly {
|
get("/dashboard/issues/assigned")(usersOnly {
|
||||||
@@ -44,20 +31,7 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/dashboard/pulls")(usersOnly {
|
get("/dashboard/pulls")(usersOnly {
|
||||||
val q = request.getParameter("q")
|
|
||||||
val account = context.loginAccount.get
|
|
||||||
Option(q).map { q =>
|
|
||||||
val condition = IssueSearchCondition(q, Map[String, Int]())
|
|
||||||
q match {
|
|
||||||
case q if(q.contains("is:issue")) => redirect(s"/dashboard/issues?q=${StringUtil.urlEncode(q)}")
|
|
||||||
case q if(q.contains(s"author:${account.userName}")) => redirect(s"/dashboard/pulls/created_by${condition.toURL}")
|
|
||||||
case q if(q.contains(s"assignee:${account.userName}")) => redirect(s"/dashboard/pulls/assigned${condition.toURL}")
|
|
||||||
case q if(q.contains(s"mentions:${account.userName}")) => redirect(s"/dashboard/pulls/mentioned${condition.toURL}")
|
|
||||||
case _ => searchPullRequests("created_by")
|
|
||||||
}
|
|
||||||
} getOrElse {
|
|
||||||
searchPullRequests("created_by")
|
searchPullRequests("created_by")
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/dashboard/pulls/created_by")(usersOnly {
|
get("/dashboard/pulls/created_by")(usersOnly {
|
||||||
@@ -73,19 +47,12 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
private def getOrCreateCondition(key: String, filter: String, userName: String) = {
|
private def getOrCreateCondition(key: String, filter: String, userName: String) = {
|
||||||
val condition = session.putAndGet(key, if(request.hasQueryString){
|
val condition = IssueSearchCondition(request)
|
||||||
val q = request.getParameter("q")
|
|
||||||
if(q == null){
|
|
||||||
IssueSearchCondition(request)
|
|
||||||
} else {
|
|
||||||
IssueSearchCondition(q, Map[String, Int]())
|
|
||||||
}
|
|
||||||
} else session.getAs[IssueSearchCondition](key).getOrElse(IssueSearchCondition()))
|
|
||||||
|
|
||||||
filter match {
|
filter match {
|
||||||
case "assigned" => condition.copy(assigned = Some(userName), author = None , mentioned = None)
|
case "assigned" => condition.copy(assigned = Some(Some(userName)), author = None, mentioned = None)
|
||||||
case "mentioned" => condition.copy(assigned = None , author = None , mentioned = Some(userName))
|
case "mentioned" => condition.copy(assigned = None, author = None, mentioned = Some(userName))
|
||||||
case _ => condition.copy(assigned = None , author = Some(userName), mentioned = None)
|
case _ => condition.copy(assigned = None, author = Some(userName), mentioned = None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +61,7 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
val userName = context.loginAccount.get.userName
|
val userName = context.loginAccount.get.userName
|
||||||
val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName)
|
val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName)
|
||||||
val userRepos = getUserRepositories(userName, context.baseUrl, true).map(repo => repo.owner -> repo.name)
|
val userRepos = getUserRepositories(userName, true).map(repo => repo.owner -> repo.name)
|
||||||
val page = IssueSearchCondition.page(request)
|
val page = IssueSearchCondition.page(request)
|
||||||
|
|
||||||
html.issues(
|
html.issues(
|
||||||
@@ -103,12 +70,14 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
countIssue(condition.copy(state = "open" ), false, userRepos: _*),
|
countIssue(condition.copy(state = "open" ), false, userRepos: _*),
|
||||||
countIssue(condition.copy(state = "closed"), false, userRepos: _*),
|
countIssue(condition.copy(state = "closed"), false, userRepos: _*),
|
||||||
filter match {
|
filter match {
|
||||||
case "assigned" => condition.copy(assigned = Some(userName))
|
case "assigned" => condition.copy(assigned = Some(Some(userName)))
|
||||||
case "mentioned" => condition.copy(mentioned = Some(userName))
|
case "mentioned" => condition.copy(mentioned = Some(userName))
|
||||||
case _ => condition.copy(author = Some(userName))
|
case _ => condition.copy(author = Some(userName))
|
||||||
},
|
},
|
||||||
filter,
|
filter,
|
||||||
getGroupNames(userName))
|
getGroupNames(userName),
|
||||||
|
Nil,
|
||||||
|
getUserRepositories(userName, withoutPhysicalInfo = true))
|
||||||
}
|
}
|
||||||
|
|
||||||
private def searchPullRequests(filter: String) = {
|
private def searchPullRequests(filter: String) = {
|
||||||
@@ -126,12 +95,14 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
countIssue(condition.copy(state = "open" ), true, allRepos: _*),
|
countIssue(condition.copy(state = "open" ), true, allRepos: _*),
|
||||||
countIssue(condition.copy(state = "closed"), true, allRepos: _*),
|
countIssue(condition.copy(state = "closed"), true, allRepos: _*),
|
||||||
filter match {
|
filter match {
|
||||||
case "assigned" => condition.copy(assigned = Some(userName))
|
case "assigned" => condition.copy(assigned = Some(Some(userName)))
|
||||||
case "mentioned" => condition.copy(mentioned = Some(userName))
|
case "mentioned" => condition.copy(mentioned = Some(userName))
|
||||||
case _ => condition.copy(author = Some(userName))
|
case _ => condition.copy(author = Some(userName))
|
||||||
},
|
},
|
||||||
filter,
|
filter,
|
||||||
getGroupNames(userName))
|
getGroupNames(userName),
|
||||||
|
Nil,
|
||||||
|
getUserRepositories(userName, withoutPhysicalInfo = true))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,26 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
import gitbucket.core.util.{Keys, FileUtil}
|
import gitbucket.core.model.Account
|
||||||
import gitbucket.core.util.ControlUtil._
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
|
import gitbucket.core.service.{AccountService, RepositoryService}
|
||||||
|
import gitbucket.core.servlet.Database
|
||||||
|
import gitbucket.core.util._
|
||||||
|
import gitbucket.core.util.SyntaxSugars._
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.eclipse.jgit.dircache.DirCache
|
||||||
|
import org.eclipse.jgit.lib.{Constants, FileMode}
|
||||||
import org.scalatra._
|
import org.scalatra._
|
||||||
import org.scalatra.servlet.{MultipartConfig, FileUploadSupport, FileItem}
|
import org.scalatra.servlet.{FileItem, FileUploadSupport, MultipartConfig}
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.{FileUtils, IOUtils}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides Ajax based file upload functionality.
|
* Provides Ajax based file upload functionality.
|
||||||
*
|
*
|
||||||
* This servlet saves uploaded file.
|
* This servlet saves uploaded file.
|
||||||
*/
|
*/
|
||||||
class FileUploadController extends ScalatraServlet with FileUploadSupport {
|
class FileUploadController extends ScalatraServlet with FileUploadSupport with RepositoryService with AccountService {
|
||||||
|
|
||||||
configureMultipartHandling(MultipartConfig(maxFileSize = Some(3 * 1024 * 1024)))
|
configureMultipartHandling(MultipartConfig(maxFileSize = Some(3 * 1024 * 1024)))
|
||||||
|
|
||||||
@@ -23,6 +31,13 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport {
|
|||||||
}, FileUtil.isImage)
|
}, FileUtil.isImage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
post("/tmp"){
|
||||||
|
execute({ (file, fileId) =>
|
||||||
|
FileUtils.writeByteArrayToFile(new java.io.File(getTemporaryDir(session.getId), fileId), file.get)
|
||||||
|
session += Keys.Session.Upload(fileId) -> file.name
|
||||||
|
}, _ => true)
|
||||||
|
}
|
||||||
|
|
||||||
post("/file/:owner/:repository"){
|
post("/file/:owner/:repository"){
|
||||||
execute({ (file, fileId) =>
|
execute({ (file, fileId) =>
|
||||||
FileUtils.writeByteArrayToFile(new java.io.File(
|
FileUtils.writeByteArrayToFile(new java.io.File(
|
||||||
@@ -31,14 +46,75 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport {
|
|||||||
}, FileUtil.isUploadableType)
|
}, FileUtil.isUploadableType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
post("/wiki/:owner/:repository"){
|
||||||
|
// Don't accept not logged-in users
|
||||||
|
session.get(Keys.Session.LoginAccount).collect { case loginAccount: Account =>
|
||||||
|
val owner = params("owner")
|
||||||
|
val repository = params("repository")
|
||||||
|
|
||||||
|
// Check whether logged-in user is collaborator
|
||||||
|
onlyWikiEditable(owner, repository, loginAccount){
|
||||||
|
execute({ (file, fileId) =>
|
||||||
|
val fileName = file.getName
|
||||||
|
LockUtil.lock(s"${owner}/${repository}/wiki") {
|
||||||
|
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||||
|
val builder = DirCache.newInCore.builder()
|
||||||
|
val inserter = git.getRepository.newObjectInserter()
|
||||||
|
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
|
||||||
|
|
||||||
|
if(headId != null){
|
||||||
|
JGitUtil.processTree(git, headId){ (path, tree) =>
|
||||||
|
if(path != fileName){
|
||||||
|
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val bytes = IOUtils.toByteArray(file.getInputStream)
|
||||||
|
builder.add(JGitUtil.createDirCacheEntry(fileName, FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, bytes)))
|
||||||
|
builder.finish()
|
||||||
|
|
||||||
|
val newHeadId = JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
|
||||||
|
Constants.HEAD, loginAccount.userName, loginAccount.mailAddress, s"Uploaded ${fileName}")
|
||||||
|
|
||||||
|
fileName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, FileUtil.isUploadableType)
|
||||||
|
}
|
||||||
|
} getOrElse BadRequest()
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/import") {
|
||||||
|
import JDBCUtil._
|
||||||
|
session.get(Keys.Session.LoginAccount).collect { case loginAccount: Account if loginAccount.isAdmin =>
|
||||||
|
execute({ (file, fileId) =>
|
||||||
|
request2Session(request).conn.importAsSQL(file.getInputStream)
|
||||||
|
}, _ => true)
|
||||||
|
}
|
||||||
|
redirect("/admin/data")
|
||||||
|
}
|
||||||
|
|
||||||
|
private def onlyWikiEditable(owner: String, repository: String, loginAccount: Account)(action: => Any): Any = {
|
||||||
|
implicit val session = Database.getSession(request)
|
||||||
|
getRepository(owner, repository) match {
|
||||||
|
case Some(x) => x.repository.options.wikiOption match {
|
||||||
|
case "ALL" if !x.repository.isPrivate => action
|
||||||
|
case "PUBLIC" if hasGuestRole(owner, repository, Some(loginAccount)) => action
|
||||||
|
case "PRIVATE" if hasDeveloperRole(owner, repository, Some(loginAccount)) => action
|
||||||
|
case _ => BadRequest()
|
||||||
|
}
|
||||||
|
case None => BadRequest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private def execute(f: (FileItem, String) => Unit, mimeTypeChcker: (String) => Boolean) = fileParams.get("file") match {
|
private def execute(f: (FileItem, String) => Unit, mimeTypeChcker: (String) => Boolean) = fileParams.get("file") match {
|
||||||
case Some(file) if(mimeTypeChcker(file.name)) =>
|
case Some(file) if(mimeTypeChcker(file.name)) =>
|
||||||
defining(FileUtil.generateFileId){ fileId =>
|
defining(FileUtil.generateFileId){ fileId =>
|
||||||
f(file, fileId)
|
f(file, fileId)
|
||||||
|
|
||||||
Ok(fileId)
|
Ok(fileId)
|
||||||
}
|
}
|
||||||
case _ => BadRequest
|
case _ => BadRequest()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +1,46 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
import gitbucket.core.api._
|
|
||||||
import gitbucket.core.helper.xml
|
import gitbucket.core.helper.xml
|
||||||
import gitbucket.core.html
|
|
||||||
import gitbucket.core.model.Account
|
import gitbucket.core.model.Account
|
||||||
import gitbucket.core.service.{RepositoryService, ActivityService, AccountService}
|
import gitbucket.core.service._
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util.{LDAPUtil, Keys, UsersAuthenticator}
|
import gitbucket.core.util.SyntaxSugars._
|
||||||
|
import gitbucket.core.util.{Keys, LDAPUtil, ReferrerAuthenticator, UsersAuthenticator}
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import io.github.gitbucket.scalatra.forms._
|
||||||
|
import org.scalatra.Ok
|
||||||
|
|
||||||
|
|
||||||
class IndexController extends IndexControllerBase
|
class IndexController extends IndexControllerBase
|
||||||
with RepositoryService with ActivityService with AccountService with UsersAuthenticator
|
with RepositoryService with ActivityService with AccountService with RepositorySearchService with IssuesService
|
||||||
|
with UsersAuthenticator with ReferrerAuthenticator
|
||||||
|
|
||||||
|
|
||||||
trait IndexControllerBase extends ControllerBase {
|
trait IndexControllerBase extends ControllerBase {
|
||||||
self: RepositoryService with ActivityService with AccountService with UsersAuthenticator =>
|
self: RepositoryService with ActivityService with AccountService with RepositorySearchService
|
||||||
|
with UsersAuthenticator with ReferrerAuthenticator =>
|
||||||
|
|
||||||
case class SignInForm(userName: String, password: String)
|
case class SignInForm(userName: String, password: String)
|
||||||
|
|
||||||
val form = mapping(
|
val signinForm = mapping(
|
||||||
"userName" -> trim(label("Username", text(required))),
|
"userName" -> trim(label("Username", text(required))),
|
||||||
"password" -> trim(label("Password", text(required)))
|
"password" -> trim(label("Password", text(required)))
|
||||||
)(SignInForm.apply)
|
)(SignInForm.apply)
|
||||||
|
|
||||||
|
// val searchForm = mapping(
|
||||||
|
// "query" -> trim(text(required)),
|
||||||
|
// "owner" -> trim(text(required)),
|
||||||
|
// "repository" -> trim(text(required))
|
||||||
|
// )(SearchForm.apply)
|
||||||
|
//
|
||||||
|
// case class SearchForm(query: String, owner: String, repository: String)
|
||||||
|
|
||||||
|
|
||||||
get("/"){
|
get("/"){
|
||||||
val loginAccount = context.loginAccount
|
context.loginAccount.map { account =>
|
||||||
if(loginAccount.isEmpty) {
|
val visibleOwnerSet: Set[String] = Set(account.userName) ++ getGroupsByUserName(account.userName)
|
||||||
html.index(getRecentActivities(),
|
gitbucket.core.html.index(getRecentActivitiesByOwners(visibleOwnerSet), Nil, getUserRepositories(account.userName, withoutPhysicalInfo = true))
|
||||||
getVisibleRepositories(loginAccount, context.baseUrl, withoutPhysicalInfo = true),
|
}.getOrElse {
|
||||||
loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl, withoutPhysicalInfo = true) }.getOrElse(Nil)
|
gitbucket.core.html.index(getRecentActivities(), getVisibleRepositories(None, withoutPhysicalInfo = true), 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)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,13 +49,18 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
if(redirect.isDefined && redirect.get.startsWith("/")){
|
if(redirect.isDefined && redirect.get.startsWith("/")){
|
||||||
flash += Keys.Flash.Redirect -> redirect.get
|
flash += Keys.Flash.Redirect -> redirect.get
|
||||||
}
|
}
|
||||||
html.signin()
|
gitbucket.core.html.signin(flash.get("userName"), flash.get("password"), flash.get("error"))
|
||||||
}
|
}
|
||||||
|
|
||||||
post("/signin", form){ form =>
|
post("/signin", signinForm){ form =>
|
||||||
authenticate(context.settings, form.userName, form.password) match {
|
authenticate(context.settings, form.userName, form.password) match {
|
||||||
case Some(account) => signin(account)
|
case Some(account) => signin(account)
|
||||||
case None => redirect("/signin")
|
case None => {
|
||||||
|
flash += "userName" -> form.userName
|
||||||
|
flash += "password" -> form.password
|
||||||
|
flash += "error" -> "Sorry, your Username and/or Password is incorrect. Please try again."
|
||||||
|
redirect("/signin")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,6 +74,15 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
xml.feed(getRecentActivities())
|
xml.feed(getRecentActivities())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get("/sidebar-collapse"){
|
||||||
|
if(params("collapse") == "true"){
|
||||||
|
session.setAttribute("sidebar-collapse", "true")
|
||||||
|
} else {
|
||||||
|
session.setAttribute("sidebar-collapse", null)
|
||||||
|
}
|
||||||
|
Ok()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set account information into HttpSession and redirect.
|
* Set account information into HttpSession and redirect.
|
||||||
*/
|
*/
|
||||||
@@ -98,25 +110,68 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
*/
|
*/
|
||||||
get("/_user/proposals")(usersOnly {
|
get("/_user/proposals")(usersOnly {
|
||||||
contentType = formats("json")
|
contentType = formats("json")
|
||||||
|
val user = params("user").toBoolean
|
||||||
|
val group = params("group").toBoolean
|
||||||
org.json4s.jackson.Serialization.write(
|
org.json4s.jackson.Serialization.write(
|
||||||
Map("options" -> getAllUsers(false).filter(!_.isGroupAccount).map(_.userName).toArray)
|
Map("options" -> (
|
||||||
|
getAllUsers(false)
|
||||||
|
.withFilter { t => (user, group) match {
|
||||||
|
case (true, true) => true
|
||||||
|
case (true, false) => !t.isGroupAccount
|
||||||
|
case (false, true) => t.isGroupAccount
|
||||||
|
case (false, false) => false
|
||||||
|
}}.map { t => t.userName }
|
||||||
|
))
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSON API for checking user existence.
|
* JSON API for checking user or group existence.
|
||||||
|
* Returns a single string which is any of "group", "user" or "".
|
||||||
*/
|
*/
|
||||||
post("/_user/existence")(usersOnly {
|
post("/_user/existence")(usersOnly {
|
||||||
getAccountByUserName(params("userName")).isDefined
|
getAccountByUserName(params("userName")).map { account =>
|
||||||
|
if(account.isGroupAccount) "group" else "user"
|
||||||
|
} getOrElse ""
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
// TODO Move to RepositoryViwerController?
|
||||||
* @see https://developer.github.com/v3/rate_limit/#get-your-current-rate-limit-status
|
get("/:owner/:repository/search")(referrersOnly { repository =>
|
||||||
* but not enabled.
|
defining(params.getOrElse("q", "").trim, params.getOrElse("type", "code")){ case (query, target) =>
|
||||||
*/
|
val page = try {
|
||||||
get("/api/v3/rate_limit"){
|
val i = params.getOrElse("page", "1").toInt
|
||||||
contentType = formats("json")
|
if(i <= 0) 1 else i
|
||||||
// this message is same as github enterprise...
|
} catch {
|
||||||
org.scalatra.NotFound(ApiError("Rate limiting is not enabled."))
|
case e: NumberFormatException => 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
target.toLowerCase match {
|
||||||
|
case "issue" => gitbucket.core.search.html.issues(
|
||||||
|
if(query.nonEmpty) searchIssues(repository.owner, repository.name, query) else Nil,
|
||||||
|
query, page, repository)
|
||||||
|
|
||||||
|
case "wiki" => gitbucket.core.search.html.wiki(
|
||||||
|
if(query.nonEmpty) searchWikiPages(repository.owner, repository.name, query) else Nil,
|
||||||
|
query, page, repository)
|
||||||
|
|
||||||
|
case _ => gitbucket.core.search.html.code(
|
||||||
|
if(query.nonEmpty) searchFiles(repository.owner, repository.name, query) else Nil,
|
||||||
|
query, page, repository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
get("/search"){
|
||||||
|
val query = params.getOrElse("query", "").trim.toLowerCase
|
||||||
|
val visibleRepositories = getVisibleRepositories(context.loginAccount, repositoryUserName = None, withoutPhysicalInfo = true)
|
||||||
|
val repositories = visibleRepositories.filter { repository =>
|
||||||
|
repository.name.toLowerCase.indexOf(query) >= 0 || repository.owner.toLowerCase.indexOf(query) >= 0
|
||||||
|
}
|
||||||
|
context.loginAccount.map { account =>
|
||||||
|
gitbucket.core.search.html.repositories(query, repositories, Nil, getUserRepositories(account.userName, withoutPhysicalInfo = true))
|
||||||
|
}.getOrElse {
|
||||||
|
gitbucket.core.search.html.repositories(query, repositories, visibleRepositories, Nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,47 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
import gitbucket.core.api._
|
|
||||||
import gitbucket.core.issues.html
|
import gitbucket.core.issues.html
|
||||||
import gitbucket.core.model.Issue
|
|
||||||
import gitbucket.core.service.IssuesService._
|
import gitbucket.core.service.IssuesService._
|
||||||
import gitbucket.core.service._
|
import gitbucket.core.service._
|
||||||
import gitbucket.core.util.ControlUtil._
|
import gitbucket.core.util.SyntaxSugars._
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util._
|
import gitbucket.core.util._
|
||||||
import gitbucket.core.view
|
import gitbucket.core.view
|
||||||
import gitbucket.core.view.Markdown
|
import gitbucket.core.view.Markdown
|
||||||
|
import io.github.gitbucket.scalatra.forms._
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import org.scalatra.{BadRequest, Ok}
|
||||||
import org.scalatra.Ok
|
|
||||||
|
|
||||||
|
|
||||||
class IssuesController extends IssuesControllerBase
|
class IssuesController extends IssuesControllerBase
|
||||||
with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
|
with IssuesService
|
||||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with WebHookIssueCommentService
|
with RepositoryService
|
||||||
|
with AccountService
|
||||||
|
with LabelsService
|
||||||
|
with MilestonesService
|
||||||
|
with ActivityService
|
||||||
|
with HandleCommentService
|
||||||
|
with IssueCreationService
|
||||||
|
with ReadableUsersAuthenticator
|
||||||
|
with ReferrerAuthenticator
|
||||||
|
with WritableUsersAuthenticator
|
||||||
|
with PullRequestService
|
||||||
|
with WebHookIssueCommentService
|
||||||
|
with CommitsService
|
||||||
|
|
||||||
trait IssuesControllerBase extends ControllerBase {
|
trait IssuesControllerBase extends ControllerBase {
|
||||||
self: IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
|
self: IssuesService
|
||||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with WebHookIssueCommentService =>
|
with RepositoryService
|
||||||
|
with AccountService
|
||||||
|
with LabelsService
|
||||||
|
with MilestonesService
|
||||||
|
with ActivityService
|
||||||
|
with HandleCommentService
|
||||||
|
with IssueCreationService
|
||||||
|
with ReadableUsersAuthenticator
|
||||||
|
with ReferrerAuthenticator
|
||||||
|
with WritableUsersAuthenticator
|
||||||
|
with PullRequestService
|
||||||
|
with WebHookIssueCommentService =>
|
||||||
|
|
||||||
case class IssueCreateForm(title: String, content: Option[String],
|
case class IssueCreateForm(title: String, content: Option[String],
|
||||||
assignedUserName: Option[String], milestoneId: Option[Int], labelNames: Option[String])
|
assignedUserName: Option[String], milestoneId: Option[Int], labelNames: Option[String])
|
||||||
@@ -56,7 +76,7 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
get("/:owner/:repository/issues")(referrersOnly { repository =>
|
get("/:owner/:repository/issues")(referrersOnly { repository =>
|
||||||
val q = request.getParameter("q")
|
val q = request.getParameter("q")
|
||||||
if(Option(q).exists(_.contains("is:pr"))){
|
if(Option(q).exists(_.contains("is:pr"))){
|
||||||
redirect(s"/${repository.owner}/${repository.name}/pulls?q=" + StringUtil.urlEncode(q))
|
redirect(s"/${repository.owner}/${repository.name}/pulls?q=${StringUtil.urlEncode(q)}")
|
||||||
} else {
|
} else {
|
||||||
searchIssues(repository)
|
searchIssues(repository)
|
||||||
}
|
}
|
||||||
@@ -64,170 +84,130 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
|
get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
|
||||||
defining(repository.owner, repository.name, params("id")){ case (owner, name, issueId) =>
|
defining(repository.owner, repository.name, params("id")){ case (owner, name, issueId) =>
|
||||||
getIssue(owner, name, issueId) map {
|
getIssue(owner, name, issueId) map { issue =>
|
||||||
|
if(issue.isPullRequest){
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||||
|
} else {
|
||||||
html.issue(
|
html.issue(
|
||||||
_,
|
issue,
|
||||||
getComments(owner, name, issueId.toInt),
|
getComments(owner, name, issueId.toInt),
|
||||||
getIssueLabels(owner, name, issueId.toInt),
|
getIssueLabels(owner, name, issueId.toInt),
|
||||||
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
|
getAssignableUserNames(owner, name),
|
||||||
getMilestonesWithIssueCount(owner, name),
|
getMilestonesWithIssueCount(owner, name),
|
||||||
getLabels(owner, name),
|
getLabels(owner, name),
|
||||||
hasWritePermission(owner, name, context.loginAccount),
|
isIssueEditable(repository),
|
||||||
|
isIssueManageable(repository),
|
||||||
repository)
|
repository)
|
||||||
} getOrElse NotFound
|
|
||||||
}
|
}
|
||||||
})
|
} 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, issue) => ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest) })
|
|
||||||
}).getOrElse(NotFound)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
|
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
|
||||||
|
if(isIssueEditable(repository)){ // TODO Should this check is provided by authenticator?
|
||||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||||
html.create(
|
html.create(
|
||||||
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
|
getAssignableUserNames(owner, name),
|
||||||
getMilestones(owner, name),
|
getMilestones(owner, name),
|
||||||
getLabels(owner, name),
|
getLabels(owner, name),
|
||||||
hasWritePermission(owner, name, context.loginAccount),
|
isIssueManageable(repository),
|
||||||
|
getContentTemplate(repository, "ISSUE_TEMPLATE"),
|
||||||
repository)
|
repository)
|
||||||
}
|
}
|
||||||
|
} else Unauthorized()
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) =>
|
post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) =>
|
||||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
if(isIssueEditable(repository)){ // TODO Should this check is provided by authenticator?
|
||||||
val writable = hasWritePermission(owner, name, context.loginAccount)
|
val issue = createIssue(
|
||||||
val userName = context.loginAccount.get.userName
|
repository,
|
||||||
|
form.title,
|
||||||
|
form.content,
|
||||||
|
form.assignedUserName,
|
||||||
|
form.milestoneId,
|
||||||
|
form.labelNames.toArray.flatMap(_.split(",")),
|
||||||
|
context.loginAccount.get)
|
||||||
|
|
||||||
// insert issue
|
redirect(s"/${issue.userName}/${issue.repositoryName}/issues/${issue.issueId}")
|
||||||
val issueId = createIssue(owner, name, userName, form.title, form.content,
|
} else Unauthorized()
|
||||||
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, issue, 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) =>
|
ajaxPost("/:owner/:repository/issues/edit_title/:id", issueTitleEditForm)(readableUsersOnly { (title, repository) =>
|
||||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||||
getIssue(owner, name, params("id")).map { issue =>
|
getIssue(owner, name, params("id")).map { issue =>
|
||||||
if(isEditable(owner, name, issue.openedUserName)){
|
if(isEditableContent(owner, name, issue.openedUserName)){
|
||||||
// update issue
|
// update issue
|
||||||
updateIssue(owner, name, issue.issueId, title, issue.content)
|
updateIssue(owner, name, issue.issueId, title, issue.content)
|
||||||
// extract references and create refer comment
|
// extract references and create refer comment
|
||||||
createReferComment(owner, name, issue.copy(title = title), title)
|
createReferComment(owner, name, issue.copy(title = title), title, context.loginAccount.get)
|
||||||
|
|
||||||
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
||||||
} else Unauthorized
|
} else Unauthorized()
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (content, repository) =>
|
ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (content, repository) =>
|
||||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||||
getIssue(owner, name, params("id")).map { issue =>
|
getIssue(owner, name, params("id")).map { issue =>
|
||||||
if(isEditable(owner, name, issue.openedUserName)){
|
if(isEditableContent(owner, name, issue.openedUserName)){
|
||||||
// update issue
|
// update issue
|
||||||
updateIssue(owner, name, issue.issueId, issue.title, content)
|
updateIssue(owner, name, issue.issueId, issue.title, content)
|
||||||
// extract references and create refer comment
|
// extract references and create refer comment
|
||||||
createReferComment(owner, name, issue, content.getOrElse(""))
|
createReferComment(owner, name, issue, content.getOrElse(""), context.loginAccount.get)
|
||||||
|
|
||||||
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
||||||
} else Unauthorized
|
} else Unauthorized()
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
|
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
|
||||||
handleComment(form.issueId, Some(form.content), repository)() map { case (issue, id) =>
|
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
|
||||||
|
val actionOpt = params.get("action").filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName))
|
||||||
|
handleComment(issue, Some(form.content), repository, actionOpt) map { case (issue, id) =>
|
||||||
redirect(s"/${repository.owner}/${repository.name}/${
|
redirect(s"/${repository.owner}/${repository.name}/${
|
||||||
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
|
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
|
||||||
} getOrElse NotFound
|
}
|
||||||
})
|
} getOrElse NotFound()
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/issues/comments/#create-a-comment
|
|
||||||
*/
|
|
||||||
post("/api/v3/repos/:owner/:repository/issues/:id/comments")(readableUsersOnly { repository =>
|
|
||||||
(for{
|
|
||||||
issueId <- params("id").toIntOpt
|
|
||||||
body <- extractFromJsonBody[CreateAComment].map(_.body) if ! body.isEmpty
|
|
||||||
(issue, id) <- handleComment(issueId, Some(body), repository)()
|
|
||||||
issueComment <- getComment(repository.owner, repository.name, id.toString())
|
|
||||||
} yield {
|
|
||||||
JsonFormat(ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(context.loginAccount.get), issue.isPullRequest))
|
|
||||||
}) getOrElse NotFound
|
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
|
post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
|
||||||
handleComment(form.issueId, form.content, repository)() map { case (issue, id) =>
|
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
|
||||||
|
val actionOpt = params.get("action").filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName))
|
||||||
|
handleComment(issue, form.content, repository, actionOpt) map { case (issue, id) =>
|
||||||
redirect(s"/${repository.owner}/${repository.name}/${
|
redirect(s"/${repository.owner}/${repository.name}/${
|
||||||
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
|
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
|
||||||
} getOrElse NotFound
|
}
|
||||||
|
} getOrElse NotFound()
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
|
ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
|
||||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||||
getComment(owner, name, params("id")).map { comment =>
|
getComment(owner, name, params("id")).map { comment =>
|
||||||
if(isEditable(owner, name, comment.commentedUserName)){
|
if(isEditableContent(owner, name, comment.commentedUserName)){
|
||||||
updateComment(comment.commentId, form.content)
|
updateComment(comment.commentId, form.content)
|
||||||
redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}")
|
redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}")
|
||||||
} else Unauthorized
|
} else Unauthorized()
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issue_comments/delete/:id")(readableUsersOnly { repository =>
|
ajaxPost("/:owner/:repository/issue_comments/delete/:id")(readableUsersOnly { repository =>
|
||||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||||
getComment(owner, name, params("id")).map { comment =>
|
getComment(owner, name, params("id")).map { comment =>
|
||||||
if(isEditable(owner, name, comment.commentedUserName)){
|
if(isEditableContent(owner, name, comment.commentedUserName)){
|
||||||
Ok(deleteComment(comment.commentId))
|
Ok(deleteComment(comment.commentId))
|
||||||
} else Unauthorized
|
} else Unauthorized()
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository =>
|
ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository =>
|
||||||
getIssue(repository.owner, repository.name, params("id")) map { x =>
|
getIssue(repository.owner, repository.name, params("id")) map { x =>
|
||||||
if(isEditable(x.userName, x.repositoryName, x.openedUserName)){
|
if(isEditableContent(x.userName, x.repositoryName, x.openedUserName)){
|
||||||
params.get("dataType") collect {
|
params.get("dataType") collect {
|
||||||
case t if t == "html" => html.editissue(
|
case t if t == "html" => html.editissue(x.content, x.issueId, repository)
|
||||||
x.content, x.issueId, x.userName, x.repositoryName)
|
|
||||||
} getOrElse {
|
} getOrElse {
|
||||||
contentType = formats("json")
|
contentType = formats("json")
|
||||||
org.json4s.jackson.Serialization.write(
|
org.json4s.jackson.Serialization.write(
|
||||||
@@ -241,21 +221,20 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
enableAnchor = true,
|
enableAnchor = true,
|
||||||
enableLineBreaks = true,
|
enableLineBreaks = true,
|
||||||
enableTaskList = true,
|
enableTaskList = true,
|
||||||
hasWritePermission = isEditable(x.userName, x.repositoryName, x.openedUserName)
|
hasWritePermission = true
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else Unauthorized
|
} else Unauthorized()
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound()
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository =>
|
ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository =>
|
||||||
getComment(repository.owner, repository.name, params("id")) map { x =>
|
getComment(repository.owner, repository.name, params("id")) map { x =>
|
||||||
if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
|
if(isEditableContent(x.userName, x.repositoryName, x.commentedUserName)){
|
||||||
params.get("dataType") collect {
|
params.get("dataType") collect {
|
||||||
case t if t == "html" => html.editcomment(
|
case t if t == "html" => html.editcomment(x.content, x.commentId, repository)
|
||||||
x.content, x.commentId, x.userName, x.repositoryName)
|
|
||||||
} getOrElse {
|
} getOrElse {
|
||||||
contentType = formats("json")
|
contentType = formats("json")
|
||||||
org.json4s.jackson.Serialization.write(
|
org.json4s.jackson.Serialization.write(
|
||||||
@@ -268,71 +247,79 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
enableAnchor = true,
|
enableAnchor = true,
|
||||||
enableLineBreaks = true,
|
enableLineBreaks = true,
|
||||||
enableTaskList = true,
|
enableTaskList = true,
|
||||||
hasWritePermission = isEditable(x.userName, x.repositoryName, x.commentedUserName)
|
hasWritePermission = true
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else Unauthorized
|
} else Unauthorized()
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound()
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/new/label")(collaboratorsOnly { repository =>
|
ajaxPost("/:owner/:repository/issues/new/label")(writableUsersOnly { repository =>
|
||||||
val labelNames = params("labelNames").split(",")
|
val labelNames = params("labelNames").split(",")
|
||||||
val labels = getLabels(repository.owner, repository.name).filter(x => labelNames.contains(x.labelName))
|
val labels = getLabels(repository.owner, repository.name).filter(x => labelNames.contains(x.labelName))
|
||||||
html.labellist(labels)
|
html.labellist(labels)
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/:id/label/new")(collaboratorsOnly { repository =>
|
ajaxPost("/:owner/:repository/issues/:id/label/new")(writableUsersOnly { repository =>
|
||||||
defining(params("id").toInt){ issueId =>
|
defining(params("id").toInt){ issueId =>
|
||||||
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
||||||
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/:id/label/delete")(collaboratorsOnly { repository =>
|
ajaxPost("/:owner/:repository/issues/:id/label/delete")(writableUsersOnly { repository =>
|
||||||
defining(params("id").toInt){ issueId =>
|
defining(params("id").toInt){ issueId =>
|
||||||
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
||||||
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/:id/assign")(collaboratorsOnly { repository =>
|
ajaxPost("/:owner/:repository/issues/:id/assign")(writableUsersOnly { repository =>
|
||||||
updateAssignedUserName(repository.owner, repository.name, params("id").toInt, assignedUserName("assignedUserName"))
|
updateAssignedUserName(repository.owner, repository.name, params("id").toInt, assignedUserName("assignedUserName"))
|
||||||
Ok("updated")
|
Ok("updated")
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/:id/milestone")(collaboratorsOnly { repository =>
|
ajaxPost("/:owner/:repository/issues/:id/milestone")(writableUsersOnly { repository =>
|
||||||
updateMilestoneId(repository.owner, repository.name, params("id").toInt, milestoneId("milestoneId"))
|
updateMilestoneId(repository.owner, repository.name, params("id").toInt, milestoneId("milestoneId"))
|
||||||
milestoneId("milestoneId").map { milestoneId =>
|
milestoneId("milestoneId").map { milestoneId =>
|
||||||
getMilestonesWithIssueCount(repository.owner, repository.name)
|
getMilestonesWithIssueCount(repository.owner, repository.name)
|
||||||
.find(_._1.milestoneId == milestoneId).map { case (_, openCount, closeCount) =>
|
.find(_._1.milestoneId == milestoneId).map { case (_, openCount, closeCount) =>
|
||||||
gitbucket.core.issues.milestones.html.progress(openCount + closeCount, closeCount)
|
gitbucket.core.issues.milestones.html.progress(openCount + closeCount, closeCount)
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound()
|
||||||
} getOrElse Ok()
|
} getOrElse Ok()
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/issues/batchedit/state")(collaboratorsOnly { repository =>
|
post("/:owner/:repository/issues/batchedit/state")(writableUsersOnly { repository =>
|
||||||
defining(params.get("value")){ action =>
|
defining(params.get("value")){ action =>
|
||||||
action match {
|
action match {
|
||||||
case Some("open") => executeBatch(repository) { handleComment(_, None, repository)( _ => Some("reopen")) }
|
case Some("open") => executeBatch(repository) { issueId =>
|
||||||
case Some("close") => executeBatch(repository) { handleComment(_, None, repository)( _ => Some("close")) }
|
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||||
case _ => // TODO BadRequest
|
handleComment(issue, None, repository, Some("reopen"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case Some("close") => executeBatch(repository) { issueId =>
|
||||||
|
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||||
|
handleComment(issue, None, repository, Some("close"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case _ => BadRequest()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/issues/batchedit/label")(collaboratorsOnly { repository =>
|
post("/:owner/:repository/issues/batchedit/label")(writableUsersOnly { repository =>
|
||||||
params("value").toIntOpt.map{ labelId =>
|
params("value").toIntOpt.map{ labelId =>
|
||||||
executeBatch(repository) { issueId =>
|
executeBatch(repository) { issueId =>
|
||||||
getIssueLabel(repository.owner, repository.name, issueId, labelId) getOrElse {
|
getIssueLabel(repository.owner, repository.name, issueId, labelId) getOrElse {
|
||||||
registerIssueLabel(repository.owner, repository.name, issueId, labelId)
|
registerIssueLabel(repository.owner, repository.name, issueId, labelId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound()
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/issues/batchedit/assign")(collaboratorsOnly { repository =>
|
post("/:owner/:repository/issues/batchedit/assign")(writableUsersOnly { repository =>
|
||||||
defining(assignedUserName("value")){ value =>
|
defining(assignedUserName("value")){ value =>
|
||||||
executeBatch(repository) {
|
executeBatch(repository) {
|
||||||
updateAssignedUserName(repository.owner, repository.name, _, value)
|
updateAssignedUserName(repository.owner, repository.name, _, value)
|
||||||
@@ -340,7 +327,7 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/issues/batchedit/milestone")(collaboratorsOnly { repository =>
|
post("/:owner/:repository/issues/batchedit/milestone")(writableUsersOnly { repository =>
|
||||||
defining(milestoneId("value")){ value =>
|
defining(milestoneId("value")){ value =>
|
||||||
executeBatch(repository) {
|
executeBatch(repository) {
|
||||||
updateMilestoneId(repository.owner, repository.name, _, value)
|
updateMilestoneId(repository.owner, repository.name, _, value)
|
||||||
@@ -356,15 +343,12 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
RawData(FileUtil.getMimeType(file.getName), file)
|
RawData(FileUtil.getMimeType(file.getName), file)
|
||||||
}
|
}
|
||||||
case _ => None
|
case _ => None
|
||||||
}) getOrElse NotFound
|
}) getOrElse NotFound()
|
||||||
})
|
})
|
||||||
|
|
||||||
val assignedUserName = (key: String) => params.get(key) filter (_.trim != "")
|
val assignedUserName = (key: String) => params.get(key) filter (_.trim != "")
|
||||||
val milestoneId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
|
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) = {
|
private def executeBatch(repository: RepositoryService.RepositoryInfo)(execute: Int => Unit) = {
|
||||||
params("checked").split(',') map(_.toInt) foreach execute
|
params("checked").split(',') map(_.toInt) foreach execute
|
||||||
params("from") match {
|
params("from") match {
|
||||||
@@ -373,132 +357,33 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Same method exists in PullRequestController. Should it moved to IssueService?
|
|
||||||
private def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String) = {
|
|
||||||
StringUtil.extractIssueId(message).foreach { issueId =>
|
|
||||||
val content = fromIssue.issueId + ":" + fromIssue.title
|
|
||||||
if(getIssue(owner, repository, issueId).isDefined){
|
|
||||||
// Not add if refer comment already exist.
|
|
||||||
if(!getComments(owner, repository, issueId.toInt).exists { x => x.action == "refer" && x.content == content }) {
|
|
||||||
createComment(owner, repository, context.loginAccount.get.userName, issueId.toInt, content, "refer")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see [[https://github.com/takezoe/gitbucket/wiki/CommentAction]]
|
|
||||||
*/
|
|
||||||
private def handleComment(issueId: Int, content: Option[String], repository: RepositoryService.RepositoryInfo)
|
|
||||||
(getAction: Issue => Option[String] =
|
|
||||||
p1 => params.get("action").filter(_ => isEditable(p1.userName, p1.repositoryName, p1.openedUserName))) = {
|
|
||||||
|
|
||||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
|
||||||
val userName = context.loginAccount.get.userName
|
|
||||||
|
|
||||||
getIssue(owner, name, issueId.toString) flatMap { issue =>
|
|
||||||
val (action, recordActivity) =
|
|
||||||
getAction(issue)
|
|
||||||
.collect {
|
|
||||||
case "close" if(!issue.closed) => true ->
|
|
||||||
(Some("close") -> Some(if(issue.isPullRequest) recordClosePullRequestActivity _ else recordCloseIssueActivity _))
|
|
||||||
case "reopen" if(issue.closed) => false ->
|
|
||||||
(Some("reopen") -> Some(recordReopenIssueActivity _))
|
|
||||||
}
|
|
||||||
.map { case (closed, t) =>
|
|
||||||
updateClosed(owner, name, issueId, closed)
|
|
||||||
t
|
|
||||||
}
|
|
||||||
.getOrElse(None -> None)
|
|
||||||
|
|
||||||
val commentId = (content, action) match {
|
|
||||||
case (None, None) => None
|
|
||||||
case (None, Some(action)) => Some(createComment(owner, name, userName, issueId, action.capitalize, action))
|
|
||||||
case (Some(content), _) => Some(createComment(owner, name, userName, issueId, content, action.map(_+ "_comment").getOrElse("comment")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// record comment activity if comment is entered
|
|
||||||
content foreach {
|
|
||||||
(if(issue.isPullRequest) recordCommentPullRequestActivity _ else recordCommentIssueActivity _)
|
|
||||||
(owner, name, userName, issueId, _)
|
|
||||||
}
|
|
||||||
recordActivity foreach ( _ (owner, name, userName, issueId, issue.title) )
|
|
||||||
|
|
||||||
// extract references and create refer comment
|
|
||||||
content.map { content =>
|
|
||||||
createReferComment(owner, name, issue, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
// call web hooks
|
|
||||||
action match {
|
|
||||||
case None => commentId.map{ commentIdSome => callIssueCommentWebHook(repository, issue, commentIdSome, context.loginAccount.get) }
|
|
||||||
case Some(act) => val webHookAction = act match {
|
|
||||||
case "open" => "opened"
|
|
||||||
case "reopen" => "reopened"
|
|
||||||
case "close" => "closed"
|
|
||||||
case _ => act
|
|
||||||
}
|
|
||||||
if(issue.isPullRequest){
|
|
||||||
callPullRequestWebHook(webHookAction, repository, issue.issueId, context.baseUrl, context.loginAccount.get)
|
|
||||||
} else {
|
|
||||||
callIssuesWebHook(webHookAction, repository, issue, context.baseUrl, context.loginAccount.get)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// notifications
|
|
||||||
Notifier() match {
|
|
||||||
case f =>
|
|
||||||
content foreach {
|
|
||||||
f.toNotify(repository, issue, _){
|
|
||||||
Notifier.msgComment(s"${context.baseUrl}/${owner}/${name}/${
|
|
||||||
if(issue.isPullRequest) "pull" else "issues"}/${issueId}#comment-${commentId.get}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
action foreach {
|
|
||||||
f.toNotify(repository, issue, _){
|
|
||||||
Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
commentId.map( issue -> _ )
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def searchIssues(repository: RepositoryService.RepositoryInfo) = {
|
private def searchIssues(repository: RepositoryService.RepositoryInfo) = {
|
||||||
defining(repository.owner, repository.name){ case (owner, repoName) =>
|
defining(repository.owner, repository.name){ case (owner, repoName) =>
|
||||||
val page = IssueSearchCondition.page(request)
|
val page = IssueSearchCondition.page(request)
|
||||||
val sessionKey = Keys.Session.Issues(owner, repoName)
|
|
||||||
|
|
||||||
// retrieve search condition
|
// retrieve search condition
|
||||||
val condition = session.putAndGet(sessionKey,
|
val condition = IssueSearchCondition(request)
|
||||||
if(request.hasQueryString){
|
|
||||||
val q = request.getParameter("q")
|
|
||||||
if(q == null || q.trim.isEmpty){
|
|
||||||
IssueSearchCondition(request)
|
|
||||||
} else {
|
|
||||||
IssueSearchCondition(q, getMilestones(owner, repoName).map(x => (x.title, x.milestoneId)).toMap)
|
|
||||||
}
|
|
||||||
} else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
|
|
||||||
)
|
|
||||||
|
|
||||||
html.list(
|
html.list(
|
||||||
"issues",
|
"issues",
|
||||||
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
|
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
|
||||||
page,
|
page,
|
||||||
if(!getAccountByUserName(owner).exists(_.isGroupAccount)){
|
getAssignableUserNames(owner, repoName),
|
||||||
(getCollaborators(owner, repoName) :+ owner).sorted
|
|
||||||
} else {
|
|
||||||
getCollaborators(owner, repoName)
|
|
||||||
},
|
|
||||||
getMilestones(owner, repoName),
|
getMilestones(owner, repoName),
|
||||||
getLabels(owner, repoName),
|
getLabels(owner, repoName),
|
||||||
countIssue(condition.copy(state = "open" ), false, owner -> repoName),
|
countIssue(condition.copy(state = "open" ), false, owner -> repoName),
|
||||||
countIssue(condition.copy(state = "closed"), false, owner -> repoName),
|
countIssue(condition.copy(state = "closed"), false, owner -> repoName),
|
||||||
condition,
|
condition,
|
||||||
repository,
|
repository,
|
||||||
hasWritePermission(owner, repoName, context.loginAccount))
|
isIssueEditable(repository),
|
||||||
|
isIssueManageable(repository))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests whether an issue or a comment is editable by a logged-in user.
|
||||||
|
*/
|
||||||
|
private def isEditableContent(owner: String, repository: String, author: String)(implicit context: Context): Boolean = {
|
||||||
|
hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,66 +2,67 @@ package gitbucket.core.controller
|
|||||||
|
|
||||||
import gitbucket.core.issues.labels.html
|
import gitbucket.core.issues.labels.html
|
||||||
import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, LabelsService}
|
import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, LabelsService}
|
||||||
import gitbucket.core.util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
|
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import io.github.gitbucket.scalatra.forms._
|
||||||
import org.scalatra.i18n.Messages
|
import org.scalatra.i18n.Messages
|
||||||
import org.scalatra.Ok
|
import org.scalatra.Ok
|
||||||
|
|
||||||
class LabelsController extends LabelsControllerBase
|
class LabelsController extends LabelsControllerBase
|
||||||
with LabelsService with IssuesService with RepositoryService with AccountService
|
with LabelsService with IssuesService with RepositoryService with AccountService
|
||||||
with ReferrerAuthenticator with CollaboratorsAuthenticator
|
with ReferrerAuthenticator with WritableUsersAuthenticator
|
||||||
|
|
||||||
trait LabelsControllerBase extends ControllerBase {
|
trait LabelsControllerBase extends ControllerBase {
|
||||||
self: LabelsService with IssuesService with RepositoryService
|
self: LabelsService with IssuesService with RepositoryService
|
||||||
with ReferrerAuthenticator with CollaboratorsAuthenticator =>
|
with ReferrerAuthenticator with WritableUsersAuthenticator =>
|
||||||
|
|
||||||
case class LabelForm(labelName: String, color: String)
|
case class LabelForm(labelName: String, color: String)
|
||||||
|
|
||||||
val labelForm = mapping(
|
val labelForm = mapping(
|
||||||
"labelName" -> trim(label("Label name", text(required, labelName, maxlength(100)))),
|
"labelName" -> trim(label("Label name", text(required, labelName, uniqueLabelName, maxlength(100)))),
|
||||||
"labelColor" -> trim(label("Color", text(required, color)))
|
"labelColor" -> trim(label("Color", text(required, color)))
|
||||||
)(LabelForm.apply)
|
)(LabelForm.apply)
|
||||||
|
|
||||||
|
|
||||||
get("/:owner/:repository/issues/labels")(referrersOnly { repository =>
|
get("/:owner/:repository/issues/labels")(referrersOnly { repository =>
|
||||||
html.list(
|
html.list(
|
||||||
getLabels(repository.owner, repository.name),
|
getLabels(repository.owner, repository.name),
|
||||||
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
||||||
repository,
|
repository,
|
||||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxGet("/:owner/:repository/issues/labels/new")(collaboratorsOnly { repository =>
|
ajaxGet("/:owner/:repository/issues/labels/new")(writableUsersOnly { repository =>
|
||||||
html.edit(None, repository)
|
html.edit(None, repository)
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/labels/new", labelForm)(collaboratorsOnly { (form, repository) =>
|
ajaxPost("/:owner/:repository/issues/labels/new", labelForm)(writableUsersOnly { (form, repository) =>
|
||||||
val labelId = createLabel(repository.owner, repository.name, form.labelName, form.color.substring(1))
|
val labelId = createLabel(repository.owner, repository.name, form.labelName, form.color.substring(1))
|
||||||
html.label(
|
html.label(
|
||||||
getLabel(repository.owner, repository.name, labelId).get,
|
getLabel(repository.owner, repository.name, labelId).get,
|
||||||
// TODO futility
|
// TODO futility
|
||||||
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
||||||
repository,
|
repository,
|
||||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxGet("/:owner/:repository/issues/labels/:labelId/edit")(collaboratorsOnly { repository =>
|
ajaxGet("/:owner/:repository/issues/labels/:labelId/edit")(writableUsersOnly { repository =>
|
||||||
getLabel(repository.owner, repository.name, params("labelId").toInt).map { label =>
|
getLabel(repository.owner, repository.name, params("labelId").toInt).map { label =>
|
||||||
html.edit(Some(label), repository)
|
html.edit(Some(label), repository)
|
||||||
} getOrElse NotFound()
|
} getOrElse NotFound()
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/labels/:labelId/edit", labelForm)(collaboratorsOnly { (form, repository) =>
|
ajaxPost("/:owner/:repository/issues/labels/:labelId/edit", labelForm)(writableUsersOnly { (form, repository) =>
|
||||||
updateLabel(repository.owner, repository.name, params("labelId").toInt, form.labelName, form.color.substring(1))
|
updateLabel(repository.owner, repository.name, params("labelId").toInt, form.labelName, form.color.substring(1))
|
||||||
html.label(
|
html.label(
|
||||||
getLabel(repository.owner, repository.name, params("labelId").toInt).get,
|
getLabel(repository.owner, repository.name, params("labelId").toInt).get,
|
||||||
// TODO futility
|
// TODO futility
|
||||||
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
||||||
repository,
|
repository,
|
||||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/labels/:labelId/delete")(collaboratorsOnly { repository =>
|
ajaxPost("/:owner/:repository/issues/labels/:labelId/delete")(writableUsersOnly { repository =>
|
||||||
deleteLabel(repository.owner, repository.name, params("labelId").toInt)
|
deleteLabel(repository.owner, repository.name, params("labelId").toInt)
|
||||||
Ok()
|
Ok()
|
||||||
})
|
})
|
||||||
@@ -80,4 +81,16 @@ trait LabelsControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def uniqueLabelName: Constraint = new Constraint(){
|
||||||
|
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] = {
|
||||||
|
val owner = params("owner")
|
||||||
|
val repository = params("repository")
|
||||||
|
params.get("labelId").map { labelId =>
|
||||||
|
getLabel(owner, repository, value).filter(_.labelId != labelId.toInt).map(_ => "Name has already been taken.")
|
||||||
|
}.getOrElse {
|
||||||
|
getLabel(owner, repository, value).map(_ => "Name has already been taken.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ package gitbucket.core.controller
|
|||||||
|
|
||||||
import gitbucket.core.issues.milestones.html
|
import gitbucket.core.issues.milestones.html
|
||||||
import gitbucket.core.service.{RepositoryService, MilestonesService, AccountService}
|
import gitbucket.core.service.{RepositoryService, MilestonesService, AccountService}
|
||||||
import gitbucket.core.util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
|
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import io.github.gitbucket.scalatra.forms._
|
||||||
|
|
||||||
class MilestonesController extends MilestonesControllerBase
|
class MilestonesController extends MilestonesControllerBase
|
||||||
with MilestonesService with RepositoryService with AccountService
|
with MilestonesService with RepositoryService with AccountService
|
||||||
with ReferrerAuthenticator with CollaboratorsAuthenticator
|
with ReferrerAuthenticator with WritableUsersAuthenticator
|
||||||
|
|
||||||
trait MilestonesControllerBase extends ControllerBase {
|
trait MilestonesControllerBase extends ControllerBase {
|
||||||
self: MilestonesService with RepositoryService
|
self: MilestonesService with RepositoryService
|
||||||
with ReferrerAuthenticator with CollaboratorsAuthenticator =>
|
with ReferrerAuthenticator with WritableUsersAuthenticator =>
|
||||||
|
|
||||||
case class MilestoneForm(title: String, description: Option[String], dueDate: Option[java.util.Date])
|
case class MilestoneForm(title: String, description: Option[String], dueDate: Option[java.util.Date])
|
||||||
|
|
||||||
@@ -27,58 +27,58 @@ trait MilestonesControllerBase extends ControllerBase {
|
|||||||
params.getOrElse("state", "open"),
|
params.getOrElse("state", "open"),
|
||||||
getMilestonesWithIssueCount(repository.owner, repository.name),
|
getMilestonesWithIssueCount(repository.owner, repository.name),
|
||||||
repository,
|
repository,
|
||||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/issues/milestones/new")(collaboratorsOnly {
|
get("/:owner/:repository/issues/milestones/new")(writableUsersOnly {
|
||||||
html.edit(None, _)
|
html.edit(None, _)
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/issues/milestones/new", milestoneForm)(collaboratorsOnly { (form, repository) =>
|
post("/:owner/:repository/issues/milestones/new", milestoneForm)(writableUsersOnly { (form, repository) =>
|
||||||
createMilestone(repository.owner, repository.name, form.title, form.description, form.dueDate)
|
createMilestone(repository.owner, repository.name, form.title, form.description, form.dueDate)
|
||||||
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
|
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/issues/milestones/:milestoneId/edit")(collaboratorsOnly { repository =>
|
get("/:owner/:repository/issues/milestones/:milestoneId/edit")(writableUsersOnly { repository =>
|
||||||
params("milestoneId").toIntOpt.map{ milestoneId =>
|
params("milestoneId").toIntOpt.map{ milestoneId =>
|
||||||
html.edit(getMilestone(repository.owner, repository.name, milestoneId), repository)
|
html.edit(getMilestone(repository.owner, repository.name, milestoneId), repository)
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound()
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/issues/milestones/:milestoneId/edit", milestoneForm)(collaboratorsOnly { (form, repository) =>
|
post("/:owner/:repository/issues/milestones/:milestoneId/edit", milestoneForm)(writableUsersOnly { (form, repository) =>
|
||||||
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
|
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
|
||||||
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
|
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
|
||||||
updateMilestone(milestone.copy(title = form.title, description = form.description, dueDate = form.dueDate))
|
updateMilestone(milestone.copy(title = form.title, description = form.description, dueDate = form.dueDate))
|
||||||
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
|
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
|
||||||
}
|
}
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound()
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/issues/milestones/:milestoneId/close")(collaboratorsOnly { repository =>
|
get("/:owner/:repository/issues/milestones/:milestoneId/close")(writableUsersOnly { repository =>
|
||||||
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
|
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
|
||||||
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
|
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
|
||||||
closeMilestone(milestone)
|
closeMilestone(milestone)
|
||||||
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
|
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
|
||||||
}
|
}
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound()
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/issues/milestones/:milestoneId/open")(collaboratorsOnly { repository =>
|
get("/:owner/:repository/issues/milestones/:milestoneId/open")(writableUsersOnly { repository =>
|
||||||
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
|
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
|
||||||
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
|
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
|
||||||
openMilestone(milestone)
|
openMilestone(milestone)
|
||||||
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
|
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
|
||||||
}
|
}
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound()
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/issues/milestones/:milestoneId/delete")(collaboratorsOnly { repository =>
|
get("/:owner/:repository/issues/milestones/:milestoneId/delete")(writableUsersOnly { repository =>
|
||||||
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
|
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
|
||||||
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
|
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
|
||||||
deleteMilestone(repository.owner, repository.name, milestone.milestoneId)
|
deleteMilestone(repository.owner, repository.name, milestone.milestoneId)
|
||||||
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
|
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
|
||||||
}
|
}
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound()
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
package gitbucket.core.controller
|
|
||||||
|
|
||||||
import gitbucket.core.admin.plugins.html
|
|
||||||
import gitbucket.core.plugin.PluginRegistry
|
|
||||||
import gitbucket.core.util.AdminAuthenticator
|
|
||||||
|
|
||||||
class PluginsController extends ControllerBase with AdminAuthenticator {
|
|
||||||
get("/admin/plugins")(adminOnly {
|
|
||||||
html.plugins(PluginRegistry().getPlugins())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user