mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-08 23:16:48 +02:00
Compare commits
2383 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1c05eb961 | ||
|
|
b25f97a96f | ||
|
|
1c0f99bd64 | ||
|
|
800d48d6d2 | ||
|
|
02a8540875 | ||
|
|
048cdfc050 | ||
|
|
13f40d2b59 | ||
|
|
fe2920a08f | ||
|
|
f59beded21 | ||
|
|
c75119badf | ||
|
|
7b1292a9af | ||
|
|
9ae26800c8 | ||
|
|
1eb8c83061 | ||
|
|
a0069fde57 | ||
|
|
ca5b121272 | ||
|
|
6f4c081f10 | ||
|
|
227ee12e4c | ||
|
|
fc1cfe3f55 | ||
|
|
76fa5fb474 | ||
|
|
3c847bf957 | ||
|
|
88427b893c | ||
|
|
737b0a9bdf | ||
|
|
c8a7ef2fdb | ||
|
|
6d6331bbf3 | ||
|
|
d1f2a72f06 | ||
|
|
576daa0669 | ||
|
|
934d1df991 | ||
|
|
aa7db68e68 | ||
|
|
a1bb667ec4 | ||
|
|
90ae489d35 | ||
|
|
5bbd4f533f | ||
|
|
283baaed57 | ||
|
|
c1e2191120 | ||
|
|
28b0ea7f88 | ||
|
|
95acb8593a | ||
|
|
17d682f83b | ||
|
|
952c916e33 | ||
|
|
3793a51e3f | ||
|
|
c8b6eb1bd9 | ||
|
|
e46f1b0efc | ||
|
|
395d6e4292 | ||
|
|
770b49cc55 | ||
|
|
95cd6b6e90 | ||
|
|
1f79ed95c2 | ||
|
|
9aef90d214 | ||
|
|
f4d1c72f08 | ||
|
|
5d5dcad32c | ||
|
|
0d195a2e90 | ||
|
|
eb76db5f2c | ||
|
|
9cc5d0ea17 | ||
|
|
1d98308a21 | ||
|
|
1ceace5539 | ||
|
|
f13a473b4e | ||
|
|
4e7c10c0dc | ||
|
|
6db34cbb6b | ||
|
|
10205a8f9b | ||
|
|
d6df35f072 | ||
|
|
ab10b77c50 | ||
|
|
fb34b0909e | ||
|
|
1a869f47e0 | ||
|
|
d9aebbda62 | ||
|
|
987407909e | ||
|
|
ba9c780602 | ||
|
|
ea5834f236 | ||
|
|
c3400f1091 | ||
|
|
7bd4d0970e | ||
|
|
4a9303d7a7 | ||
|
|
5f0cacd7c1 | ||
|
|
f075132878 | ||
|
|
b72556c007 | ||
|
|
47489d9cb1 | ||
|
|
2ee70dc1b2 | ||
|
|
3400b9a0ab | ||
|
|
ad054d2f80 | ||
|
|
b0c2e5588c | ||
|
|
1fe379111c | ||
|
|
2180e31d13 | ||
|
|
275772ad00 | ||
|
|
e80da63515 | ||
|
|
71cce5b470 | ||
|
|
bb188ec948 | ||
|
|
281522fc88 | ||
|
|
a045fc6ae4 | ||
|
|
8e8e794574 | ||
|
|
735e425984 | ||
|
|
5f47b126e3 | ||
|
|
33d82beb72 | ||
|
|
3de5d806b5 | ||
|
|
8eb522fb38 | ||
|
|
370e4339f3 | ||
|
|
5b0eb7ece5 | ||
|
|
18434854d8 | ||
|
|
d3f57bdb45 | ||
|
|
37734ce26b | ||
|
|
b6cf080822 | ||
|
|
bbc817d86d | ||
|
|
5e88f3f787 | ||
|
|
f64d4843f3 | ||
|
|
bcb3450e2b | ||
|
|
c607045b7c | ||
|
|
f8e9093273 | ||
|
|
40c06417e5 | ||
|
|
c3c5535022 | ||
|
|
b7fc76d932 | ||
|
|
c8d666baba | ||
|
|
a64741011c | ||
|
|
ae9ee4779f | ||
|
|
5fd2d61861 | ||
|
|
939c9156ad | ||
|
|
d17aed2357 | ||
|
|
13382b47d1 | ||
|
|
5e5a1ea5a8 | ||
|
|
cf6d1ea137 | ||
|
|
f735e4a133 | ||
|
|
86b67863f8 | ||
|
|
718582af44 | ||
|
|
23024cacaa | ||
|
|
f62cf409eb | ||
|
|
47845dfe1b | ||
|
|
b7bb6b0787 | ||
|
|
ea41786f8c | ||
|
|
962ae2130e | ||
|
|
90ea05f2a1 | ||
|
|
f8bda516d6 | ||
|
|
378c031ecb | ||
|
|
9a5db80dea | ||
|
|
992eb0ceda | ||
|
|
39e1ac2398 | ||
|
|
d1c77de5a0 | ||
|
|
3f8069638c | ||
|
|
d62fc1185c | ||
|
|
768706e1d1 | ||
|
|
8cc9771237 | ||
|
|
8df30ef01b | ||
|
|
dd2e5bfedf | ||
|
|
e3c7eb092f | ||
|
|
5b3c3e2e7c | ||
|
|
0e04925b6b | ||
|
|
9a127256f3 | ||
|
|
1033122fec | ||
|
|
847f96d537 | ||
|
|
70f40846bb | ||
|
|
3a540aa660 | ||
|
|
1adc9b3223 | ||
|
|
0309496df6 | ||
|
|
f83ecac7ae | ||
|
|
cd4d75e35e | ||
|
|
eb61bc50d6 | ||
|
|
4bbb22f73b | ||
|
|
fcb374c5c2 | ||
|
|
a03d1c97c2 | ||
|
|
2d58b7f2d7 | ||
|
|
332a1b4b0b | ||
|
|
6bd58b0c45 | ||
|
|
fb175df851 | ||
|
|
b41aad92f2 | ||
|
|
aabae2ef7f | ||
|
|
0c3d1fd86d | ||
|
|
adba849ec5 | ||
|
|
8539486c6e | ||
|
|
86f4b41beb | ||
|
|
aa54eff3d6 | ||
|
|
27ab21c9a7 | ||
|
|
557ed827d0 | ||
|
|
9cc466a727 | ||
|
|
9a9be12324 | ||
|
|
8e91b9f0b5 | ||
|
|
2862ceb5ad | ||
|
|
d157426d66 | ||
|
|
58635674cb | ||
|
|
f6a048e0f7 | ||
|
|
c4dc1d7334 | ||
|
|
efd5a64749 | ||
|
|
13800a7023 | ||
|
|
43d19d7d52 | ||
|
|
8a8278906a | ||
|
|
d15b3fb2f6 | ||
|
|
bcd92916ca | ||
|
|
810cbda123 | ||
|
|
fee7cebdf1 | ||
|
|
28105d6d3a | ||
|
|
1673832607 | ||
|
|
298e43e612 | ||
|
|
00b88d6b6e | ||
|
|
735123b93e | ||
|
|
fce3b3749c | ||
|
|
0a12b82b48 | ||
|
|
9061d6bf7f | ||
|
|
9ed8b554f3 | ||
|
|
e306303cc8 | ||
|
|
c4bea091fe | ||
|
|
2b383d79f1 | ||
|
|
788e90469c | ||
|
|
f37711c816 | ||
|
|
f2c9d99f30 | ||
|
|
6073497e5e | ||
|
|
5d2ccfb0df | ||
|
|
3745243078 | ||
|
|
30a1968793 | ||
|
|
581bcb3dc8 | ||
|
|
cd243f910a | ||
|
|
0b420177c4 | ||
|
|
0d8e022a0d | ||
|
|
2f87d30359 | ||
|
|
98a5263a07 | ||
|
|
208f08c552 | ||
|
|
b66852ec28 | ||
|
|
8d687660a9 | ||
|
|
c7749b281f | ||
|
|
ad5a0bb442 | ||
|
|
6056642f69 | ||
|
|
21dcbf20b4 | ||
|
|
d92f0080ff | ||
|
|
035cb170e0 | ||
|
|
de5b2a9704 | ||
|
|
55f4b8c124 | ||
|
|
887baf2f08 | ||
|
|
bd63e1e75e | ||
|
|
bc8dd4b3c2 | ||
|
|
15b348fd3d | ||
|
|
5b5a644baa | ||
|
|
fa2d7db0ca | ||
|
|
1893c212f3 | ||
|
|
3c2dcb7b08 | ||
|
|
3d95679a1d | ||
|
|
29f380efa0 | ||
|
|
1da17940a2 | ||
|
|
348eada5b3 | ||
|
|
6f9450fece | ||
|
|
2344ef7583 | ||
|
|
b7b1befb27 | ||
|
|
902f7ef95f | ||
|
|
6bf71827f0 | ||
|
|
5a005cf5a6 | ||
|
|
25729e3193 | ||
|
|
450b598f1f | ||
|
|
f36bcef50c | ||
|
|
86ff842eb2 | ||
|
|
94ca597cf8 | ||
|
|
e46e55f985 | ||
|
|
50a63a8c87 | ||
|
|
029d1a3a11 | ||
|
|
6df1b005bf | ||
|
|
e50082a9dd | ||
|
|
9c4beca998 | ||
|
|
d73cb094b6 | ||
|
|
9e83882c6f | ||
|
|
d109ac0327 | ||
|
|
695fda4a73 | ||
|
|
439d51bec1 | ||
|
|
c3b89c96e0 | ||
|
|
eb83c5713c | ||
|
|
58c22274ef | ||
|
|
bfd8c3a958 | ||
|
|
f402587a9a | ||
|
|
7736747d68 | ||
|
|
e6ee55e0a0 | ||
|
|
a2e8d24fdb | ||
|
|
9fb0d7eb40 | ||
|
|
24d4763fc8 | ||
|
|
e14a67e56d | ||
|
|
d8fac332ab | ||
|
|
c3ae06751e | ||
|
|
63ab1e3566 | ||
|
|
c552b922b3 | ||
|
|
d26f16ebdc | ||
|
|
cf18550a2c | ||
|
|
8d2d3571b8 | ||
|
|
d2ac5aa0bf | ||
|
|
f1ae6784f5 | ||
|
|
8a6448c64f | ||
|
|
98ccd4b1d4 | ||
|
|
71b4a313e2 | ||
|
|
1bf6939fc3 | ||
|
|
0000949966 | ||
|
|
f767e621a4 | ||
|
|
d40c8ff6eb | ||
|
|
e6838d8891 | ||
|
|
440dd0386b | ||
|
|
37b181c5d0 | ||
|
|
5e7afa0f41 | ||
|
|
badc9b5117 | ||
|
|
5257c4fc2c | ||
|
|
f47e389a9b | ||
|
|
8327333305 | ||
|
|
4bd05835a5 | ||
|
|
a51b57af2a | ||
|
|
17ff024166 | ||
|
|
ed0c8e3f2c | ||
|
|
6b762b0693 | ||
|
|
62e6d0d6e8 | ||
|
|
6947f57bd8 | ||
|
|
08295afb51 | ||
|
|
8d5b494785 | ||
|
|
8d52fc06ed | ||
|
|
1a9982446f | ||
|
|
85b83af73f | ||
|
|
d99898e191 | ||
|
|
2044f5b838 | ||
|
|
bb804f6597 | ||
|
|
407c742596 | ||
|
|
4d3756ac0a | ||
|
|
0739b3048b | ||
|
|
ff5a05511d | ||
|
|
186ce769a2 | ||
|
|
848c698491 | ||
|
|
41ea2087d1 | ||
|
|
1d77727867 | ||
|
|
051d059e5c | ||
|
|
324107beef | ||
|
|
801d71b6d2 | ||
|
|
106f7a41d8 | ||
|
|
1b46651c32 | ||
|
|
459b25e075 | ||
|
|
e3c3a61f0b | ||
|
|
a311ee5ef5 | ||
|
|
b13decf0e9 | ||
|
|
f6b92ef40b | ||
|
|
919b1d01e3 | ||
|
|
fe73c11611 | ||
|
|
c8af6c4b5a | ||
|
|
5d2d36dccf | ||
|
|
a11f711778 | ||
|
|
6920704caa | ||
|
|
451a6ef359 | ||
|
|
a93f4cc780 | ||
|
|
f9fda26e7a | ||
|
|
7435902b70 | ||
|
|
feb57c97b9 | ||
|
|
7021942a6e | ||
|
|
9251d64de8 | ||
|
|
d0c99727e9 | ||
|
|
1fbfcfb446 | ||
|
|
38328b2ffe | ||
|
|
3cce4e5308 | ||
|
|
757292d670 | ||
|
|
ef16804b49 | ||
|
|
dafabb6278 | ||
|
|
1f37362da4 | ||
|
|
1dba28d153 | ||
|
|
e83b017ef2 | ||
|
|
eeabbfd599 | ||
|
|
55a8602bba | ||
|
|
2e80e3baaf | ||
|
|
3134bb0428 | ||
|
|
134b5df6a2 | ||
|
|
aab84d9275 | ||
|
|
5209c29d0f | ||
|
|
cc975f8ffa | ||
|
|
125040f90b | ||
|
|
7009aaeb24 | ||
|
|
a135c6977f | ||
|
|
55991a6f17 | ||
|
|
17e1de6174 | ||
|
|
12a58f393e | ||
|
|
ebb57a80e3 | ||
|
|
7a53bd8766 | ||
|
|
d8b46b194d | ||
|
|
5db7d863ff | ||
|
|
7d7c11aa1a | ||
|
|
95748a2f2f | ||
|
|
e77045abe3 | ||
|
|
d73ddbdbcb | ||
|
|
8893a4a456 | ||
|
|
608112ce22 | ||
|
|
2e9155fbcc | ||
|
|
f33a30c697 | ||
|
|
e208dd5966 | ||
|
|
a6cb71f9c3 | ||
|
|
91deeb969b | ||
|
|
040d812f2a | ||
|
|
772ac80764 | ||
|
|
ef67c94272 | ||
|
|
fdb4a6bdc6 | ||
|
|
57902af87c | ||
|
|
92fea3ff01 | ||
|
|
cbd16169e4 | ||
|
|
299df34bf4 | ||
|
|
48a92df719 | ||
|
|
b806439e2f | ||
|
|
1db586c0bd | ||
|
|
26e2bfbf43 | ||
|
|
28c4ac6a19 | ||
|
|
c0d9d68fca | ||
|
|
122ed1dd0f | ||
|
|
f18b83999a | ||
|
|
fc28aacb52 | ||
|
|
efdfe2b1b5 | ||
|
|
2872da4f94 | ||
|
|
5d3c5e7f3c | ||
|
|
a86fb480b2 | ||
|
|
7daf33c149 | ||
|
|
0d9afdc939 | ||
|
|
a9a26193cd | ||
|
|
684973ea85 | ||
|
|
0149593272 | ||
|
|
1ae3df76bd | ||
|
|
d8e0a06d93 | ||
|
|
b2d842ddd0 | ||
|
|
580374208f | ||
|
|
8ab96f09cb | ||
|
|
f5b6728358 | ||
|
|
c7d46ee18f | ||
|
|
eafdd0fb61 | ||
|
|
a14394dd88 | ||
|
|
e28b0394ec | ||
|
|
11903e9728 | ||
|
|
0e498d1a81 | ||
|
|
f073112814 | ||
|
|
9ee71e9f25 | ||
|
|
0aafc16648 | ||
|
|
58246a91b0 | ||
|
|
53ae59271a | ||
|
|
1a2e4e72bd | ||
|
|
2a83c1b9ba | ||
|
|
5b245978d4 | ||
|
|
1463cee2a4 | ||
|
|
f4b910c268 | ||
|
|
d39c371635 | ||
|
|
19b3c2a265 | ||
|
|
28efc38fc4 | ||
|
|
6a7e948e89 | ||
|
|
645d23b531 | ||
|
|
50ae5bb7cc | ||
|
|
38728910cb | ||
|
|
e2f695777d | ||
|
|
06a98d0f94 | ||
|
|
944cbf04ed | ||
|
|
84891abc04 | ||
|
|
a848bb43b6 | ||
|
|
d57a2e5eae | ||
|
|
d1adcb876d | ||
|
|
8505d8ae0e | ||
|
|
3a567cb4a7 | ||
|
|
20dbba116a | ||
|
|
f7d7b5bd7b | ||
|
|
dd15420f2c | ||
|
|
31945533c2 | ||
|
|
9288e0abe0 | ||
|
|
5641fee39a | ||
|
|
db88458a14 | ||
|
|
3ff89bc648 | ||
|
|
61f3d2d513 | ||
|
|
788f253ad0 | ||
|
|
947d93ddc7 | ||
|
|
74063885b1 | ||
|
|
554fd6d700 | ||
|
|
1fb6861565 | ||
|
|
6c5350a51b | ||
|
|
00da7e9a82 | ||
|
|
e18bed12c0 | ||
|
|
d2bb7e912f | ||
|
|
73ed69a4ad | ||
|
|
d8fe6a0a55 | ||
|
|
b278bfd159 | ||
|
|
872beb777f | ||
|
|
aebcf5d183 | ||
|
|
aab9b71901 | ||
|
|
9cbab137fc | ||
|
|
358bc23931 | ||
|
|
7396bf0675 | ||
|
|
61166c4388 | ||
|
|
4b9f2c7728 | ||
|
|
6b496bdef2 | ||
|
|
0e795f58dd | ||
|
|
e2ac8e29fe | ||
|
|
bc80adc412 | ||
|
|
2f634625ea | ||
|
|
d80774d8d0 | ||
|
|
ecf3e97518 | ||
|
|
3758d1f5ad | ||
|
|
3e53008d35 | ||
|
|
afde5c2685 | ||
|
|
224c355d44 | ||
|
|
269718bfa6 | ||
|
|
d145fdbb23 | ||
|
|
a60848b16c | ||
|
|
e6d6843a37 | ||
|
|
0b44c794f9 | ||
|
|
5346db93e1 | ||
|
|
a0a7c7f428 | ||
|
|
57228147ce | ||
|
|
d122c92db1 | ||
|
|
7761946ec0 | ||
|
|
47a131c232 | ||
|
|
c6e095d066 | ||
|
|
282d5fe239 | ||
|
|
5164d57787 | ||
|
|
884fc5318a | ||
|
|
626e68eae8 | ||
|
|
0b76307354 | ||
|
|
3f9892f12f | ||
|
|
b525c0ede7 | ||
|
|
902e7513d9 | ||
|
|
6b824a47f5 | ||
|
|
d14a5df3fe | ||
|
|
0a4b78160f | ||
|
|
54be93b0da | ||
|
|
59c9de9f41 | ||
|
|
1631c1f147 | ||
|
|
0bd833a6b7 | ||
|
|
cf69a67029 | ||
|
|
3b1a359367 | ||
|
|
880c6ac554 | ||
|
|
8e732c68bc | ||
|
|
9799735420 | ||
|
|
9ffae4241d | ||
|
|
68cb95e758 | ||
|
|
967dafb87e | ||
|
|
743bb94e83 | ||
|
|
c461e6ac0b | ||
|
|
b2b31da80b | ||
|
|
0f6453cb26 | ||
|
|
c2eece31b0 | ||
|
|
4fa28e9040 | ||
|
|
889e94a494 | ||
|
|
c908b5e642 | ||
|
|
26f6d25481 | ||
|
|
034870ba19 | ||
|
|
54f6a68a8a | ||
|
|
626113affa | ||
|
|
4e299b2ad5 | ||
|
|
cb7c91f666 | ||
|
|
50208c2df3 | ||
|
|
8a49870822 | ||
|
|
bf7c93cd91 | ||
|
|
6c5777801f | ||
|
|
6887dab787 | ||
|
|
150531a1e2 | ||
|
|
a7a53610e6 | ||
|
|
2ec8189d47 | ||
|
|
d75d1eed10 | ||
|
|
2f426990b7 | ||
|
|
2e6c8f7054 | ||
|
|
206a8f5afc | ||
|
|
e340207cac | ||
|
|
12857ae6a3 | ||
|
|
43c8518a40 | ||
|
|
1b65ae2062 | ||
|
|
3ba31f205e | ||
|
|
bf28c2aacc | ||
|
|
80834220b3 | ||
|
|
d8aacdaa94 | ||
|
|
cf763993cf | ||
|
|
89006a8720 | ||
|
|
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 | ||
|
|
45db917ee7 | ||
|
|
42494ce58a | ||
|
|
93c75a3ffd | ||
|
|
b9283fb544 | ||
|
|
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 | ||
|
|
68d090f81a | ||
|
|
7ef74ac3ee | ||
|
|
5853691844 | ||
|
|
3a8b93d44a | ||
|
|
806a5aecef | ||
|
|
44d2918dee | ||
|
|
64f7db6585 | ||
|
|
fb0cd272ce | ||
|
|
83e619ecd4 | ||
|
|
3af89a7897 | ||
|
|
1e94b69a68 | ||
|
|
95b1945bc1 | ||
|
|
8aaba606bc | ||
|
|
f40657a7ff | ||
|
|
239c7371a8 | ||
|
|
788e56d926 | ||
|
|
37303a8c5a | ||
|
|
981b228a88 | ||
|
|
c6449d4c10 | ||
|
|
0f70e5b1d6 | ||
|
|
9c078971ab | ||
|
|
0f0c3c1b1d | ||
|
|
835f35393e | ||
|
|
fd30facd8f | ||
|
|
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 | ||
|
|
f3271846ea | ||
|
|
d39d6691e6 | ||
|
|
832b33f949 | ||
|
|
1373a93c75 | ||
|
|
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 | ||
|
|
b2773ff5b7 | ||
|
|
8ee017c3fa | ||
|
|
0eab5295db | ||
|
|
11fccf38a6 | ||
|
|
1d0180c436 | ||
|
|
7e7e45e794 | ||
|
|
c760af7810 | ||
|
|
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 | ||
|
|
c077dd0046 | ||
|
|
9cc6beead7 | ||
|
|
92f6792ad9 | ||
|
|
25031eadfb | ||
|
|
a7024615e1 | ||
|
|
8191a4bedb | ||
|
|
7e1f5aa353 | ||
|
|
44e161f6ec | ||
|
|
7bd17ed038 | ||
|
|
a47404130b | ||
|
|
669949aa8c | ||
|
|
c773bb90f6 | ||
|
|
18f525523b | ||
|
|
3bfd228df1 | ||
|
|
c072ab2dc5 | ||
|
|
53e06c5e86 | ||
|
|
b0a749bdc1 | ||
|
|
809443a46f | ||
|
|
59ef44dd71 | ||
|
|
d77973a29e | ||
|
|
be6bc16d3d | ||
|
|
407e926abe | ||
|
|
117655b011 | ||
|
|
2ea4a5a7ef | ||
|
|
30b0279efe | ||
|
|
7511701bb2 | ||
|
|
a919ae3430 | ||
|
|
c30ee24f8d | ||
|
|
10b5de571e | ||
|
|
c6f4ec7250 | ||
|
|
b229058726 | ||
|
|
21363794b6 | ||
|
|
1754d37ac0 | ||
|
|
1c36b185ec | ||
|
|
1a4b94e5df | ||
|
|
31bd8e1741 | ||
|
|
e8824ad1ac | ||
|
|
17636a5ecf | ||
|
|
8f36ff60de | ||
|
|
7ad605afba | ||
|
|
f080e8693e | ||
|
|
e27bf7868f | ||
|
|
db55fe9ff3 | ||
|
|
de4db0764c | ||
|
|
9d644b4625 | ||
|
|
354dc27e84 | ||
|
|
1c639474d3 | ||
|
|
4d8b47732e | ||
|
|
8650abf679 | ||
|
|
27dcc2ef48 | ||
|
|
94a12cd28c | ||
|
|
a982b64fd4 | ||
|
|
ee5b4bbf2e | ||
|
|
d09ddd8d07 | ||
|
|
c7bf47820c | ||
|
|
4dfb01d59e | ||
|
|
54bef6505f | ||
|
|
2aa71ed217 | ||
|
|
ef3b02b718 | ||
|
|
d90938421f | ||
|
|
e4992bbd17 | ||
|
|
7853b22522 | ||
|
|
9475215fa6 | ||
|
|
8622bbd2ac | ||
|
|
0cdcc252e0 | ||
|
|
9477cca8e8 | ||
|
|
176e427058 | ||
|
|
1bf26cacb9 | ||
|
|
38e8247483 | ||
|
|
44aa12108c | ||
|
|
dd73405aa9 | ||
|
|
6710393a53 | ||
|
|
711aee1c8b | ||
|
|
51be1048d5 | ||
|
|
50166f04d8 | ||
|
|
7eb6fea08b | ||
|
|
8e3c054da4 | ||
|
|
5249b67df1 | ||
|
|
583830c1c2 | ||
|
|
bb75f1c034 | ||
|
|
b7a9236283 | ||
|
|
7e0e172d37 | ||
|
|
ea37a34476 | ||
|
|
fb5564de07 | ||
|
|
cead7e9e4e | ||
|
|
7cf23b6c56 | ||
|
|
1080821862 | ||
|
|
0bf843ce2a | ||
|
|
b8bd8f5512 | ||
|
|
12fba30ef8 | ||
|
|
dc5cb74f4d | ||
|
|
446a524b5a | ||
|
|
121110aede | ||
|
|
c76a8562ea | ||
|
|
495d8069f5 | ||
|
|
70af6d0c35 | ||
|
|
9777d543b1 | ||
|
|
abfc6eea1e | ||
|
|
fd2f3e252c | ||
|
|
3b6d0065a2 | ||
|
|
51acf72e0d | ||
|
|
6ad724d80c | ||
|
|
7ffe2ab864 | ||
|
|
2f6febb748 | ||
|
|
092c897150 | ||
|
|
30f072f925 | ||
|
|
72d0282613 | ||
|
|
a56f766497 | ||
|
|
31b5583896 | ||
|
|
7c95bfc77e | ||
|
|
c6804fe589 | ||
|
|
fc41e282ce | ||
|
|
d3b21a4e39 | ||
|
|
7b6d9850a2 | ||
|
|
84d97817e3 | ||
|
|
2c6d2176bb | ||
|
|
7840d28ed2 | ||
|
|
d4fd2e6cd6 | ||
|
|
70e8385246 | ||
|
|
3fd23f1749 | ||
|
|
a3e3aea4e0 | ||
|
|
dcfb8ad8eb | ||
|
|
35f82e91bc | ||
|
|
f1533cb168 | ||
|
|
763fbec0ca | ||
|
|
111c893d94 | ||
|
|
80c38a45c5 | ||
|
|
548860607b | ||
|
|
66b5fe7337 | ||
|
|
2b2a117912 | ||
|
|
39a10fd167 | ||
|
|
93f8dbd42a | ||
|
|
4f280d0df8 | ||
|
|
5e9ff3278a | ||
|
|
b4b68f0e17 | ||
|
|
97bd324cb0 | ||
|
|
2738406710 | ||
|
|
8e78345cbe | ||
|
|
bd9e064137 | ||
|
|
eb49365bcb | ||
|
|
de92e28c7a | ||
|
|
c18f8fd87b | ||
|
|
34272cb4c4 | ||
|
|
b3f8a02494 | ||
|
|
f767a55350 | ||
|
|
9b2c3848d9 | ||
|
|
e34d016581 | ||
|
|
b64b447b42 | ||
|
|
c6a4c13394 | ||
|
|
c8822cb4ca | ||
|
|
c05e7218f3 | ||
|
|
01872d3440 | ||
|
|
91bbb3e4dc | ||
|
|
80ebd9fb0e | ||
|
|
2ab217251a | ||
|
|
2b20f6c74c | ||
|
|
042f855cd5 | ||
|
|
720ab7e0a3 | ||
|
|
4b1b100aa5 | ||
|
|
541b7e2a79 | ||
|
|
00cd3adc7b | ||
|
|
5a97a518a6 | ||
|
|
d7219068cd | ||
|
|
a79c07f095 | ||
|
|
9f27f70c87 | ||
|
|
8fa79db368 | ||
|
|
a1efa60741 | ||
|
|
a08a212fdc | ||
|
|
6166eb3743 | ||
|
|
5194fc5f15 | ||
|
|
1a97beb8cf | ||
|
|
99d23398ad | ||
|
|
6accdefb8c | ||
|
|
98fc64deaa | ||
|
|
30a8cefc37 | ||
|
|
9d47c3ccb3 | ||
|
|
5a1ab8d485 | ||
|
|
5d526f243e | ||
|
|
d13bb47ee7 | ||
|
|
8b8c6ee861 | ||
|
|
0a2d95e434 | ||
|
|
fdd119c477 | ||
|
|
4f94ca1384 | ||
|
|
ed21ee8bdb | ||
|
|
efd257dee0 | ||
|
|
bacf391a39 | ||
|
|
e8a1543466 | ||
|
|
81c79003ec | ||
|
|
73cf9661ac | ||
|
|
6e994b0ae1 | ||
|
|
c5da975cea | ||
|
|
1f3ef962e8 | ||
|
|
f6066a0361 | ||
|
|
ace65cf261 | ||
|
|
1df537ce5c | ||
|
|
bf64f6b4f4 | ||
|
|
0283ec574d | ||
|
|
d566f64e8b | ||
|
|
bb9add9da9 | ||
|
|
75d085a2c4 | ||
|
|
4eab07ffaf | ||
|
|
cf7aaa25cd | ||
|
|
8011b22de6 | ||
|
|
a108489d71 | ||
|
|
185c132771 | ||
|
|
3b11e905a1 | ||
|
|
5a04fe7ae6 | ||
|
|
92c73062cc | ||
|
|
d07624bdc1 | ||
|
|
e1dbe80ccd | ||
|
|
6d69a52292 | ||
|
|
68af5479c8 | ||
|
|
3970eca8dc | ||
|
|
b11d36c3a5 | ||
|
|
3c53fd8618 | ||
|
|
4ca4c57fff | ||
|
|
317a6fde30 | ||
|
|
ad08d385e6 | ||
|
|
805e12aceb | ||
|
|
3a45912400 | ||
|
|
19817e2659 | ||
|
|
50dc205ef7 | ||
|
|
2402a3ac72 | ||
|
|
e1c155d09d | ||
|
|
84c3bc4ad4 | ||
|
|
353784c23e | ||
|
|
a359624f01 | ||
|
|
a0f684cfdf | ||
|
|
1ea1e74a0c | ||
|
|
8f7c5fc922 | ||
|
|
667ef680c1 | ||
|
|
972ab0df50 | ||
|
|
1fddc01f6e | ||
|
|
bb2e77d899 | ||
|
|
a3daf13c15 | ||
|
|
fb2b2e37ce | ||
|
|
c1381179aa | ||
|
|
9e2dc3f892 | ||
|
|
5aa548d613 | ||
|
|
5225a95d3a | ||
|
|
53b7a1fce5 | ||
|
|
02369a4949 | ||
|
|
1ca548991b | ||
|
|
0772070523 | ||
|
|
4bf3848856 | ||
|
|
512425de4c | ||
|
|
7f28bd6a26 | ||
|
|
4088b2c1e8 | ||
|
|
919d55c002 | ||
|
|
068bbd0c3b | ||
|
|
9f50528192 | ||
|
|
4c149cf01c | ||
|
|
c86c706406 | ||
|
|
3b0a0f55b5 | ||
|
|
4232b8184e | ||
|
|
e5f3dfe293 | ||
|
|
22af94d36a | ||
|
|
d6b6781861 | ||
|
|
2222299793 | ||
|
|
fdd9a184b5 | ||
|
|
99492e3f8e | ||
|
|
a42c40bbc1 | ||
|
|
2794f9fcfc | ||
|
|
28c0262e74 | ||
|
|
8634191bd2 | ||
|
|
f73c86d533 | ||
|
|
f042d709ac | ||
|
|
e2a6149a93 | ||
|
|
b2a7e2c7e2 | ||
|
|
89fc143075 | ||
|
|
a754a92799 | ||
|
|
dc26fcf609 | ||
|
|
b9db57eeef | ||
|
|
9b377c727d | ||
|
|
e5b8d81bb4 | ||
|
|
c85b31a7d5 | ||
|
|
6580e5458a | ||
|
|
4e4e65eaa6 | ||
|
|
9d19aad384 | ||
|
|
c16a9f234b | ||
|
|
ace551c33d | ||
|
|
1e6e686692 | ||
|
|
afdcc3f7c0 | ||
|
|
00e64bc46c | ||
|
|
a959e1820f | ||
|
|
3dfbdbfe51 | ||
|
|
5c46dc0bd3 | ||
|
|
db60db674f | ||
|
|
687a4f14e1 | ||
|
|
bb10365b8b | ||
|
|
74ed3bf6a0 | ||
|
|
d1d7fdc488 | ||
|
|
67775a4c62 | ||
|
|
317b5cb096 | ||
|
|
2929517d7e | ||
|
|
51e788396d | ||
|
|
1321653bf6 | ||
|
|
3899854854 | ||
|
|
c0ca842ba7 | ||
|
|
24b05d28db | ||
|
|
f0268b105c | ||
|
|
0a46e180a9 | ||
|
|
e6a215a9c3 | ||
|
|
8ca7117065 | ||
|
|
ba0a07b835 | ||
|
|
4a35b65c2c | ||
|
|
836fa47812 | ||
|
|
5b658ef6ff | ||
|
|
e9ff24d9a7 | ||
|
|
a92051a4c3 | ||
|
|
77b3650580 | ||
|
|
67ee6857ad | ||
|
|
5ab15c0a14 | ||
|
|
96a3f2c301 | ||
|
|
85707264c4 | ||
|
|
ed05422ea8 | ||
|
|
8f10c8051e | ||
|
|
41fff399b5 | ||
|
|
9e237647b0 | ||
|
|
1be53c6746 | ||
|
|
f2368b03c0 | ||
|
|
95284c0b36 | ||
|
|
f1e21a93fb | ||
|
|
689811f659 | ||
|
|
33ccb4e98c | ||
|
|
29f6e98f9c | ||
|
|
775c8cc064 | ||
|
|
9a974d047c | ||
|
|
3fd252d2db | ||
|
|
43edb034c8 | ||
|
|
e629ca391e | ||
|
|
0db7eba3f2 | ||
|
|
a472abc88e | ||
|
|
861c619c19 | ||
|
|
124f331963 | ||
|
|
76fcd44191 | ||
|
|
2124c0b88c | ||
|
|
2f5ab8e3b9 | ||
|
|
32c8b6914b | ||
|
|
7e9d940f64 | ||
|
|
59e826b630 | ||
|
|
88f3ee4b13 | ||
|
|
b7380a084e | ||
|
|
cb65e790ae | ||
|
|
573eabee93 | ||
|
|
f0d4c6546a | ||
|
|
b0943c87c8 | ||
|
|
4f1208ea98 | ||
|
|
82b056bd43 | ||
|
|
4c417daee5 | ||
|
|
28c47dd9c7 | ||
|
|
02f16639ea | ||
|
|
cd5e28c0b8 | ||
|
|
6f7579f8d9 | ||
|
|
cd62220ba0 | ||
|
|
96e6aa89e3 | ||
|
|
f1d2a71b49 | ||
|
|
fd4fe0dc0d | ||
|
|
3d5e4a4225 | ||
|
|
10ffb452d0 | ||
|
|
3218bddbdc | ||
|
|
59f78dcbcb | ||
|
|
0fe062a02f | ||
|
|
869eaf8cfd | ||
|
|
5b445c9736 | ||
|
|
6f3f3eaa99 | ||
|
|
a23ce92676 | ||
|
|
ad55d5199d | ||
|
|
bd05895761 | ||
|
|
c68dd6c891 | ||
|
|
daae9ae1e7 | ||
|
|
33dde75ae1 | ||
|
|
b2e1e1796d | ||
|
|
c4a682773c | ||
|
|
07e1873cb3 | ||
|
|
14d0c4dc32 | ||
|
|
19f79b6e54 | ||
|
|
e0b9fc9481 | ||
|
|
16c28ad938 | ||
|
|
0e5af9ffa9 | ||
|
|
a0f6f03d4e | ||
|
|
f4cdfc5f32 | ||
|
|
b45988705b | ||
|
|
dd50239759 | ||
|
|
7297a07466 | ||
|
|
e5fa43f91c | ||
|
|
88080c8d02 | ||
|
|
d38dfee540 | ||
|
|
4da6a7b6f9 | ||
|
|
957dfeed6d | ||
|
|
aa5a07b98e | ||
|
|
fd1ee07297 | ||
|
|
07026125a9 | ||
|
|
badb747d24 | ||
|
|
e27c20d600 | ||
|
|
eb46c94c7c | ||
|
|
2e58f82a61 | ||
|
|
df28013b18 | ||
|
|
8f4e285974 | ||
|
|
08c4eeeefe | ||
|
|
1fdc2c629c | ||
|
|
a6767a1c3d | ||
|
|
c539d6b643 | ||
|
|
80c3ffdfd7 | ||
|
|
f80f2106fe | ||
|
|
cbfae66882 | ||
|
|
8a5014fcbe | ||
|
|
54d1bff213 | ||
|
|
0075664b9a | ||
|
|
a2c26f0f2c | ||
|
|
45f41a13e4 | ||
|
|
faae237ac5 | ||
|
|
e01758e74c | ||
|
|
db7dd31c79 | ||
|
|
7d7ac5e2be | ||
|
|
577016a33f | ||
|
|
fdf2102923 | ||
|
|
8b47e57be0 | ||
|
|
8dad6b64b0 | ||
|
|
05d36abdab | ||
|
|
04e31c5b4f | ||
|
|
6470428a85 | ||
|
|
a7efb3989a | ||
|
|
32072d0bbf | ||
|
|
78a3e4454d | ||
|
|
1ff68111b4 | ||
|
|
e576178e1e | ||
|
|
0f8bc2b03d | ||
|
|
43565458d4 | ||
|
|
71cc3be6d5 | ||
|
|
9a8eef7b19 | ||
|
|
a08c4368b7 | ||
|
|
3b456b2aab | ||
|
|
31559418ba | ||
|
|
692a6e43bc | ||
|
|
af4cce654c | ||
|
|
c63b02fd4a | ||
|
|
e6974b6e51 | ||
|
|
da3b7dbeff | ||
|
|
9737bd7012 | ||
|
|
55fd8e5e2d | ||
|
|
9c4f181d93 | ||
|
|
374342cfc1 | ||
|
|
6fbdd237d1 | ||
|
|
4e78f01a09 | ||
|
|
8853264808 | ||
|
|
6db9b8038f | ||
|
|
c73e89ccd4 | ||
|
|
f58a506780 | ||
|
|
411d19e74e | ||
|
|
bd51ffd9d2 | ||
|
|
83980fdccd | ||
|
|
baab243bc8 | ||
|
|
4dd8e1dc63 | ||
|
|
0b11b8b084 | ||
|
|
704775dc60 | ||
|
|
c7a7be1de0 | ||
|
|
e21a970977 | ||
|
|
a526dcf2dd | ||
|
|
a7b48d63e4 | ||
|
|
3677906e95 | ||
|
|
e86710fbbd | ||
|
|
73e850493a | ||
|
|
7d735f6f8a | ||
|
|
043e99a9eb | ||
|
|
65e079b1d3 | ||
|
|
32e6f584d8 | ||
|
|
ebc5219ce6 | ||
|
|
ad8e620bdf | ||
|
|
8590c693b9 | ||
|
|
5568acc5f3 | ||
|
|
c467594199 | ||
|
|
8b29bf7d93 | ||
|
|
43b7f83082 | ||
|
|
9ef5c981ef | ||
|
|
f027ac34d4 | ||
|
|
2ea31d6869 | ||
|
|
a1af7d0f9c | ||
|
|
797bf37bfa | ||
|
|
7f78815c11 | ||
|
|
d8a3f308ed | ||
|
|
74e18a982d | ||
|
|
e86ad423c5 | ||
|
|
2f00060c57 | ||
|
|
3b7e6aa68e | ||
|
|
5f3d6242fd | ||
|
|
6793a86bae | ||
|
|
d99ce20529 | ||
|
|
93e7b604cd | ||
|
|
59c18056fc | ||
|
|
5c81ce9b68 | ||
|
|
c9cf62701f | ||
|
|
f65babca4b | ||
|
|
23c146fc5d | ||
|
|
e14f336142 | ||
|
|
54647be5bd | ||
|
|
9517d65646 | ||
|
|
0156e401fa | ||
|
|
d6e2bc464d | ||
|
|
5124ff593d | ||
|
|
727c90afdc | ||
|
|
9ebd5e3265 | ||
|
|
d120142127 | ||
|
|
f9e4cddcaf | ||
|
|
8f64f174d9 | ||
|
|
d6817796b3 | ||
|
|
ab19d473c4 | ||
|
|
48f0116358 | ||
|
|
d8e6e97845 | ||
|
|
9a8920788c | ||
|
|
27864a3a3c | ||
|
|
b39e863591 | ||
|
|
d8d18ed25c | ||
|
|
7661e8cadd | ||
|
|
7d3c7a0c61 | ||
|
|
7375ff9f97 | ||
|
|
5df6ec8985 | ||
|
|
83fd2648f5 | ||
|
|
8e81758941 | ||
|
|
41a6a29771 | ||
|
|
9a8479ee58 | ||
|
|
73766f11eb | ||
|
|
a22878e2c5 | ||
|
|
1a2eb9d1e7 | ||
|
|
277ace3c8e | ||
|
|
40f376dbd9 | ||
|
|
444af0935e | ||
|
|
fb15fa0e43 | ||
|
|
bcd3e14870 | ||
|
|
c18702dcea | ||
|
|
1341ef9c52 | ||
|
|
f605a8d085 | ||
|
|
e0f7a7a3c6 | ||
|
|
1d72bed442 | ||
|
|
284b8e7c16 | ||
|
|
ff9fb24094 | ||
|
|
fde4448dd0 | ||
|
|
d16ce90a3d | ||
|
|
3ed5525956 | ||
|
|
855d1e12aa | ||
|
|
e03797a58f | ||
|
|
f0d38cf8ec | ||
|
|
2723580e17 | ||
|
|
1977aa481d | ||
|
|
4b36a8f831 | ||
|
|
96b56e38ba | ||
|
|
849d117ad3 | ||
|
|
8d57fca779 | ||
|
|
0dc867306b | ||
|
|
eefb4c01ec | ||
|
|
ccce499f7f | ||
|
|
9f11eaa4d3 | ||
|
|
7b85c0e55f | ||
|
|
7e92f1abd5 | ||
|
|
825f2518e9 | ||
|
|
def1e877db | ||
|
|
6acbd5b2cf | ||
|
|
73b7aef4a9 | ||
|
|
3d73e3922b | ||
|
|
224e44151f | ||
|
|
d9c1293985 | ||
|
|
849a40d4b5 | ||
|
|
177387e9b0 | ||
|
|
cacce54714 | ||
|
|
12082322ee | ||
|
|
7e0a5b7fec | ||
|
|
d2cf4afc81 | ||
|
|
3e0a50926f | ||
|
|
cce62de075 | ||
|
|
dff816324d | ||
|
|
d9450df7e9 | ||
|
|
41fc81fab6 | ||
|
|
aa35498bdd | ||
|
|
a33e2c6e36 | ||
|
|
75ef82d18a | ||
|
|
eb3c522122 | ||
|
|
6c8bcfc62e | ||
|
|
14becd0bd6 | ||
|
|
7390e21934 | ||
|
|
e408eb43bb | ||
|
|
dc0aa0851e | ||
|
|
51d7c43489 | ||
|
|
6b37967162 | ||
|
|
a03b9584ee | ||
|
|
34649dfeda | ||
|
|
bc84cfc2c8 | ||
|
|
bcba1f068b | ||
|
|
e6f30ef86b | ||
|
|
9e67999ef0 | ||
|
|
be75cef752 | ||
|
|
19ead71b48 | ||
|
|
7ebe1d6c62 | ||
|
|
2331b58b87 | ||
|
|
d495b04d85 | ||
|
|
751a8703ef | ||
|
|
1e6d26221d | ||
|
|
44a8e98c7b | ||
|
|
415519716e | ||
|
|
597f86dc7b | ||
|
|
579ed19949 | ||
|
|
9221bfa045 | ||
|
|
3057a31a6c | ||
|
|
d47ccf587c | ||
|
|
3e78d423ac | ||
|
|
0299cee5ec | ||
|
|
97ceffe689 | ||
|
|
9019d93449 | ||
|
|
32006e02c0 | ||
|
|
5ba0f6d51e | ||
|
|
c004d501f6 | ||
|
|
6067fa0fca | ||
|
|
e2c6658e59 | ||
|
|
0a6e50cbbe | ||
|
|
3732963d4b | ||
|
|
83bcbef6ce | ||
|
|
47cb4d1c49 | ||
|
|
32799cead7 | ||
|
|
dbedc2166b | ||
|
|
e86b404ca2 | ||
|
|
321a3a72f0 | ||
|
|
b512e7c390 | ||
|
|
ae7ead6272 | ||
|
|
db55719a6f | ||
|
|
4e1094e75b | ||
|
|
3fd97662f5 | ||
|
|
d6946b93c3 | ||
|
|
20217058fe | ||
|
|
e67365a19f | ||
|
|
c2f1817c6a | ||
|
|
4a948d9b01 | ||
|
|
377bc2703b | ||
|
|
196890b26f | ||
|
|
491fc2c164 | ||
|
|
1bed38f175 | ||
|
|
b124c31f65 | ||
|
|
8c588cbd66 | ||
|
|
334753b1ad | ||
|
|
f6e7401d1b | ||
|
|
ab564cc2d4 | ||
|
|
b10839a5c2 | ||
|
|
bb3323fb0e | ||
|
|
0b15ecbacd | ||
|
|
9f6afaed07 | ||
|
|
925420734e | ||
|
|
fb6bb12c52 | ||
|
|
22d12d0488 | ||
|
|
6888d959e1 | ||
|
|
65e66f52f6 | ||
|
|
225abfa126 | ||
|
|
876757f2d4 | ||
|
|
7e77398645 | ||
|
|
73c76a5a88 | ||
|
|
e5fca0d6cc | ||
|
|
eed7e5177f | ||
|
|
dd54ab31cb | ||
|
|
0085cb24ad | ||
|
|
6a758902ef | ||
|
|
0d81a9a9b6 | ||
|
|
6e4f6da633 | ||
|
|
15118ca5c1 | ||
|
|
8161560757 | ||
|
|
9ba564c864 | ||
|
|
06b5b92673 | ||
|
|
a417c373f1 | ||
|
|
5f5cc8d454 | ||
|
|
b9b6589bd7 | ||
|
|
b79f6a5fa0 | ||
|
|
0acbaeae86 | ||
|
|
bd046da3d0 | ||
|
|
a889ed7c46 | ||
|
|
e24684cb2b | ||
|
|
5f939c18b4 | ||
|
|
140f1eb31b | ||
|
|
d412dd5009 | ||
|
|
41e49423b2 | ||
|
|
8643bfeb37 | ||
|
|
31b6adf0e5 | ||
|
|
f1ac2b3507 | ||
|
|
172af307a6 | ||
|
|
135e1ef73d | ||
|
|
da55bf6af3 | ||
|
|
883a9c8b17 | ||
|
|
7da89940e3 | ||
|
|
3233b0ae3c | ||
|
|
4c2ed09915 | ||
|
|
256b6c480f | ||
|
|
dc311837f9 | ||
|
|
92aec48c99 | ||
|
|
a6ada8c457 | ||
|
|
dcc601502e | ||
|
|
dd58d8c804 | ||
|
|
2ade54b7e3 | ||
|
|
136c5854f3 | ||
|
|
c597238d9c | ||
|
|
2552a58e08 | ||
|
|
74ad5872a3 | ||
|
|
485d502bd3 | ||
|
|
47bc8d030e | ||
|
|
48fe7133f7 | ||
|
|
5d962dc5e4 | ||
|
|
31e8e5a951 | ||
|
|
858373c628 | ||
|
|
7f142d2c0d | ||
|
|
08b86232a8 | ||
|
|
6bf4f42fdb | ||
|
|
f3c7de36d8 | ||
|
|
19f556de57 | ||
|
|
e4467df411 | ||
|
|
8d305a1fb1 | ||
|
|
b47153e645 | ||
|
|
c71766c84b | ||
|
|
23e4d679ae | ||
|
|
182acb2e02 | ||
|
|
b255b15006 | ||
|
|
b458f88161 | ||
|
|
398d8f2f1c | ||
|
|
85c1a56cbf | ||
|
|
da216c6960 | ||
|
|
bc91b153bf | ||
|
|
bc50b47d3a | ||
|
|
aed15a7f25 | ||
|
|
a1f09117b0 | ||
|
|
0a4a4a51ca | ||
|
|
f7fd53bf09 | ||
|
|
cbfb863a54 | ||
|
|
2848f07b83 | ||
|
|
55224ddcd8 | ||
|
|
054ae75b6b | ||
|
|
a10188260c | ||
|
|
70c386a934 | ||
|
|
08eb21844a | ||
|
|
7b37d6b571 | ||
|
|
f52bd2bcc0 | ||
|
|
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
|
||||||
6
.github/CONTRIBUTING.md
vendored
Normal file
6
.github/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# The guidelines for contributing
|
||||||
|
|
||||||
|
- At first, see [Wiki](https://github.com/gitbucket/gitbucket/wiki) and check issues and pull requests whether there is a same request in the past.
|
||||||
|
- 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. If you don't wanna waste your time to make a pull request, ask us about your idea at [gitter room](https://gitter.im/gitbucket/gitbucket) before staring your work.
|
||||||
|
- You can edit the GitBucket documentation on Wiki if you have a GitHub account. When you find any mistakes or lacks in the documentation, please update it directly.
|
||||||
|
- All your contributions are handled as [Apache Software License, Version 2.0](https://github.com/gitbucket/gitbucket/blob/master/LICENSE). When you create a pull request or update the documentation, we assume you agreed this clause.
|
||||||
18
.github/ISSUE_TEMPLATE.md
vendored
Normal file
18
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
### 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)*
|
||||||
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
|
||||||
5
.github/SUPPORT.md
vendored
Normal file
5
.github/SUPPORT.md
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# The support guidelines
|
||||||
|
|
||||||
|
- 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.
|
||||||
|
- Write issues in English if it's possible. It enables many of contributors to help you.
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,5 +1,7 @@
|
|||||||
*.class
|
*.class
|
||||||
*.log
|
*.log
|
||||||
|
.ensime
|
||||||
|
.ensime_cache
|
||||||
|
|
||||||
# sbt specific
|
# sbt specific
|
||||||
dist/*
|
dist/*
|
||||||
@@ -14,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
|
||||||
|
|||||||
19
.travis.yml
19
.travis.yml
@@ -1,3 +1,18 @@
|
|||||||
language: scala
|
language: scala
|
||||||
scala:
|
sudo: true
|
||||||
- 2.11.2
|
jdk:
|
||||||
|
- oraclejdk8
|
||||||
|
script:
|
||||||
|
- sbt test
|
||||||
|
before_script:
|
||||||
|
- 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
|
||||||
|
|||||||
466
CHANGELOG.md
Normal file
466
CHANGELOG.md
Normal file
@@ -0,0 +1,466 @@
|
|||||||
|
# Changelog
|
||||||
|
All changes to the project will be documented in this file.
|
||||||
|
|
||||||
|
### 4.21.2 - 27 Jan 2018
|
||||||
|
- Bugfix
|
||||||
|
|
||||||
|
### 4.21.1 - 27 Jan 2018
|
||||||
|
- Bugfix
|
||||||
|
|
||||||
|
### 4.21.0 - 27 Jan 2018
|
||||||
|
- Release page
|
||||||
|
- OpenID Connect support
|
||||||
|
- New database viewer
|
||||||
|
- Submodule links to web page
|
||||||
|
- Clarify close/reopen button
|
||||||
|
|
||||||
|
## 4.20.0 - 23 Dec 2017
|
||||||
|
- Squash and rebase merge strategy for pull requests
|
||||||
|
- Quick pull request creation
|
||||||
|
- Download patch from the diff view
|
||||||
|
- Fork and create repository are proceeded asynchronously
|
||||||
|
- Create new repository by copying existing git repository
|
||||||
|
- Hide overflowed repository names in the sidebar
|
||||||
|
- Support CreateEvent web hook
|
||||||
|
- Display conflicting files if pull request can't be merged
|
||||||
|
|
||||||
|
## 4.19.3 - 7 Dec 2017
|
||||||
|
- Fix file uploading bug
|
||||||
|
- Fix reply comment form behavior in the diff view
|
||||||
|
|
||||||
|
## 4.19.2 - 3 Dec 2017
|
||||||
|
- Fix routing bug in `CompositeScalatraFilter`
|
||||||
|
- Resolve id attribute collision in the web hook editing form
|
||||||
|
|
||||||
|
## 4.19.1 - 2 Dec 2017
|
||||||
|
- Update gitbucket-notifications-plugin because it had a version compatibility issue
|
||||||
|
|
||||||
|
## 4.19.0 - 2 Dec 2017
|
||||||
|
- [gitbucket-maven-repository-plugin](https://github.com/takezoe/gitbucket-maven-repository-plugin) is available
|
||||||
|
- Upgrade to Scalatra 2.6
|
||||||
|
- Improve layout of the system settings page
|
||||||
|
- New extension point (`sshCommandProvider`)
|
||||||
|
- Dropped [gitbucket-pages-plugin](https://github.com/gitbucket/gitbucket-pages-plugin) from bundled plugins temporary because we couldn't complete update for Scalatra 2.6 before this release.
|
||||||
|
|
||||||
|
## 4.18.0 - 14 Oct 2017
|
||||||
|
- Form to reply to review comment
|
||||||
|
- Display fullname in username suggestion
|
||||||
|
- Commit hook plugins are applied to online editing
|
||||||
|
- Improve gitbucket-ci-plugin
|
||||||
|
|
||||||
|
## 4.17.0 - 30 Sep 2017
|
||||||
|
- [gitbucket-ci-plugin](https://github.com/takezoe/gitbucket-ci-plugin) is available
|
||||||
|
- Transferring to URL with commit ID
|
||||||
|
- Drop uploadable file type limitation
|
||||||
|
- Improve Mailer API
|
||||||
|
- Web API and webhook enhancement
|
||||||
|
|
||||||
|
## 4.16.0 - 2 Sep 2017
|
||||||
|
- Support AdminLTE color skin
|
||||||
|
- Improve unexpected error handling
|
||||||
|
- Show commit status on the commits list
|
||||||
|
|
||||||
|
## 4.15.0 - 5 Aug 2017
|
||||||
|
- Bundle GitBucket organization plugins
|
||||||
|
- Notifications plugin
|
||||||
|
- Plugin hot deployment
|
||||||
|
- Update Slick to 3.2.1 from 3.2.0
|
||||||
|
- Support ed25519 keys for SSH
|
||||||
|
- Markdown preview in comment editing forms
|
||||||
|
|
||||||
|
## 4.14.1 - 4 Jul 2017
|
||||||
|
- Bug fix: Possibility of error in forking repository
|
||||||
|
|
||||||
|
## 4.14 - 1 Jul 2017
|
||||||
|
- Support priority in issues and pull requests
|
||||||
|
- Show icons when the sidebar is collapsed
|
||||||
|
- Support gollum events in web hook
|
||||||
|
- Support account (user / group) level web hook
|
||||||
|
- Add `--max_file_size` option
|
||||||
|
- Configuration by system property or environment variable
|
||||||
|
|
||||||
|
## 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
|
||||||
|
- GFM inline breaks support in Markdown
|
||||||
|
- WebHook on create review comment is available
|
||||||
|
- WebHook event trigger is selectable
|
||||||
|
|
||||||
|
## 3.8 - 31 Oct 2015
|
||||||
|
- Moved to GitHub organization
|
||||||
|
- Omit diff view for large differences
|
||||||
|
- Repository creation API
|
||||||
|
- Render url as link in repository description
|
||||||
|
- Expand attachable file types
|
||||||
|
|
||||||
|
## 3.7 - 3 Oct 2015
|
||||||
|
- Markdown processor has been switched to [markedj](https://github.com/gitbucket/markedj) from pegdown
|
||||||
|
- Clone in desktop button
|
||||||
|
- Providing MD5 and SHA-1 checksum for `gitbucket.war` has started
|
||||||
|
|
||||||
|
## 3.6 - 30 Aug 2015
|
||||||
|
- User interface Improvements: Especially, commit list, issues and pull request have been updated largely.
|
||||||
|
- Installed plugins list has been available at the system administration console.
|
||||||
|
- Pages and repository list in the sidebar have been limited and more pages and repositories link is available.
|
||||||
|
- More reference link notation in Markdown has been supported.
|
||||||
|
|
||||||
|
## 3.5 - 1 Aug 2015
|
||||||
|
- Octicons has been applied
|
||||||
|
- Global header has been enhanced. Now it's further similar to GitHub.
|
||||||
|
- Default compare / pull request target has been changed to the parent repository
|
||||||
|
- A lot of updates for [gitbucket-gist-plugin](https://github.com/gitbucket/gitbucket-gist-plugin)
|
||||||
|
|
||||||
|
## 3.4 - 27 Jun 2015
|
||||||
|
- Declarative style plug-in definition
|
||||||
|
- New extension point to add markup render
|
||||||
|
- go-import support
|
||||||
|
|
||||||
|
## 3.3 - 31 May 2015
|
||||||
|
- Rich graphical diff for images
|
||||||
|
- File finder is available in the repository viewer
|
||||||
|
- Blame is displayed at the source viewer
|
||||||
|
- Remain user data and repositories even if user is disabled
|
||||||
|
- Mobile view improvement
|
||||||
|
|
||||||
|
## 3.2 - 3 May 2015
|
||||||
|
- Directory history button
|
||||||
|
- Compare / pull request button
|
||||||
|
- Limit of activity log
|
||||||
|
|
||||||
|
## 3.1.1 - 4 Apr 2015
|
||||||
|
- Rolled back H2 version to avoid version compatibility issue
|
||||||
|
- Plug-ins became possible to access ServletContext
|
||||||
|
|
||||||
|
## 3.1 - 28 Mar 2015
|
||||||
|
- Web APIs for Jenkins github pull-request builder
|
||||||
|
- Improved diff view
|
||||||
|
- Bump Scalatra to 2.3.1, sbt to 0.13.8
|
||||||
|
|
||||||
|
## 3.0 - 3 Mar 2015
|
||||||
|
- New plug-in system is available
|
||||||
|
- Connection pooling by c3p0
|
||||||
|
- New branch UI
|
||||||
|
- Compare between specified commit ids
|
||||||
|
|
||||||
|
## 2.8 - 1 Feb 2015
|
||||||
|
- New logo and icons
|
||||||
|
- New system setting options to control visibility
|
||||||
|
- Comment on side-by-side diff
|
||||||
|
- Information message on sign-in page
|
||||||
|
- Fork repository by group account
|
||||||
|
|
||||||
|
## 2.7 - 29 Dec 2014
|
||||||
|
- Comment for commit and diff
|
||||||
|
- Fix security issue in markdown rendering
|
||||||
|
- Some bug fix and improvements
|
||||||
|
|
||||||
|
## 2.6 - 24 Nov 2014
|
||||||
|
- Search box at issues and pull requests
|
||||||
|
- Information from administrator
|
||||||
|
- Pull request UI has been updated
|
||||||
|
- Move to TravisCI from Buildhive
|
||||||
|
- Some bug fix and improvements
|
||||||
|
|
||||||
|
## 2.5 - 4 Nov 2014
|
||||||
|
- New Dashboard
|
||||||
|
- Change datetime format
|
||||||
|
- Create branch from Web UI
|
||||||
|
- Task list in Markdown
|
||||||
|
- Some bug fix and improvements
|
||||||
|
|
||||||
|
## 2.4.1 - 6 Oct 2014
|
||||||
|
- Bug fix
|
||||||
|
|
||||||
|
## 2.4 - 6 Oct 2014
|
||||||
|
- New UI is applied to Issues and Pull requests
|
||||||
|
- Side-by-side diff is available
|
||||||
|
- Fix relative path problem in Markdown links and images
|
||||||
|
- Plugin System is disabled in default
|
||||||
|
- Some bug fix and improvements
|
||||||
|
|
||||||
|
## 2.3 - 1 Sep 2014
|
||||||
|
- Scala based plugin system
|
||||||
|
- Embedded Jetty war extraction directory moved to `GITBUCKET_HOME/tmp`
|
||||||
|
- Some bug fix and improvements
|
||||||
|
|
||||||
|
## 2.2.1 - 5 Aug 2014
|
||||||
|
- Bug fix
|
||||||
|
|
||||||
|
## 2.2 - 4 Aug 2014
|
||||||
|
- Plug-in system is available
|
||||||
|
- Move to Scala 2.11, Scalatra 2.3 and Slick 2.1
|
||||||
|
- tar.gz export for repository contents
|
||||||
|
- LDAP authentication improvement (mail address became optional)
|
||||||
|
- Show news feed of a private repository to members
|
||||||
|
- Some bug fix and improvements
|
||||||
|
|
||||||
|
## 2.1 - 6 Jul 2014
|
||||||
|
- Upgrade to Slick 2.0 from 1.9
|
||||||
|
- Base part of the plug-in system is merged
|
||||||
|
- Many bug fix and improvements
|
||||||
|
|
||||||
|
## 2.0 - 31 May 2014
|
||||||
|
- Modern Github UI
|
||||||
|
- Preview in AceEditor
|
||||||
|
- Select lines by clicking line number in blob view
|
||||||
|
|
||||||
|
## 1.13 - 29 Apr 2014
|
||||||
|
- Direct file editing in the repository viewer using AceEditor
|
||||||
|
- File attachment for issues
|
||||||
|
- Atom feed of user activity
|
||||||
|
- Fix some bugs
|
||||||
|
|
||||||
|
## 1.12 - 29 Mar 2014
|
||||||
|
- SSH repository access is available
|
||||||
|
- Allow users can create and management their groups
|
||||||
|
- Git submodule support
|
||||||
|
- Close issues via commit messages
|
||||||
|
- Show repository description below the name on repository page
|
||||||
|
- Fix presentation of the source viewer
|
||||||
|
- Upgrade to sbt 0.13
|
||||||
|
- Fix some bugs
|
||||||
|
|
||||||
|
## 1.11.1 - 06 Mar 2014
|
||||||
|
- Bug fix
|
||||||
|
|
||||||
|
## 1.11 - 01 Mar 2014
|
||||||
|
- Base URL for redirection, notification and repository URL box is configurable
|
||||||
|
- Remove ```--https``` option because it's possible to substitute in the base url
|
||||||
|
- Headline anchor is available for Markdown contents such as Wiki page
|
||||||
|
- Improve H2 connectivity
|
||||||
|
- Label is available for pull requests not only issues
|
||||||
|
- Delete branch button is added
|
||||||
|
- Repository icons are updated
|
||||||
|
- Select lines of source code by URL hash like `#L10` or `#L10-L15` in repository viewer
|
||||||
|
- Display reference to issue from others in comment list
|
||||||
|
- Fix some bugs
|
||||||
|
|
||||||
|
## 1.10 - 01 Feb 2014
|
||||||
|
- Rename repository
|
||||||
|
- Transfer repository owner
|
||||||
|
- Change default data directory to `HOME/.gitbucket` from `HOME/gitbucket` to avoid problem like #243, but if data directory already exist at HOME/gitbucket, it continues being used.
|
||||||
|
- Add LDAP display name attribute
|
||||||
|
- Response performance improvement
|
||||||
|
- Fix some bugs
|
||||||
|
|
||||||
|
## 1.9 - 28 Dec 2013
|
||||||
|
- Display GITBUCKET_HOME on the system settings page
|
||||||
|
- Fix some bugs
|
||||||
|
|
||||||
|
## 1.8 - 30 Nov 2013
|
||||||
|
- Add user and group deletion
|
||||||
|
- Improve pull request performance
|
||||||
|
- Pull request synchronization (when source repository is updated after pull request, it's applied to the pull request)
|
||||||
|
- LDAP StartTLS support
|
||||||
|
- Enable hard wrapping in Markdown
|
||||||
|
- Add new some options to specify the data directory. See details in [Wiki](https://github.com/takezoe/gitbucket/wiki/DirectoryStructure).
|
||||||
|
- Fix some bugs
|
||||||
|
|
||||||
|
## 1.7 - 26 Oct 2013
|
||||||
|
- Support working on Java6 in embedded Jetty mode
|
||||||
|
- Add `--host` option to bind specified host name in embedded Jetty mode
|
||||||
|
- Add `--https=true` option to force https scheme when using embedded Jetty mode at the back of https proxy
|
||||||
|
- Add full name as user property
|
||||||
|
- Change link color for absent Wiki pages
|
||||||
|
- Add ZIP download button to the repository viewer tab
|
||||||
|
- Improve ZIP exporting performance
|
||||||
|
- Expand issue and comment textarea for long text automatically
|
||||||
|
- Add conflict detection in Wiki
|
||||||
|
- Add reverting wiki page from history
|
||||||
|
- Match committer to user name by email address
|
||||||
|
- Mail notification sender is customizable
|
||||||
|
- Add link to changeset in refs comment for issues
|
||||||
|
- Fix some bugs
|
||||||
|
|
||||||
|
## 1.6 - 1 Oct 2013
|
||||||
|
- Web hook
|
||||||
|
- Performance improvement for pull request
|
||||||
|
- Executable war file
|
||||||
|
- Specify suitable Content-Type for downloaded files in the repository viewer
|
||||||
|
- Fix some bugs
|
||||||
|
|
||||||
|
## 1.5 - 4 Sep 2013
|
||||||
|
- Fork and pull request
|
||||||
|
- LDAP authentication
|
||||||
|
- Mail notification
|
||||||
|
- Add an option to turn off the gravatar support
|
||||||
|
- Add the branch tab in the repository viewer
|
||||||
|
- Encoding auto detection for the file content in the repository viewer
|
||||||
|
- Add favicon, header logo and icons for the timeline
|
||||||
|
- Specify data directory via environment variable GITBUCKET_HOME
|
||||||
|
- Fix some bugs
|
||||||
|
|
||||||
|
## 1.4 - 31 Jul 2013
|
||||||
|
- Group management
|
||||||
|
- Repository search for code and issues
|
||||||
|
- Display user related issues on the dashboard
|
||||||
|
- Display participants avatar of issues on the issue page
|
||||||
|
- Performance improvement for repository viewer
|
||||||
|
- Alert by milestone due date
|
||||||
|
- H2 database administration console
|
||||||
|
- Fix some bugs
|
||||||
|
|
||||||
|
## 1.3 - 18 Jul 2013
|
||||||
|
- Batch updating for issues
|
||||||
|
- Display assigned user on issue list
|
||||||
|
- User icon and Gravatar support
|
||||||
|
- Convert @xxxx to link to the account page
|
||||||
|
- Add copy to clipboard button for git clone URL
|
||||||
|
- Allow multi-byte characters as wiki page name
|
||||||
|
- Allow to create the empty repository
|
||||||
|
- Fix some bugs
|
||||||
|
|
||||||
|
## 1.2 - 09 Jul 2013
|
||||||
|
- Add activity timeline
|
||||||
|
- Bugfix for Git 1.8.1.5 or later
|
||||||
|
- Allow multi-byte characters as label
|
||||||
|
- Fix some bugs
|
||||||
|
|
||||||
|
## 1.1 - 05 Jul 2013
|
||||||
|
- Fix some bugs
|
||||||
|
- Upgrade to JGit 3.0
|
||||||
|
|
||||||
|
## 1.0 - 04 Jul 2013
|
||||||
|
- This is a first public release
|
||||||
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.
|
||||||
|
|||||||
279
README.md
279
README.md
@@ -1,254 +1,79 @@
|
|||||||
GitBucket [](https://gitter.im/takezoe/gitbucket) [](https://travis-ci.org/takezoe/gitbucket)
|
GitBucket [](https://gitter.im/gitbucket/gitbucket) [](https://travis-ci.org/gitbucket/gitbucket)
|
||||||
=========
|
=========
|
||||||
|
|
||||||
GitBucket is the easily installable Github clone written with Scala.
|
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
|
||||||
- Mail notification
|
- a Plug-in system
|
||||||
- Activity timeline
|
|
||||||
- User management (for Administrators)
|
|
||||||
- Group (like Organization in Github)
|
|
||||||
- LDAP integration
|
|
||||||
- Gravatar support
|
|
||||||
|
|
||||||
Following features are not implemented, but we will make them in the future release!
|
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).
|
||||||
|
|
||||||
- Network graph
|
|
||||||
- Statistics
|
|
||||||
- Watch / Star
|
|
||||||
|
|
||||||
If you want to try the development version of GitBucket, see the documentation for developers at [Wiki](https://github.com/takezoe/gitbucket/wiki).
|
|
||||||
|
|
||||||
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/takezoe/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 nignx)
|
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]`
|
||||||
|
- `--max_file_size=[MAX_FILE_SIZE]`
|
||||||
|
|
||||||
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]
|
`MAX_FILE_SIZE` is the max file size for upload files.
|
||||||
- --prefix=[CONTEXTPATH]
|
|
||||||
- --host=[HOSTNAME]
|
|
||||||
- --gitbucket.home=[DATA_DIR]
|
|
||||||
|
|
||||||
To upgrade GitBucket, only replace gitbucket.war. All GitBucket data is stored in HOME/.gitbucket. So if you want to back up GitBucket data, copy this directory to the other disk.
|
You can also deploy `gitbucket.war` to a servlet container which supports Servlet 3.0 (like Jetty, Tomcat, JBoss, etc)
|
||||||
|
|
||||||
For Installation on Windows Server with IIS see [this wiki page](https://github.com/takezoe/gitbucket/wiki/Installation-on-IIS-and-Helicontech-Zoo)
|
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).
|
||||||
|
|
||||||
### Mac OS X
|
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.
|
||||||
#### Installing Via Homebrew
|
|
||||||
|
|
||||||
$ brew install gitbucket
|
Plugins
|
||||||
==> Downloading https://github.com/takezoe/gitbucket/releases/download/1.10/gitbucket.war
|
|
||||||
######################################################################## 100.0%
|
|
||||||
==> Caveats
|
|
||||||
Note: When using launchctl the port will be 8080.
|
|
||||||
|
|
||||||
To have launchd start gitbucket at login:
|
|
||||||
ln -sfv /usr/local/opt/gitbucket/*.plist ~/Library/LaunchAgents
|
|
||||||
Then to load gitbucket now:
|
|
||||||
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.gitbucket.plist
|
|
||||||
Or, if you don't want/need launchctl, you can just run:
|
|
||||||
java -jar /usr/local/opt/gitbucket/libexec/gitbucket.war
|
|
||||||
==> Summary
|
|
||||||
/usr/local/Cellar/gitbucket/1.10: 3 files, 42M, built in 11 seconds
|
|
||||||
|
|
||||||
#### Manual Installation
|
|
||||||
On OS X, copy the [gitbucket.plist](https://raw.github.com/takezoe/gitbucket/master/contrib/macosx/gitbucket.plist) file to `~/Library/LaunchAgents/`
|
|
||||||
|
|
||||||
Run the following commands in `Terminal` to
|
|
||||||
|
|
||||||
- start gitbucket: `launchctl load ~/Library/LaunchAgents/gitbucket.plist`
|
|
||||||
- stop gitbucket: `launchctl unload ~/Library/LaunchAgents/gitbucket.plist`
|
|
||||||
|
|
||||||
Release Notes
|
|
||||||
--------
|
--------
|
||||||
### 2.7 - 29 Dec 2014
|
GitBucket has a plug-in system that allows extra functionality. Officially the following plug-ins are provided:
|
||||||
- Comment for commit and diff
|
|
||||||
- Fix security issue in markdown rendering
|
|
||||||
- Some bug fix and improvements
|
|
||||||
|
|
||||||
### 2.6 - 24 Nov 2014
|
- [gitbucket-gist-plugin](https://github.com/gitbucket/gitbucket-gist-plugin)
|
||||||
- Search box at issues and pull requests
|
- [gitbucket-emoji-plugin](https://github.com/gitbucket/gitbucket-emoji-plugin)
|
||||||
- Information from administrator
|
- [gitbucket-pages-plugin](https://github.com/gitbucket/gitbucket-pages-plugin)
|
||||||
- Pull request UI has been updated
|
- [gitbucket-notifications-plugin](https://github.com/gitbucket/gitbucket-notifications-plugin)
|
||||||
- Move to TravisCI from Buildhive
|
|
||||||
- Some bug fix and improvements
|
|
||||||
|
|
||||||
### 2.5 - 4 Nov 2014
|
You can find more plugins made by the community at [GitBucket community plugins](https://gitbucket-plugins.github.io/).
|
||||||
- New Dashboard
|
|
||||||
- Change datetime format
|
|
||||||
- Create branch from Web UI
|
|
||||||
- Task list in Markdown
|
|
||||||
- Some bug fix and improvements
|
|
||||||
|
|
||||||
### 2.4.1 - 6 Oct 2014
|
Support
|
||||||
- Bug fix
|
--------
|
||||||
|
|
||||||
### 2.4 - 6 Oct 2014
|
- 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.
|
||||||
- New UI is applied to Issues and Pull requests
|
- If you can't find same question and report, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
|
||||||
- Side-by-side diff is available
|
- 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.
|
||||||
- Fix relative path problem in Markdown links and images
|
|
||||||
- Plugin System is disabled in default
|
|
||||||
- Some bug fix and improvements
|
|
||||||
|
|
||||||
### 2.3 - 1 Sep 2014
|
What's New in 4.22.x
|
||||||
- Scala based plugin system
|
-------------
|
||||||
- Embedded Jetty war extraction directory moved to `GITBUCKET_HOME/tmp`
|
### 4.22.0 - 3 Mar 2018
|
||||||
- Some bug fix and improvements
|
- Pull request merge strategy settings
|
||||||
|
- Create repository with an empty commit
|
||||||
|
- Improve database viewer
|
||||||
|
- Update maven-repository-plugin
|
||||||
|
|
||||||
### 2.2.1 - 5 Aug 2014
|
See the [change log](CHANGELOG.md) for all of the updates.
|
||||||
- Bug fix
|
|
||||||
|
|
||||||
### 2.2 - 4 Aug 2014
|
|
||||||
- Plug-in system is available
|
|
||||||
- Move to Scala 2.11, Scalatra 2.3 and Slick 2.1
|
|
||||||
- tar.gz export for repository contents
|
|
||||||
- LDAP authentication improvement (mail address became optional)
|
|
||||||
- Show news feed of a private repository to members
|
|
||||||
- Some bug fix and improvements
|
|
||||||
|
|
||||||
### 2.1 - 6 Jul 2014
|
|
||||||
- Upgrade to Slick 2.0 from 1.9
|
|
||||||
- Base part of the plug-in system is merged
|
|
||||||
- Many bug fix and improvements
|
|
||||||
|
|
||||||
### 2.0 - 31 May 2014
|
|
||||||
- Modern Github UI
|
|
||||||
- Preview in AceEditor
|
|
||||||
- Select lines by clicking line number in blob view
|
|
||||||
|
|
||||||
### 1.13 - 29 Apr 2014
|
|
||||||
- Direct file editing in the repository viewer using AceEditor
|
|
||||||
- File attachment for issues
|
|
||||||
- Atom feed of user activity
|
|
||||||
- Fix some bugs
|
|
||||||
|
|
||||||
### 1.12 - 29 Mar 2014
|
|
||||||
- SSH repository access is available
|
|
||||||
- Allow users can create and management their groups
|
|
||||||
- Git submodule support
|
|
||||||
- Close issues via commit messages
|
|
||||||
- Show repository description below the name on repository page
|
|
||||||
- Fix presentation of the source viewer
|
|
||||||
- Upgrade to sbt 0.13
|
|
||||||
- Fix some bugs
|
|
||||||
|
|
||||||
### 1.11.1 - 06 Mar 2014
|
|
||||||
- Bug fix
|
|
||||||
|
|
||||||
### 1.11 - 01 Mar 2014
|
|
||||||
- Base URL for redirection, notification and repository URL box is configurable
|
|
||||||
- Remove ```--https``` option because it's possible to substitute in the base url
|
|
||||||
- Headline anchor is available for Markdown contents such as Wiki page
|
|
||||||
- Improve H2 connectivity
|
|
||||||
- Label is available for pull requests not only issues
|
|
||||||
- Delete branch button is added
|
|
||||||
- Repository icons are updated
|
|
||||||
- Select lines of source code by URL hash like `#L10` or `#L10-L15` in repository viewer
|
|
||||||
- Display reference to issue from others in comment list
|
|
||||||
- Fix some bugs
|
|
||||||
|
|
||||||
### 1.10 - 01 Feb 2014
|
|
||||||
- Rename repository
|
|
||||||
- Transfer repository owner
|
|
||||||
- Change default data directory to `HOME/.gitbucket` from `HOME/gitbucket` to avoid problem like #243, but if data directory already exist at HOME/gitbucket, it continues being used.
|
|
||||||
- Add LDAP display name attribute
|
|
||||||
- Response performance improvement
|
|
||||||
- Fix some bugs
|
|
||||||
|
|
||||||
### 1.9 - 28 Dec 2013
|
|
||||||
- Display GITBUCKET_HOME on the system settings page
|
|
||||||
- Fix some bugs
|
|
||||||
|
|
||||||
### 1.8 - 30 Nov 2013
|
|
||||||
- Add user and group deletion
|
|
||||||
- Improve pull request performance
|
|
||||||
- Pull request synchronization (when source repository is updated after pull request, it's applied to the pull request)
|
|
||||||
- LDAP StartTLS support
|
|
||||||
- Enable hard wrapping in Markdown
|
|
||||||
- Add new some options to specify the data directory. See details in [Wiki](https://github.com/takezoe/gitbucket/wiki/DirectoryStructure).
|
|
||||||
- Fix some bugs
|
|
||||||
|
|
||||||
### 1.7 - 26 Oct 2013
|
|
||||||
- Support working on Java6 in embedded Jetty mode
|
|
||||||
- Add `--host` option to bind specified host name in embedded Jetty mode
|
|
||||||
- Add `--https=true` option to force https scheme when using embedded Jetty mode at the back of https proxy
|
|
||||||
- Add full name as user property
|
|
||||||
- Change link color for absent Wiki pages
|
|
||||||
- Add ZIP download button to the repository viewer tab
|
|
||||||
- Improve ZIP exporting performance
|
|
||||||
- Expand issue and comment textarea for long text automatically
|
|
||||||
- Add conflict detection in Wiki
|
|
||||||
- Add reverting wiki page from history
|
|
||||||
- Match committer to user name by email address
|
|
||||||
- Mail notification sender is customizable
|
|
||||||
- Add link to changeset in refs comment for issues
|
|
||||||
- Fix some bugs
|
|
||||||
|
|
||||||
### 1.6 - 1 Oct 2013
|
|
||||||
- Web hook
|
|
||||||
- Performance improvement for pull request
|
|
||||||
- Executable war file
|
|
||||||
- Specify suitable Content-Type for downloaded files in the repository viewer
|
|
||||||
- Fix some bugs
|
|
||||||
|
|
||||||
### 1.5 - 4 Sep 2013
|
|
||||||
- Fork and pull request
|
|
||||||
- LDAP authentication
|
|
||||||
- Mail notification
|
|
||||||
- Add an option to turn off the gravatar support
|
|
||||||
- Add the branch tab in the repository viewer
|
|
||||||
- Encoding auto detection for the file content in the repository viewer
|
|
||||||
- Add favicon, header logo and icons for the timeline
|
|
||||||
- Specify data directory via environment variable GITBUCKET_HOME
|
|
||||||
- Fix some bugs
|
|
||||||
|
|
||||||
### 1.4 - 31 Jul 2013
|
|
||||||
- Group management
|
|
||||||
- Repository search for code and issues
|
|
||||||
- Display user related issues on the dashboard
|
|
||||||
- Display participants avatar of issues on the issue page
|
|
||||||
- Performance improvement for repository viewer
|
|
||||||
- Alert by milestone due date
|
|
||||||
- H2 database administration console
|
|
||||||
- Fix some bugs
|
|
||||||
|
|
||||||
### 1.3 - 18 Jul 2013
|
|
||||||
- Batch updating for issues
|
|
||||||
- Display assigned user on issue list
|
|
||||||
- User icon and Gravatar support
|
|
||||||
- Convert @xxxx to link to the account page
|
|
||||||
- Add copy to clipboard button for git clone URL
|
|
||||||
- Allow multi-byte characters as wiki page name
|
|
||||||
- Allow to create the empty repository
|
|
||||||
- Fix some bugs
|
|
||||||
|
|
||||||
### 1.2 - 09 Jul 2013
|
|
||||||
- Add activity timeline
|
|
||||||
- Bugfix for Git 1.8.1.5 or later
|
|
||||||
- Allow multi-byte characters as label
|
|
||||||
- Fix some bugs
|
|
||||||
|
|
||||||
### 1.1 - 05 Jul 2013
|
|
||||||
- Fix some bugs
|
|
||||||
- Upgrade to JGit 3.0
|
|
||||||
|
|
||||||
### 1.0 - 04 Jul 2013
|
|
||||||
- This is a first public release
|
|
||||||
|
|||||||
256
build.sbt
Normal file
256
build.sbt
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
import com.typesafe.sbt.license.{DepModuleInfo, LicenseInfo}
|
||||||
|
import com.typesafe.sbt.pgp.PgpKeys._
|
||||||
|
|
||||||
|
val Organization = "io.github.gitbucket"
|
||||||
|
val Name = "gitbucket"
|
||||||
|
val GitBucketVersion = "4.22.0"
|
||||||
|
val ScalatraVersion = "2.6.1"
|
||||||
|
val JettyVersion = "9.4.7.v20170914"
|
||||||
|
|
||||||
|
lazy val root = (project in file(".")).enablePlugins(SbtTwirl, ScalatraPlugin, JRebelPlugin).settings(
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
sourcesInBase := false
|
||||||
|
organization := Organization
|
||||||
|
name := Name
|
||||||
|
version := GitBucketVersion
|
||||||
|
scalaVersion := "2.12.4"
|
||||||
|
|
||||||
|
// 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.10.0.201712302008-r",
|
||||||
|
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.10.0.201712302008-r",
|
||||||
|
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
||||||
|
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
||||||
|
"org.scalatra" %% "scalatra-forms" % ScalatraVersion,
|
||||||
|
"org.json4s" %% "json4s-jackson" % "3.5.3",
|
||||||
|
"commons-io" % "commons-io" % "2.6",
|
||||||
|
"io.github.gitbucket" % "solidbase" % "1.0.2",
|
||||||
|
"io.github.gitbucket" % "markedj" % "1.0.15",
|
||||||
|
"org.apache.commons" % "commons-compress" % "1.15",
|
||||||
|
"org.apache.commons" % "commons-email" % "1.5",
|
||||||
|
"org.apache.httpcomponents" % "httpclient" % "4.5.4",
|
||||||
|
"org.apache.sshd" % "apache-sshd" % "1.6.0" exclude("org.slf4j","slf4j-jdk14"),
|
||||||
|
"org.apache.tika" % "tika-core" % "1.17",
|
||||||
|
"com.github.takezoe" %% "blocking-slick-32" % "0.0.10",
|
||||||
|
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||||
|
"com.h2database" % "h2" % "1.4.196",
|
||||||
|
"org.mariadb.jdbc" % "mariadb-java-client" % "2.2.2",
|
||||||
|
"org.postgresql" % "postgresql" % "42.1.4",
|
||||||
|
"ch.qos.logback" % "logback-classic" % "1.2.3",
|
||||||
|
"com.zaxxer" % "HikariCP" % "2.7.4",
|
||||||
|
"com.typesafe" % "config" % "1.3.2",
|
||||||
|
"com.typesafe.akka" %% "akka-actor" % "2.5.8",
|
||||||
|
"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.1.Final",
|
||||||
|
"com.enragedginger" %% "akka-quartz-scheduler" % "1.6.1-akka-2.5.x" exclude("c3p0","c3p0"),
|
||||||
|
"net.coobird" % "thumbnailator" % "0.4.8",
|
||||||
|
"com.github.zafarkhaja" % "java-semver" % "0.9.0",
|
||||||
|
"com.nimbusds" % "oauth2-oidc-sdk" % "5.45",
|
||||||
|
"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.13.0" % "test",
|
||||||
|
"com.wix" % "wix-embedded-mysql" % "3.0.0" % "test",
|
||||||
|
"ru.yandex.qatools.embed" % "postgresql-embedded" % "2.6" % "test",
|
||||||
|
"net.i2p.crypto" % "eddsa" % "0.2.0",
|
||||||
|
"is.tagomor.woothee" % "woothee-java" % "1.7.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 =>
|
||||||
|
if (path.endsWith(".jar")) {
|
||||||
|
// Legacy JRebel agent
|
||||||
|
Seq("-noverify", "-XX:+UseConcMarkSweepGC", "-XX:+CMSClassUnloadingEnabled", s"-javaagent:${path}")
|
||||||
|
} else {
|
||||||
|
// New JRebel agent
|
||||||
|
Seq(s"-agentpath:${path}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exclude a war file from published artifacts
|
||||||
|
signedArtifacts := {
|
||||||
|
signedArtifacts.value.filterNot { case (_, file) => file.getName.endsWith(".war") || file.getName.endsWith(".war.asc") }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.Attributes.{Name => AttrName}
|
||||||
|
import java.util.jar.{Manifest => JarManifest}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// include plugins
|
||||||
|
val pluginsDir = temp / "WEB-INF" / "classes" / "plugins"
|
||||||
|
IO createDirectory (pluginsDir)
|
||||||
|
IO copyFile(Keys.baseDirectory.value / "plugins.json", pluginsDir / "plugins.json")
|
||||||
|
|
||||||
|
val json = IO read(Keys.baseDirectory.value / "plugins.json")
|
||||||
|
PluginsJson.getUrls(json).foreach { url =>
|
||||||
|
log info s"Download: ${url}"
|
||||||
|
IO transfer(new java.net.URL(url).openStream, pluginsDir / url.substring(url.lastIndexOf("/") + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// zip it up
|
||||||
|
IO delete (temp / "META-INF" / "MANIFEST.MF")
|
||||||
|
val contentMappings = (temp.allPaths --- PathFinder(temp)).get pair { file => IO.relativizeFile(temp, file) }
|
||||||
|
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.map { case (file, path) => (file, path.toString) } , 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>
|
||||||
|
)
|
||||||
|
|
||||||
|
licenseOverrides := {
|
||||||
|
case DepModuleInfo("com.github.bkromhout", "java-diff-utils", _) =>
|
||||||
|
LicenseInfo(LicenseCategory.Apache, "Apache-2.0", "http://www.apache.org/licenses/LICENSE-2.0")
|
||||||
|
}
|
||||||
61
build.xml
61
build.xml
@@ -1,61 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<project name="gitbucket" default="all" basedir=".">
|
|
||||||
|
|
||||||
<property name="target.dir" value="target"/>
|
|
||||||
<property name="embed.classes.dir" value="${target.dir}/embed-classes"/>
|
|
||||||
<property name="jetty.dir" value="embed-jetty"/>
|
|
||||||
<property name="scala.version" value="2.11"/>
|
|
||||||
<property name="gitbucket.version" value="0.0.1"/>
|
|
||||||
<property name="jetty.version" value="8.1.8.v20121106"/>
|
|
||||||
<property name="servlet.version" value="3.0.0.v201112011016"/>
|
|
||||||
|
|
||||||
<condition property="sbt.exec" value="sbt.bat" else="sbt.sh">
|
|
||||||
<os family="windows" />
|
|
||||||
</condition>
|
|
||||||
|
|
||||||
<target name="clean">
|
|
||||||
<delete dir="${embed.classes.dir}"/>
|
|
||||||
<delete file="${target.dir}/scala-${scala.version}/gitbucket.war"/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="war" depends="clean">
|
|
||||||
<exec executable="${sbt.exec}" resolveexecutable="true" failonerror="true">
|
|
||||||
<arg line="clean compile test package" />
|
|
||||||
</exec>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="embed" depends="war">
|
|
||||||
<mkdir dir="${embed.classes.dir}"/>
|
|
||||||
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/javax.servlet-${servlet.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-continuation-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-http-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-io-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-security-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-server-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-servlet-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-util-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-webapp-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-xml-${jetty.version}.jar" />
|
|
||||||
|
|
||||||
<zip destfile="${target.dir}/scala-${scala.version}/gitbucket_${scala.version}-${gitbucket.version}.war"
|
|
||||||
basedir="${embed.classes.dir}"
|
|
||||||
update = "true"
|
|
||||||
includes="javax/**,org/**"/>
|
|
||||||
|
|
||||||
<zip destfile="${target.dir}/scala-${scala.version}/gitbucket_${scala.version}-${gitbucket.version}.war"
|
|
||||||
basedir="${target.dir}/scala-${scala.version}/classes"
|
|
||||||
update = "true"
|
|
||||||
includes="JettyLauncher.class,HttpsSupportConnector.class"/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="rename" depends="embed">
|
|
||||||
<move file="${target.dir}/scala-${scala.version}/gitbucket_${scala.version}-${gitbucket.version}.war"
|
|
||||||
tofile="${target.dir}/scala-${scala.version}/gitbucket.war"/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="all" depends="rename">
|
|
||||||
</target>
|
|
||||||
|
|
||||||
|
|
||||||
</project>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
%~d0
|
|
||||||
cmd /k cd %~p0
|
|
||||||
@@ -8,6 +8,6 @@ Common scripts are in this directory.
|
|||||||
This version of scripts has so far only been tested on Ubuntu and Mac. Someone else will have to test on RedHat.
|
This version of scripts has so far only been tested on Ubuntu and Mac. Someone else will have to test on RedHat.
|
||||||
|
|
||||||
To run:
|
To run:
|
||||||
1. Edit `gitbucket.conf` to suit.
|
|
||||||
2. Type: `install`
|
|
||||||
|
|
||||||
|
1. Edit `gitbucket.conf` to suit.
|
||||||
|
2. Type: `install`
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ GITBUCKET_WAR_DIR=$GITBUCKET_DIR/lib
|
|||||||
GITBUCKET_WAR_FILE=$GITBUCKET_WAR_DIR/gitbucket.war
|
GITBUCKET_WAR_FILE=$GITBUCKET_WAR_DIR/gitbucket.war
|
||||||
|
|
||||||
# GitBucket version to fetch when installing
|
# GitBucket version to fetch when installing
|
||||||
GITBUCKET_VERSION=2.1
|
GITBUCKET_VERSION=3.5
|
||||||
|
|
||||||
#
|
#
|
||||||
# End of configuration section. Ignore this part
|
# End of configuration section. Ignore this part
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ createDir "$GITBUCKET_DIR"
|
|||||||
createDir "$GITBUCKET_LOG_DIR"
|
createDir "$GITBUCKET_LOG_DIR"
|
||||||
|
|
||||||
echo "Fetching GitBucket v$GITBUCKET_VERSION and saving as $GITBUCKET_WAR_FILE"
|
echo "Fetching GitBucket v$GITBUCKET_VERSION and saving as $GITBUCKET_WAR_FILE"
|
||||||
sudo wget -qO "$GITBUCKET_WAR_FILE" https://github.com/takezoe/gitbucket/releases/download/$GITBUCKET_VERSION/gitbucket.war
|
sudo wget -qO "$GITBUCKET_WAR_FILE" https://github.com/gitbucket/gitbucket/releases/download/$GITBUCKET_VERSION/gitbucket.war
|
||||||
|
|
||||||
sudo rm -f "$GITBUCKET_LOG_DIR/run.log"
|
sudo rm -f "$GITBUCKET_LOG_DIR/run.log"
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Summary: GitHub clone written with Scala.
|
|||||||
Version: 2.6
|
Version: 2.6
|
||||||
Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
License: Apache
|
License: Apache
|
||||||
URL: https://github.com/takezoe/gitbucket
|
URL: https://github.com/gitbucket/gitbucket
|
||||||
Group: System/Servers
|
Group: System/Servers
|
||||||
Source0: %{name}.war
|
Source0: %{name}.war
|
||||||
Source1: %{name}.init
|
Source1: %{name}.init
|
||||||
|
|||||||
21
contrib/linux/redhat/selinux/gitbucket.te
Normal file
21
contrib/linux/redhat/selinux/gitbucket.te
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
module gitbucket 1.0;
|
||||||
|
|
||||||
|
require {
|
||||||
|
type smtp_port_t;
|
||||||
|
type tomcat_t;
|
||||||
|
type tomcat_var_lib_t;
|
||||||
|
type unreserved_port_t;
|
||||||
|
|
||||||
|
class file { execute };
|
||||||
|
class tcp_socket { name_bind };
|
||||||
|
class tcp_socket { name_connect };
|
||||||
|
}
|
||||||
|
|
||||||
|
# allow tomcat to send emails
|
||||||
|
allow tomcat_t smtp_port_t:tcp_socket { name_connect };
|
||||||
|
|
||||||
|
# allow file executes, required during repo creation
|
||||||
|
allow tomcat_t tomcat_var_lib_t:file { execute };
|
||||||
|
|
||||||
|
# allow tomcat to serve repositories via SSH
|
||||||
|
allow tomcat_t unreserved_port_t:tcp_socket { name_bind };
|
||||||
32
contrib/linux/redhat/selinux/readme.md
Normal file
32
contrib/linux/redhat/selinux/readme.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Red Hat Enterprise Linux / CentOS SELinux policy module for GitBucket
|
||||||
|
|
||||||
|
One way to run GitBucket on Enterprise Linux is under Tomcat. Since EL 7.4, Tomcat is no longer unconfined.
|
||||||
|
Thus since 7.4, Enterprise Linux blocks certain operations that are required for GitBucket to work properly:
|
||||||
|
|
||||||
|
* Tomcat is not allowed to connect to SMTP ports, which is required to send email notifications.
|
||||||
|
* Tomcat is not allowed to execute files, which is required for creating repositories.
|
||||||
|
* Tomcat is not allowed to act as a server on unreserved ports, which is required for serving repositories via SSH.
|
||||||
|
|
||||||
|
To mitigate this, you can use the SELinux policy module provided as `gitbucket.te`. You can deploy the module with the
|
||||||
|
attached script, e.g.:
|
||||||
|
|
||||||
|
~~~
|
||||||
|
./sedeploy.sh gitbucket
|
||||||
|
~~~
|
||||||
|
|
||||||
|
You most likely also need to fix file contexts on your system. Assuming a new, default Tomcat installation on 7.4, you
|
||||||
|
can do so by issuing the following commands:
|
||||||
|
|
||||||
|
~~~
|
||||||
|
GITBUCKET_HOME='/usr/share/tomcat/.gitbucket'
|
||||||
|
mkdir -p ${GITBUCKET_HOME}
|
||||||
|
chown tomcat.tomcat ${GITBUCKET_HOME}
|
||||||
|
semanage fcontext -a -t tomcat_var_lib_t "${GITBUCKET_HOME}(/.*)?"
|
||||||
|
restorecon -rv ${GITBUCKET_HOME}
|
||||||
|
|
||||||
|
JAVA_CONF='/usr/share/tomcat/.java'
|
||||||
|
mkdir -p ${JAVA_CONF}
|
||||||
|
chown tomcat.tomcat ${JAVA_CONF}
|
||||||
|
semanage fcontext -a -t tomcat_cache_t "${JAVA_CONF}(/.*)?"
|
||||||
|
restorecon -rv ${JAVA_CONF}
|
||||||
|
~~~
|
||||||
14
contrib/linux/redhat/selinux/sedeploy.sh
Executable file
14
contrib/linux/redhat/selinux/sedeploy.sh
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
MODULE=${1}
|
||||||
|
|
||||||
|
# this will create a .mod file
|
||||||
|
checkmodule -M -m -o ${MODULE}.mod ${MODULE}.te
|
||||||
|
|
||||||
|
# this will create a compiled semodule
|
||||||
|
semodule_package -m ${MODULE}.mod -o ${MODULE}.pp
|
||||||
|
|
||||||
|
# this will install the module
|
||||||
|
semodule -i ${MODULE}.pp
|
||||||
22
doc/activity.md
Normal file
22
doc/activity.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
Activity Timeline
|
||||||
|
========
|
||||||
|
GitBucket records several types of user activity to ```ACTIVITY``` table. Activity types are shown below:
|
||||||
|
|
||||||
|
type | message | additional information
|
||||||
|
------------------|------------------------------------------------------|------------------------
|
||||||
|
create_repository |$user created $owner/$repo |-
|
||||||
|
open_issue |$user opened issue $owner/$repo#$issueId |-
|
||||||
|
close_issue |$user closed issue $owner/$repo#$issueId |-
|
||||||
|
close_issue |$user closed pull request $owner/$repo#$issueId |-
|
||||||
|
reopen_issue |$user reopened issue $owner/$repo#$issueId |-
|
||||||
|
comment_issue |$user commented on issue $owner/$repo#$issueId |-
|
||||||
|
comment_issue |$user commented on pull request $owner/$repo#$issueId |-
|
||||||
|
create_wiki |$user created the $owner/$repo wiki |$page
|
||||||
|
edit_wiki |$user edited the $owner/$repo wiki |$page<br>$page:$commitId(since 1.5)
|
||||||
|
push |$user pushed to $owner/$repo#$branch to $owner/$repo |$commitId:$shortMessage\n*
|
||||||
|
create_tag |$user created tag $tag at $owner/$repo |-
|
||||||
|
create_branch |$user created branch $branch at $owner/$repo |-
|
||||||
|
delete_branch |$user deleted branch $branch at $owner/$repo |-
|
||||||
|
fork |$user forked $owner/$repo to $owner/$repo |-
|
||||||
|
open_pullreq |$user opened pull request $owner/$repo#issueId |-
|
||||||
|
merge_pullreq |$user merge pull request $owner/$repo#issueId |-
|
||||||
60
doc/authenticator.md
Normal file
60
doc/authenticator.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
Authentication in Controller
|
||||||
|
========
|
||||||
|
GitBucket provides many [authenticators](https://github.com/gitbucket/gitbucket/blob/master/src/main/scala/gitbucket/core/util/Authenticator.scala) to access controlling in the controller.
|
||||||
|
|
||||||
|
For example, in the case of `RepositoryViwerController`,
|
||||||
|
it references three authenticators: `ReadableUsersAuthenticator`, `ReferrerAuthenticator` and `CollaboratorsAuthenticator`.
|
||||||
|
|
||||||
|
```scala
|
||||||
|
class RepositoryViewerController extends RepositoryViewerControllerBase
|
||||||
|
with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
|
||||||
|
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with CommitStatusService
|
||||||
|
with WebHookPullRequestService with WebHookPullRequestReviewCommentService
|
||||||
|
|
||||||
|
trait RepositoryViewerControllerBase extends ControllerBase {
|
||||||
|
self: RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
|
||||||
|
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with CommitStatusService
|
||||||
|
with WebHookPullRequestService with WebHookPullRequestReviewCommentService =>
|
||||||
|
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Authenticators provides a method to add guard to actions in the controller:
|
||||||
|
|
||||||
|
- `ReadableUsersAuthenticator` provides `readableUsersOnly` method
|
||||||
|
- `ReferrerAuthenticator` provides `referrersOnly` method
|
||||||
|
- `CollaboratorsAuthenticator` provides `collaboratorsOnly` method
|
||||||
|
|
||||||
|
These methods are available in each actions as below:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
// Allows only the repository owner (or manager for group repository) and administrators.
|
||||||
|
get("/:owner/:repository/tree/*")(referrersOnly { repository =>
|
||||||
|
...
|
||||||
|
})
|
||||||
|
|
||||||
|
// Allows only collaborators and administrators.
|
||||||
|
get("/:owner/:repository/new/*")(collaboratorsOnly { repository =>
|
||||||
|
...
|
||||||
|
})
|
||||||
|
|
||||||
|
// Allows only signed in users which can access the repository.
|
||||||
|
post("/:owner/:repository/commit/:id/comment/new", commentForm)(readableUsersOnly { (form, repository) =>
|
||||||
|
...
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Currently, GitBucket provides below authenticators:
|
||||||
|
|
||||||
|
|Trait | Method | Description |
|
||||||
|
|--------------------------|-----------------|--------------------------------------------------------------------------------------|
|
||||||
|
|OneselfAuthenticator |oneselfOnly |Allows only oneself and administrators. |
|
||||||
|
|OwnerAuthenticator |ownerOnly |Allows only the repository owner and administrators. |
|
||||||
|
|UsersAuthenticator |usersOnly |Allows only signed in users. |
|
||||||
|
|AdminAuthenticator |adminOnly |Allows only administrators. |
|
||||||
|
|CollaboratorsAuthenticator|collaboratorsOnly|Allows only collaborators and administrators. |
|
||||||
|
|ReferrerAuthenticator |referrersOnly |Allows only the repository owner (or manager for group repository) and administrators.|
|
||||||
|
|ReadableUsersAuthenticator|readableUsersOnly|Allows only signed in users which can access the repository. |
|
||||||
|
|GroupManagerAuthenticator |managersOnly |Allows only the group managers. |
|
||||||
|
|
||||||
|
Of course, if you make a new plugin, you can define a your own authenticator according to requirement in your plugin.
|
||||||
54
doc/auto_update.md
Normal file
54
doc/auto_update.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
Automatic Schema Updating
|
||||||
|
========
|
||||||
|
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.GitBucketCoreModule](https://github.com/gitbucket/gitbucket/blob/master/src/main/scala/gitbucket/core/GitBucketCoreModule.scala) at first.
|
||||||
|
|
||||||
|
```scala
|
||||||
|
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")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
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).
|
||||||
81
doc/comment_action.md
Normal file
81
doc/comment_action.md
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
About Action in Issue Comment
|
||||||
|
========
|
||||||
|
After the issue creation at GitBucket, users can add comments or close it.
|
||||||
|
The details are saved at `ISSUE_COMMENT` table.
|
||||||
|
|
||||||
|
To determine if it was any operation, you see the `ACTION` column.
|
||||||
|
And in the case of some actions, `CONTENT` column value contains additional information.
|
||||||
|
|
||||||
|
|ACTION |CONTENT |
|
||||||
|
|----------------|----------------------|
|
||||||
|
|comment |comment |
|
||||||
|
|close_comment |comment |
|
||||||
|
|reopen_comment |comment |
|
||||||
|
|close |"Close" |
|
||||||
|
|reopen |"Reopen" |
|
||||||
|
|commit |comment commitId |
|
||||||
|
|merge |comment |
|
||||||
|
|delete_branch |branchName |
|
||||||
|
|refer |issueId:title |
|
||||||
|
|add_label |labelName |
|
||||||
|
|delete_label |labelName |
|
||||||
|
|change_priority |oldPriority:priority |
|
||||||
|
|change_milestone|oldMilestone:milestone|
|
||||||
|
|assign |oldAssigned:assigned |
|
||||||
|
|
||||||
|
### comment
|
||||||
|
|
||||||
|
This value is saved when users have made a normal comment.
|
||||||
|
|
||||||
|
### close_comment, reopen_comment
|
||||||
|
|
||||||
|
These values are saved when users have reopened or closed the issue with comments.
|
||||||
|
|
||||||
|
### close, reopen
|
||||||
|
|
||||||
|
These values are saved when users have reopened or closed the issue.
|
||||||
|
At the same time, store the fixed value(i.e. "Close" or "Reopen") to the `CONTENT` column.
|
||||||
|
Therefore, this comment is not displayed, and not counted as a comment.
|
||||||
|
|
||||||
|
### commit
|
||||||
|
|
||||||
|
This value is saved when users have pushed including the `#issueId` to the commit message.
|
||||||
|
At the same time, store it to the `CONTENT` column with its commit id.
|
||||||
|
This comment is displayed. But it can not be edited by all users, and also not counted as a comment.
|
||||||
|
|
||||||
|
### merge
|
||||||
|
|
||||||
|
This value is saved when users have merged the pull request.
|
||||||
|
At the same time, store the message to the `CONTENT` column.
|
||||||
|
This comment is displayed. But it can not be edited by all users, and also not counted as a comment.
|
||||||
|
|
||||||
|
### delete_branch
|
||||||
|
|
||||||
|
This value is saved when users have deleted the branch. Users can delete branch after merging pull request which is requested from the same repository.
|
||||||
|
At the same time, store it to the `CONTENT` column with the deleted branch name.
|
||||||
|
Therefore, this comment is not displayed, and not counted as a comment.
|
||||||
|
|
||||||
|
### refer
|
||||||
|
|
||||||
|
This value is saved when other issue or issue comment contains reference to the issue like `#issueId`.
|
||||||
|
At the same time, store id and title of the referrer issue as `id:title`.
|
||||||
|
|
||||||
|
### add_label
|
||||||
|
|
||||||
|
This value is saved when users have added the label.
|
||||||
|
|
||||||
|
### delete_label
|
||||||
|
|
||||||
|
This value is saved when users have deleted the label.
|
||||||
|
|
||||||
|
### change_priority
|
||||||
|
|
||||||
|
This value is saved when users have changed the priority.
|
||||||
|
|
||||||
|
### change_milestone
|
||||||
|
|
||||||
|
This value is saved when users have changed the milestone.
|
||||||
|
|
||||||
|
### assign
|
||||||
|
|
||||||
|
This value is saved when users have assign issue/PR to user or remove the assign.
|
||||||
44
doc/directory.md
Normal file
44
doc/directory.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
Directory Structure
|
||||||
|
========
|
||||||
|
GitBucket persists all data into __HOME/.gitbucket__ in default (In 1.9 or before, HOME/gitbucket is default).
|
||||||
|
|
||||||
|
This directory has following structure:
|
||||||
|
|
||||||
|
```
|
||||||
|
* /HOME/gitbucket
|
||||||
|
* /repositories
|
||||||
|
* /USER_NAME
|
||||||
|
* /REPO_NAME.git (substance of repository. GitServlet sees this directory)
|
||||||
|
* /REPO_NAME
|
||||||
|
* /issues (files which are attached to issue)
|
||||||
|
* /REPO_NAME.wiki.git (wiki repository)
|
||||||
|
* /data
|
||||||
|
* /USER_NAME
|
||||||
|
* /files
|
||||||
|
* avatar.xxx (image file of user avatar)
|
||||||
|
* /plugins
|
||||||
|
* /PLUGIN_NAME
|
||||||
|
* plugin.js
|
||||||
|
* /tmp
|
||||||
|
* /_upload
|
||||||
|
* /SESSION_ID (removed at session timeout)
|
||||||
|
* current time millis + random 10 alphanumeric chars (temporary file for file uploading)
|
||||||
|
* /USER_NAME
|
||||||
|
* /init-REPO_NAME (used in repository creation and removed after it) ... unused since 1.8
|
||||||
|
* /REPO_NAME.wiki (working directory for wiki repository) ... unused since 1.8
|
||||||
|
* /REPO_NAME
|
||||||
|
* /download (temporary directories are created under this directory)
|
||||||
|
```
|
||||||
|
|
||||||
|
There are some ways to specify the data directory instead of the default location.
|
||||||
|
|
||||||
|
1. Environment variable __GITBUCKET_HOME__
|
||||||
|
2. System property __gitbucket.home__ (e.g. ```-Dgitbucket.home=PATH_TO_DATADIR```)
|
||||||
|
3. Command line option for embedded Jetty (e.g. ```java -jar gitbucket.war --data=PATH_TO_DATADIR```)
|
||||||
|
4. Context parameter __gitbucket.home__ in web.xml like below:
|
||||||
|
```xml
|
||||||
|
<context-param>
|
||||||
|
<param-name>gitbucket.home</param-name>
|
||||||
|
<param-value>PATH_TO_DATADIR</param-value>
|
||||||
|
</context-param>
|
||||||
|
```
|
||||||
48
doc/how_to_run.md
Normal file
48
doc/how_to_run.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
How to run from the source tree
|
||||||
|
========
|
||||||
|
|
||||||
|
Install [sbt](http://www.scala-sbt.org/index.html) at first.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ brew install sbt
|
||||||
|
```
|
||||||
|
|
||||||
|
Run for Development
|
||||||
|
--------
|
||||||
|
|
||||||
|
If you want to test GitBucket, type the following command in the root directory of the source tree.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sbt ~jetty:start
|
||||||
|
```
|
||||||
|
|
||||||
|
Then access `http://localhost:8080/` in your browser. The default administrator account is `root` and password is `root`.
|
||||||
|
|
||||||
|
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
|
||||||
|
--------
|
||||||
|
|
||||||
|
To build war file, run the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sbt package
|
||||||
|
```
|
||||||
|
|
||||||
|
`gitbucket_2.12-x.x.x.war` is generated into `target/scala-2.12`.
|
||||||
|
|
||||||
|
To build an executable war file, run
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sbt executable
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
||||||
@@ -379,21 +379,21 @@
|
|||||||
<path d="M588.909,926.673 L560.094,910.545 L564.713,935.73 L588.909,926.673 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="1.313" stroke-linecap="round"/>
|
<path d="M588.909,926.673 L560.094,910.545 L564.713,935.73 L588.909,926.673 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="1.313" stroke-linecap="round"/>
|
||||||
</g>
|
</g>
|
||||||
<g id="rect3075-11">
|
<g id="rect3075-11">
|
||||||
<path d="M779.562,898.094 C779.396,912.75 779.229,927.406 779.062,942.062 C781.26,942.084 783.458,942.104 785.656,942.125 C784.104,943.688 782.552,945.25 781,946.813 C801.708,967.521 822.417,988.229 843.125,1008.938 C858.531,993.531 873.938,978.125 889.344,962.719 C868.635,942 847.927,921.281 827.219,900.563 C825.49,902.302 823.76,904.042 822.031,905.781 C822.052,903.386 822.073,900.99 822.094,898.594 C807.917,898.427 793.74,898.261 779.563,898.094 z" fill="#FFFFFF"/>
|
<path d="M1389.845,733.625 C1389.679,748.281 1389.512,762.937 1389.345,777.593 C1391.543,777.615 1393.741,777.635 1395.939,777.656 C1394.387,779.219 1392.835,780.781 1391.283,782.344 C1411.991,803.052 1432.7,823.76 1453.408,844.469 C1468.814,829.062 1484.221,813.656 1499.627,798.25 C1478.918,777.531 1458.21,756.812 1437.502,736.094 C1435.773,737.833 1434.043,739.573 1432.314,741.312 C1432.335,738.917 1432.356,736.521 1432.377,734.125 C1418.2,733.958 1404.023,733.792 1389.846,733.625 z" fill="#FFFFFF"/>
|
||||||
<path d="M779.562,898.094 C779.396,912.75 779.229,927.406 779.062,942.062 C781.26,942.084 783.458,942.104 785.656,942.125 C784.104,943.688 782.552,945.25 781,946.813 C801.708,967.521 822.417,988.229 843.125,1008.938 C858.531,993.531 873.938,978.125 889.344,962.719 C868.635,942 847.927,921.281 827.219,900.563 C825.49,902.302 823.76,904.042 822.031,905.781 C822.052,903.386 822.073,900.99 822.094,898.594 C807.917,898.427 793.74,898.261 779.563,898.094 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="9" stroke-linecap="round"/>
|
<path d="M1389.845,733.625 C1389.679,748.281 1389.512,762.937 1389.345,777.593 C1391.543,777.615 1393.741,777.635 1395.939,777.656 C1394.387,779.219 1392.835,780.781 1391.283,782.344 C1411.991,803.052 1432.7,823.76 1453.408,844.469 C1468.814,829.062 1484.221,813.656 1499.627,798.25 C1478.918,777.531 1458.21,756.812 1437.502,736.094 C1435.773,737.833 1434.043,739.573 1432.314,741.312 C1432.335,738.917 1432.356,736.521 1432.377,734.125 C1418.2,733.958 1404.023,733.792 1389.846,733.625 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="9" stroke-linecap="round"/>
|
||||||
</g>
|
</g>
|
||||||
<path d="M606.483,964.91 L606.483,951.243 L672.089,951.243 L672.089,964.91 z" fill="#B3B3B3" id="rect2995-0-2-8-6"/>
|
<path d="M606.483,964.91 L606.483,951.243 L672.089,951.243 L672.089,964.91 z" fill="#B3B3B3" id="rect2995-0-2-8-6"/>
|
||||||
<g id="rect3075-11-7">
|
<g id="rect3075-11-7">
|
||||||
<path d="M786.383,905.075 C786.256,916.229 786.129,927.383 786.003,938.537 C787.675,938.553 789.348,938.568 791.021,938.584 C789.839,939.773 788.658,940.963 787.477,942.152 C803.237,957.911 818.997,973.671 834.756,989.431 C846.481,977.706 858.206,965.982 869.93,954.257 C854.171,938.489 838.411,922.722 822.651,906.954 C821.335,908.278 820.019,909.602 818.703,910.926 C818.719,909.102 818.734,907.279 818.751,905.456 C807.961,905.329 797.172,905.202 786.383,905.075 z" fill="#FFFFFF"/>
|
<path d="M1396.666,740.606 C1396.539,751.76 1396.412,762.914 1396.286,774.068 C1397.958,774.084 1399.631,774.099 1401.304,774.115 C1400.122,775.304 1398.941,776.494 1397.76,777.683 C1413.52,793.442 1429.28,809.202 1445.039,824.962 C1456.764,813.237 1468.489,801.513 1480.213,789.788 C1464.454,774.02 1448.694,758.253 1432.934,742.485 C1431.618,743.809 1430.302,745.133 1428.986,746.457 C1429.002,744.633 1429.017,742.81 1429.034,740.987 C1418.244,740.86 1407.455,740.733 1396.666,740.606 z" fill="#FFFFFF"/>
|
||||||
<path d="M786.383,905.075 C786.256,916.229 786.129,927.383 786.003,938.537 C787.675,938.553 789.348,938.568 791.021,938.584 C789.839,939.773 788.658,940.963 787.477,942.152 C803.237,957.911 818.997,973.671 834.756,989.431 C846.481,977.706 858.206,965.982 869.93,954.257 C854.171,938.489 838.411,922.722 822.651,906.954 C821.335,908.278 820.019,909.602 818.703,910.926 C818.719,909.102 818.734,907.279 818.751,905.456 C807.961,905.329 797.172,905.202 786.383,905.075 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="6.849" stroke-linecap="round"/>
|
<path d="M1396.666,740.606 C1396.539,751.76 1396.412,762.914 1396.286,774.068 C1397.958,774.084 1399.631,774.099 1401.304,774.115 C1400.122,775.304 1398.941,776.494 1397.76,777.683 C1413.52,793.442 1429.28,809.202 1445.039,824.962 C1456.764,813.237 1468.489,801.513 1480.213,789.788 C1464.454,774.02 1448.694,758.253 1432.934,742.485 C1431.618,743.809 1430.302,745.133 1428.986,746.457 C1429.002,744.633 1429.017,742.81 1429.034,740.987 C1418.244,740.86 1407.455,740.733 1396.666,740.606 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="6.849" stroke-linecap="round"/>
|
||||||
</g>
|
</g>
|
||||||
<g id="path3100-2">
|
<g id="path3100-2">
|
||||||
<path d="M813.748,916.688 C818.255,921.195 818.255,928.501 813.748,933.008 C809.242,937.514 801.935,937.514 797.429,933.008 C792.922,928.501 792.922,921.195 797.429,916.688 C801.935,912.182 809.242,912.182 813.748,916.688 z" fill="#FFFFFF"/>
|
<path d="M1424.031,752.219 C1428.538,756.726 1428.538,764.032 1424.031,768.539 C1419.525,773.045 1412.218,773.045 1407.712,768.539 C1403.205,764.032 1403.205,756.726 1407.712,752.219 C1412.218,747.713 1419.525,747.713 1424.031,752.219 z" fill="#FFFFFF"/>
|
||||||
<path d="M813.748,916.688 C818.255,921.195 818.255,928.501 813.748,933.008 C809.242,937.514 801.935,937.514 797.429,933.008 C792.922,928.501 792.922,921.195 797.429,916.688 C801.935,912.182 809.242,912.182 813.748,916.688 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.585" stroke-linecap="round"/>
|
<path d="M1424.031,752.219 C1428.538,756.726 1428.538,764.032 1424.031,768.539 C1419.525,773.045 1412.218,773.045 1407.712,768.539 C1403.205,764.032 1403.205,756.726 1407.712,752.219 C1412.218,747.713 1419.525,747.713 1424.031,752.219 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.585" stroke-linecap="round"/>
|
||||||
</g>
|
</g>
|
||||||
<g id="rect4114">
|
<g id="rect4114">
|
||||||
<path d="M813.845,955.107 L834.888,934.064 L864.012,963.188 L842.969,984.231 z" fill="#FFFFFF"/>
|
<path d="M1424.128,790.638 L1445.171,769.595 L1474.295,798.719 L1453.252,819.762 z" fill="#FFFFFF"/>
|
||||||
<path d="M813.845,955.107 L834.888,934.064 L864.012,963.188 L842.969,984.231 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.133"/>
|
<path d="M1424.128,790.638 L1445.171,769.595 L1474.295,798.719 L1453.252,819.762 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.133"/>
|
||||||
</g>
|
</g>
|
||||||
<g id="path2991-7-6">
|
<g id="path2991-7-6">
|
||||||
<path d="M969.889,84.636 C969.889,122.652 939.072,153.469 901.056,153.469 C863.04,153.469 832.223,122.652 832.223,84.636 C832.223,46.62 863.04,15.803 901.056,15.803 C939.072,15.803 969.889,46.62 969.889,84.636 z" fill="#A0A0A0"/>
|
<path d="M969.889,84.636 C969.889,122.652 939.072,153.469 901.056,153.469 C863.04,153.469 832.223,122.652 832.223,84.636 C832.223,46.62 863.04,15.803 901.056,15.803 C939.072,15.803 969.889,46.62 969.889,84.636 z" fill="#A0A0A0"/>
|
||||||
@@ -750,5 +750,45 @@
|
|||||||
</g>
|
</g>
|
||||||
<path d="M1396.792,592.168 C1426.908,592.168 1450.613,610.989 1450.613,639.54" fill-opacity="0" stroke="#A0A0A0" stroke-width="20" stroke-linecap="round"/>
|
<path d="M1396.792,592.168 C1426.908,592.168 1450.613,610.989 1450.613,639.54" fill-opacity="0" stroke="#A0A0A0" stroke-width="20" stroke-linecap="round"/>
|
||||||
<path d="M1397.792,545.653 C1453.613,544.493 1499.627,588.735 1499.627,636.54" fill-opacity="0" stroke="#A0A0A0" stroke-width="20" stroke-linecap="round"/>
|
<path d="M1397.792,545.653 C1453.613,544.493 1499.627,588.735 1499.627,636.54" fill-opacity="0" stroke="#A0A0A0" stroke-width="20" stroke-linecap="round"/>
|
||||||
|
<path d="M871.125,1039.025 C871.125,1039.025 873.794,1016.889 908.043,1011.524 C919.748,1009.691 945.861,1005.107 945.861,978.522" fill-opacity="0" stroke="#A0A0A0" stroke-width="17.059" id="path3207"/>
|
||||||
|
<g id="rect3818-4-8-4">
|
||||||
|
<path d="M869.474,950.497 L871.997,950.497 L871.997,1031.308 L869.474,1031.308 z" fill="#A0A0A0"/>
|
||||||
|
<g>
|
||||||
|
<path d="M869.474,950.497 L871.997,950.497 L871.997,1031.308 L869.474,1031.308 z" fill="#A0A0A0"/>
|
||||||
|
<path d="M869.474,950.497 L871.997,950.497 L871.997,1031.308 L869.474,1031.308 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="15"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g id="path3795-4-8-7-8">
|
||||||
|
<path d="M888.074,1051.656 C888.074,1060.906 880.5,1068.405 871.159,1068.405 C861.817,1068.405 854.243,1060.906 854.243,1051.656 C854.243,1042.406 861.817,1034.908 871.159,1034.908 C880.5,1034.908 888.074,1042.406 888.074,1051.656 z" fill="#FFFFFF"/>
|
||||||
|
<path d="M888.074,1051.656 C888.074,1060.906 880.5,1068.405 871.159,1068.405 C861.817,1068.405 854.243,1060.906 854.243,1051.656 C854.243,1042.406 861.817,1034.908 871.159,1034.908 C880.5,1034.908 888.074,1042.406 888.074,1051.656 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="7.989"/>
|
||||||
|
</g>
|
||||||
|
<g id="path3795-8-4-8">
|
||||||
|
<path d="M886.883,935.155 C886.883,944.404 879.31,951.903 869.968,951.903 C860.626,951.903 853.054,944.404 853.054,935.155 C853.054,925.904 860.626,918.405 869.968,918.405 C879.31,918.405 886.883,925.904 886.883,935.155 z" fill="#FFFFFF"/>
|
||||||
|
<path d="M886.883,935.155 C886.883,944.404 879.31,951.903 869.968,951.903 C860.626,951.903 853.054,944.404 853.054,935.155 C853.054,925.904 860.626,918.405 869.968,918.405 C879.31,918.405 886.883,925.904 886.883,935.155 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="7.989"/>
|
||||||
|
</g>
|
||||||
|
<g id="path3795-8-4-8-2">
|
||||||
|
<path d="M965.046,971.602 C965.046,980.852 957.472,988.351 948.13,988.351 C938.789,988.351 931.215,980.852 931.215,971.602 C931.215,962.352 938.789,954.854 948.13,954.854 C957.472,954.854 965.046,962.352 965.046,971.602 z" fill="#FFFFFF"/>
|
||||||
|
<path d="M965.046,971.602 C965.046,980.852 957.472,988.351 948.13,988.351 C938.789,988.351 931.215,980.852 931.215,971.602 C931.215,962.352 938.789,954.854 948.13,954.854 C957.472,954.854 965.046,962.352 965.046,971.602 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="7.989"/>
|
||||||
|
</g>
|
||||||
|
<path d="M1114.353,1042.412 C1114.353,1042.412 1117.022,1020.275 1151.271,1014.91 C1162.976,1013.077 1189.089,1008.493 1189.089,981.909" fill-opacity="0" stroke="#000000" stroke-width="17.059" id="path3207"/>
|
||||||
|
<g id="rect3818-4-8-4">
|
||||||
|
<path d="M1112.701,953.884 L1115.225,953.884 L1115.225,1034.695 L1112.701,1034.695 z" fill="#A0A0A0"/>
|
||||||
|
<g>
|
||||||
|
<path d="M1112.701,953.884 L1115.225,953.884 L1115.225,1034.695 L1112.701,1034.695 z" fill="#A0A0A0"/>
|
||||||
|
<path d="M1112.701,953.884 L1115.225,953.884 L1115.225,1034.695 L1112.701,1034.695 z" fill-opacity="0" stroke="#000000" stroke-width="15"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g id="path3795-4-8-7-8">
|
||||||
|
<path d="M1131.302,1055.043 C1131.302,1064.293 1123.728,1071.791 1114.386,1071.791 C1105.045,1071.791 1097.471,1064.293 1097.471,1055.043 C1097.471,1045.792 1105.045,1038.294 1114.386,1038.294 C1123.728,1038.294 1131.302,1045.792 1131.302,1055.043 z" fill="#FFFFFF"/>
|
||||||
|
<path d="M1131.302,1055.043 C1131.302,1064.293 1123.728,1071.791 1114.386,1071.791 C1105.045,1071.791 1097.471,1064.293 1097.471,1055.043 C1097.471,1045.792 1105.045,1038.294 1114.386,1038.294 C1123.728,1038.294 1131.302,1045.792 1131.302,1055.043 z" fill-opacity="0" stroke="#000000" stroke-width="7.989"/>
|
||||||
|
</g>
|
||||||
|
<g id="path3795-8-4-8">
|
||||||
|
<path d="M1130.111,938.542 C1130.111,947.791 1122.537,955.29 1113.196,955.29 C1103.854,955.29 1096.282,947.791 1096.282,938.542 C1096.282,929.291 1103.854,921.792 1113.196,921.792 C1122.537,921.792 1130.111,929.291 1130.111,938.542 z" fill="#FFFFFF"/>
|
||||||
|
<path d="M1130.111,938.542 C1130.111,947.791 1122.537,955.29 1113.196,955.29 C1103.854,955.29 1096.282,947.791 1096.282,938.542 C1096.282,929.291 1103.854,921.792 1113.196,921.792 C1122.537,921.792 1130.111,929.291 1130.111,938.542 z" fill-opacity="0" stroke="#000000" stroke-width="7.989"/>
|
||||||
|
</g>
|
||||||
|
<g id="path3795-8-4-8-2">
|
||||||
|
<path d="M1208.274,974.989 C1208.274,984.239 1200.7,991.738 1191.358,991.738 C1182.016,991.738 1174.443,984.239 1174.443,974.989 C1174.443,965.739 1182.016,958.241 1191.358,958.241 C1200.7,958.241 1208.274,965.739 1208.274,974.989 z" fill="#FFFFFF"/>
|
||||||
|
<path d="M1208.274,974.989 C1208.274,984.239 1200.7,991.738 1191.358,991.738 C1182.016,991.738 1174.443,984.239 1174.443,974.989 C1174.443,965.739 1182.016,958.241 1191.358,958.241 C1200.7,958.241 1208.274,965.739 1208.274,974.989 z" fill-opacity="0" stroke="#000000" stroke-width="7.989"/>
|
||||||
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 82 KiB |
119
doc/jrebel.md
Normal file
119
doc/jrebel.md
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
JRebel integration (optional)
|
||||||
|
=============================
|
||||||
|
|
||||||
|
[JRebel](https://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 slow "app restart" per modification of codes. Alsp it's only used during development, and doesn't change your deployed app in any way.
|
||||||
|
|
||||||
|
JRebel is not open source, but we can use it free for non-commercial use.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## 1. Get a JRebel license
|
||||||
|
|
||||||
|
Sign up for a [myJRebel](https://my.jrebel.com/register). You will need to create an account.
|
||||||
|
|
||||||
|
## 2. Download JRebel
|
||||||
|
|
||||||
|
Download the most recent ["nosetup" JRebel zip](https://zeroturnaround.com/software/jrebel/download/prev-releases/).
|
||||||
|
Next, unzip the downloaded file.
|
||||||
|
|
||||||
|
## 3. Activate
|
||||||
|
|
||||||
|
Follow `readme.txt` in the extracted directory to activate your downloaded JRebel.
|
||||||
|
|
||||||
|
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 set the environment variable `JREBEL`.
|
||||||
|
For example, if you unzipped your JRebel download in your home directory, you would use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export JREBEL=~/jrebel/legacy/jrebel.jar # legacy agent
|
||||||
|
export JREBEL=~/jrebel/lib/libjrebel64.dylib # new agent
|
||||||
|
```
|
||||||
|
|
||||||
|
You can choose the legacy JRebel agent or the new one.
|
||||||
|
See [the document](https://zeroturnaround.com/software/jrebel/jrebel7-agent-upgrade-cli/) for details.
|
||||||
|
|
||||||
|
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:quickstart
|
||||||
|
:
|
||||||
|
2017-09-21 15:46:35 JRebel:
|
||||||
|
2017-09-21 15:46:35 JRebel: #############################################################
|
||||||
|
2017-09-21 15:46:35 JRebel:
|
||||||
|
2017-09-21 15:46:35 JRebel: Legacy Agent 7.0.15 (201709080836)
|
||||||
|
2017-09-21 15:46:35 JRebel: (c) Copyright ZeroTurnaround AS, Estonia, Tartu.
|
||||||
|
2017-09-21 15:46:35 JRebel:
|
||||||
|
2017-09-21 15:46:35 JRebel: Over the last 2 days JRebel prevented
|
||||||
|
2017-09-21 15:46:35 JRebel: at least 8 redeploys/restarts saving you about 0.3 hours.
|
||||||
|
2017-09-21 15:46:35 JRebel:
|
||||||
|
2017-09-21 15:46:35 JRebel: Licensed to Naoki Takezoe (using myJRebel).
|
||||||
|
2017-09-21 15:46:35 JRebel:
|
||||||
|
2017-09-21 15:46:35 JRebel:
|
||||||
|
2017-09-21 15:46:35 JRebel: #############################################################
|
||||||
|
2017-09-21 15:46:35 JRebel:
|
||||||
|
:
|
||||||
|
|
||||||
|
> ~compile
|
||||||
|
[success] Total time: 2 s, completed 2017/09/21 15:50:06
|
||||||
|
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 href="@context.path/" class="logo">
|
||||||
|
<img src="@helpers.assets("/common/images/gitbucket.svg")" style="width: 24px; height: 24px; display: inline;"/>
|
||||||
|
GitBucket
|
||||||
|
change code !!!!!!!!!!!!!!!!
|
||||||
|
<span class="header-version">@gitbucket.core.GitBucketCoreModule.getVersions.last.getVersion</span>
|
||||||
|
</a>
|
||||||
|
:
|
||||||
|
```
|
||||||
|
|
||||||
|
If JRebel is doing is correctly installed you will see a notice for you:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Waiting for source changes... (press enter to interrupt)
|
||||||
|
[info] Compiling 1 Scala source to /Users/naoki.takezoe/gitbucket/target/scala-2.12/classes...
|
||||||
|
[success] Total time: 1 s, completed 2017/09/21 15:55:40
|
||||||
|
```
|
||||||
|
|
||||||
|
And you reload browser, JRebel give notice of that it has reloaded classes:
|
||||||
|
|
||||||
|
```
|
||||||
|
2. Waiting for source changes... (press enter to interrupt)
|
||||||
|
2017-09-21 15:55:40 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 routing patterns, there is nothing JRebel can do, you will have to restart by `jetty:quickstart`.
|
||||||
101
doc/licenses.md
Normal file
101
doc/licenses.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# gitbucket-licenses
|
||||||
|
|
||||||
|
Category | License | Dependency | Notes
|
||||||
|
--- | --- | --- | ---
|
||||||
|
Apache | [ Apache License, Version 2.0 ]( http://opensource.org/licenses/apache2.0.php ) | org.osgi # org.osgi.core # 4.3.1 | <notextile></notextile>
|
||||||
|
Apache | [Apache 2](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.googlecode.javaewah # JavaEWAH # 1.1.6 | <notextile></notextile>
|
||||||
|
Apache | [Apache 2](http://www.apache.org/licenses/LICENSE-2.0) | org.cache2k # cache2k-all # 1.0.0.CR1 | <notextile></notextile>
|
||||||
|
Apache | [Apache 2](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.objenesis # objenesis # 2.5 | <notextile></notextile>
|
||||||
|
Apache | [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0) | org.apache.sshd # apache-sshd # 1.4.0 | <notextile></notextile>
|
||||||
|
Apache | [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0) | org.apache.sshd # sshd-core # 1.4.0 | <notextile></notextile>
|
||||||
|
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | com.typesafe # config # 1.3.1 | <notextile></notextile>
|
||||||
|
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | com.typesafe.akka # akka-actor_2.12 # 2.5.0 | <notextile></notextile>
|
||||||
|
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | commons-io # commons-io # 2.5 | <notextile></notextile>
|
||||||
|
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | fr.brouillard.oss.security.xhub # xhub4j-core # 1.0.0 | <notextile></notextile>
|
||||||
|
Apache | [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.commons # commons-compress # 1.13 | <notextile></notextile>
|
||||||
|
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.commons # commons-email # 1.4 | <notextile></notextile>
|
||||||
|
Apache | [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.commons # commons-lang3 # 3.5 | <notextile></notextile>
|
||||||
|
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.httpcomponents # httpclient # 4.5.3 | <notextile></notextile>
|
||||||
|
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.httpcomponents # httpcore # 4.4.6 | <notextile></notextile>
|
||||||
|
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.httpcomponents # httpmime # 4.5.2 | <notextile></notextile>
|
||||||
|
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.tika # tika-core # 1.14 | <notextile></notextile>
|
||||||
|
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.liquibase # liquibase-core # 3.4.1 | <notextile></notextile>
|
||||||
|
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-http # 9.2.19.v20160908 | <notextile></notextile>
|
||||||
|
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-io # 9.2.19.v20160908 | <notextile></notextile>
|
||||||
|
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-security # 9.2.19.v20160908 | <notextile></notextile>
|
||||||
|
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-server # 9.2.19.v20160908 | <notextile></notextile>
|
||||||
|
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-servlet # 9.2.19.v20160908 | <notextile></notextile>
|
||||||
|
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-util # 9.2.19.v20160908 | <notextile></notextile>
|
||||||
|
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-webapp # 9.2.19.v20160908 | <notextile></notextile>
|
||||||
|
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-xml # 9.2.19.v20160908 | <notextile></notextile>
|
||||||
|
Apache | [Apache Software License, Version 1.1](http://www.apache.org/licenses/LICENSE-1.1) | org.bouncycastle # bcpg-jdk15on # 1.56 | <notextile></notextile>
|
||||||
|
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | com.github.bkromhout # java-diff-utils # 2.1.1 | <notextile></notextile>
|
||||||
|
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.html) | com.typesafe.play # twirl-api_2.12 # 1.3.7 | <notextile></notextile>
|
||||||
|
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.json4s # json4s-ast_2.12 # 3.5.1 | <notextile></notextile>
|
||||||
|
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.json4s # json4s-core_2.12 # 3.5.1 | <notextile></notextile>
|
||||||
|
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.json4s # json4s-jackson_2.12 # 3.5.1 | <notextile></notextile>
|
||||||
|
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.json4s # json4s-scalap_2.12 # 3.5.1 | <notextile></notextile>
|
||||||
|
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.enragedginger # akka-quartz-scheduler_2.12 # 1.6.0-akka-2.4.x | <notextile></notextile>
|
||||||
|
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.fasterxml.jackson.core # jackson-annotations # 2.8.0 | <notextile></notextile>
|
||||||
|
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.fasterxml.jackson.core # jackson-core # 2.8.4 | <notextile></notextile>
|
||||||
|
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.fasterxml.jackson.core # jackson-databind # 2.8.4 | <notextile></notextile>
|
||||||
|
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.github.takezoe # blocking-slick-32_2.12 # 0.0.10 | <notextile></notextile>
|
||||||
|
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.google.code.findbugs # jsr305 # 3.0.0 | <notextile></notextile>
|
||||||
|
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.zaxxer # HikariCP # 2.6.1 | <notextile></notextile>
|
||||||
|
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | commons-codec # commons-codec # 1.9 | <notextile></notextile>
|
||||||
|
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | commons-logging # commons-logging # 1.2 | <notextile></notextile>
|
||||||
|
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | de.flapdoodle.embed # de.flapdoodle.embed.process # 2.0.1 | <notextile></notextile>
|
||||||
|
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | eu.medsea.mimeutil # mime-util # 2.1.3 | <notextile></notextile>
|
||||||
|
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | io.github.gitbucket # markedj # 1.0.15 | <notextile></notextile>
|
||||||
|
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | io.github.gitbucket # scalatra-forms_2.12 # 1.1.0 | <notextile></notextile>
|
||||||
|
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | io.github.gitbucket # solidbase # 1.0.2 | <notextile></notextile>
|
||||||
|
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | net.bytebuddy # byte-buddy # 1.6.11 | <notextile></notextile>
|
||||||
|
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | net.bytebuddy # byte-buddy-agent # 1.6.11 | <notextile></notextile>
|
||||||
|
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.quartz-scheduler # quartz # 2.2.3 | <notextile></notextile>
|
||||||
|
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | ru.yandex.qatools.embed # postgresql-embedded # 2.0 | <notextile></notextile>
|
||||||
|
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | tomcat # tomcat-apr # 5.5.23 | <notextile></notextile>
|
||||||
|
Apache | [the Apache License, ASL Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.scalactic # scalactic_2.12 # 3.0.0 | <notextile></notextile>
|
||||||
|
Apache | [the Apache License, ASL Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.scalatest # scalatest_2.12 # 3.0.0 | <notextile></notextile>
|
||||||
|
BSD | [BSD](LICENSE.txt) | com.thoughtworks.paranamer # paranamer # 2.8 | <notextile></notextile>
|
||||||
|
BSD | [BSD](http://software.clapper.org/grizzled-slf4j/license.html) | org.clapper # grizzled-slf4j_2.12 # 1.3.0 | <notextile></notextile>
|
||||||
|
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-common_2.12 # 2.5.0 | <notextile></notextile>
|
||||||
|
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-json_2.12 # 2.5.0 | <notextile></notextile>
|
||||||
|
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-scalatest_2.12 # 2.5.0 | <notextile></notextile>
|
||||||
|
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-test_2.12 # 2.5.0 | <notextile></notextile>
|
||||||
|
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra_2.12 # 2.5.0 | <notextile></notextile>
|
||||||
|
BSD | [BSD 3-Clause](http://www.scala-lang.org/license.html) | org.scala-lang # scala-library # 2.12.3 | <notextile></notextile>
|
||||||
|
BSD | [BSD 3-Clause](http://www.scala-lang.org/license.html) | org.scala-lang # scala-reflect # 2.12.3 | <notextile></notextile>
|
||||||
|
BSD | [BSD 3-clause](http://opensource.org/licenses/BSD-3-Clause) | org.scala-lang.modules # scala-java8-compat_2.12 # 0.8.0 | <notextile></notextile>
|
||||||
|
BSD | [BSD 3-clause](http://opensource.org/licenses/BSD-3-Clause) | org.scala-lang.modules # scala-parser-combinators_2.12 # 1.0.4 | <notextile></notextile>
|
||||||
|
BSD | [BSD 3-clause](http://opensource.org/licenses/BSD-3-Clause) | org.scala-lang.modules # scala-xml_2.12 # 1.0.6 | <notextile></notextile>
|
||||||
|
BSD | [BSD License](http://www.opensource.org/licenses/bsd-license.php) | com.wix # wix-embedded-mysql # 2.1.4 | <notextile></notextile>
|
||||||
|
BSD | [BSD-2-Clause](https://jdbc.postgresql.org/about/license.html) | org.postgresql # postgresql # 42.0.0 | <notextile></notextile>
|
||||||
|
BSD | [Eclipse Distribution License (New BSD License)](null) | org.eclipse.jgit # org.eclipse.jgit # 4.8.0.201706111038-r | <notextile></notextile>
|
||||||
|
BSD | [Eclipse Distribution License (New BSD License)](null) | org.eclipse.jgit # org.eclipse.jgit.archive # 4.8.0.201706111038-r | <notextile></notextile>
|
||||||
|
BSD | [Eclipse Distribution License (New BSD License)](null) | org.eclipse.jgit # org.eclipse.jgit.http.server # 4.8.0.201706111038-r | <notextile></notextile>
|
||||||
|
BSD | [New BSD License](http://www.opensource.org/licenses/bsd-license.php) | org.hamcrest # hamcrest-core # 1.3 | <notextile></notextile>
|
||||||
|
BSD | [Revised BSD](http://www.jcraft.com/jsch/LICENSE.txt) | com.jcraft # jsch # 0.1.54 | <notextile></notextile>
|
||||||
|
BSD | [Two-clause BSD-style license](http://github.com/slick/slick/blob/master/LICENSE.txt) | com.typesafe.slick # slick_2.12 # 3.2.1 | <notextile></notextile>
|
||||||
|
CC0 | [CC0](http://creativecommons.org/publicdomain/zero/1.0/) | org.reactivestreams # reactive-streams # 1.0.0 | <notextile></notextile>
|
||||||
|
CDDL | [COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0](https://glassfish.dev.java.net/public/CDDLv1.0.html) | javax.activation # activation # 1.1.1 | <notextile></notextile>
|
||||||
|
GPL | [CDDL/GPLv2+CE](https://glassfish.java.net/public/CDDL+GPL_1_1.html) | com.sun.mail # javax.mail # 1.5.2 | <notextile></notextile>
|
||||||
|
GPL with Classpath Extension | [CDDL + GPLv2 with classpath exception](https://glassfish.dev.java.net/nonav/public/CDDL+GPL.html) | javax.servlet # javax.servlet-api # 3.1.0 | <notextile></notextile>
|
||||||
|
LGPL | [GNU Lesser General Public License](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html) | ch.qos.logback # logback-classic # 1.2.3 | <notextile></notextile>
|
||||||
|
LGPL | [GNU Lesser General Public License](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html) | ch.qos.logback # logback-core # 1.2.3 | <notextile></notextile>
|
||||||
|
LGPL | [LGPL, version 2.1](http://www.gnu.org/licenses/licenses.html) | net.java.dev.jna # jna # 4.0.0 | <notextile></notextile>
|
||||||
|
LGPL | [LGPL, version 2.1](http://www.gnu.org/licenses/licenses.html) | net.java.dev.jna # jna-platform # 4.0.0 | <notextile></notextile>
|
||||||
|
LGPL | [LGPL-2.1](null) | org.mariadb.jdbc # mariadb-java-client # 2.0.3 | <notextile></notextile>
|
||||||
|
MIT | [MIT License](http://www.opensource.org/licenses/mit-license.php) | org.slf4j # slf4j-api # 1.7.25 | <notextile></notextile>
|
||||||
|
MIT | [The MIT License](http://www.opensource.org/licenses/mit-license.php) | com.github.zafarkhaja # java-semver # 0.9.0 | <notextile></notextile>
|
||||||
|
MIT | [The MIT License](https://jsoup.org/license) | org.jsoup # jsoup # 1.10.2 | <notextile></notextile>
|
||||||
|
MIT | [The MIT License](http://github.com/mockito/mockito/blob/master/LICENSE) | org.mockito # mockito-all # 1.10.19 | <notextile></notextile>
|
||||||
|
MIT | [The MIT License](http://github.com/mockito/mockito/blob/master/LICENSE) | org.mockito # mockito-core # 2.7.22 | <notextile></notextile>
|
||||||
|
MIT | [The MIT License (MIT)](http://www.opensource.org/licenses/mit-license.html) | net.coobird # thumbnailator # 0.4.8 | <notextile></notextile>
|
||||||
|
Mozilla | [MPL 2.0 or EPL 1.0](http://h2database.com/html/license.html) | com.h2database # h2 # 1.4.195 | <notextile></notextile>
|
||||||
|
Mozilla | [Mozilla Public License 1.1 (MPL 1.1)](http://www.mozilla.org/MPL/MPL-1.1.html) | com.googlecode.juniversalchardet # juniversalchardet # 1.0.3 | <notextile></notextile>
|
||||||
|
Public Domain | [Public Domain](http://en.wikipedia.org/wiki/Public_domain) | net.i2p.crypto # eddsa # 0.1.0 | <notextile></notextile>
|
||||||
|
unrecognized | [Bouncy Castle Licence](http://www.bouncycastle.org/licence.html) | org.bouncycastle # bcpkix-jdk15on # 1.56 | <notextile></notextile>
|
||||||
|
unrecognized | [Bouncy Castle Licence](http://www.bouncycastle.org/licence.html) | org.bouncycastle # bcprov-jdk15on # 1.56 | <notextile></notextile>
|
||||||
|
unrecognized | [Eclipse Public License 1.0](http://www.eclipse.org/legal/epl-v10.html) | junit # junit # 4.12 | <notextile></notextile>
|
||||||
|
unrecognized | [The OpenLDAP Public License](http://www.openldap.org/software/release/license.html) | com.novell.ldap # jldap # 2009-10-07 | <notextile></notextile>
|
||||||
|
|
||||||
12
doc/readme.md
Normal file
12
doc/readme.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
Developer's Guide
|
||||||
|
========
|
||||||
|
* [How to run from source tree](how_to_run.md)
|
||||||
|
* [Directory Structure](directory.md)
|
||||||
|
* [Mapping and Validation](validation.md)
|
||||||
|
* [Authentication in Controller](authenticator.md)
|
||||||
|
* [About Action in Issue Comment](comment_action.md)
|
||||||
|
* [Activity Types](activity.md)
|
||||||
|
* [Automatic Schema Updating](auto_update.md)
|
||||||
|
* [Release Operation](release.md)
|
||||||
|
* [JRebel integration (optional)](jrebel.md)
|
||||||
|
* [Licenses](licenses.md)
|
||||||
61
doc/release.md
Normal file
61
doc/release.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
Release Operation
|
||||||
|
========
|
||||||
|
|
||||||
|
Update version number
|
||||||
|
--------
|
||||||
|
|
||||||
|
Note to update version number in files below:
|
||||||
|
|
||||||
|
### build.sbt
|
||||||
|
|
||||||
|
```scala
|
||||||
|
val Organization = "gitbucket"
|
||||||
|
val Name = "gitbucket"
|
||||||
|
val GitBucketVersion = "4.0.0" // <---- update version!!
|
||||||
|
val ScalatraVersion = "2.4.0"
|
||||||
|
val JettyVersion = "9.3.6.v20151106"
|
||||||
|
```
|
||||||
|
|
||||||
|
### src/main/scala/gitbucket/core/GitBucketCoreModule.scala
|
||||||
|
|
||||||
|
```scala
|
||||||
|
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")
|
||||||
|
),
|
||||||
|
// add new version definition
|
||||||
|
new Version("4.1.0",
|
||||||
|
new LiquibaseMigration("update/gitbucket-core_4.1.xml")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Generate release files
|
||||||
|
--------
|
||||||
|
|
||||||
|
### Make release war file
|
||||||
|
|
||||||
|
Run `sbt executable`. The release war file and fingerprint are generated into `target/executable/gitbucket.war`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ sbt executable
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deploy assembly jar file
|
||||||
|
|
||||||
|
For plug-in development, we have to publish the GitBucket jar file to the Maven central repository as well. At first, hit following command to publish artifacts to the sonatype OSS repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ sbt publish-signed
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
71
doc/validation.md
Normal file
71
doc/validation.md
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
Mapping and Validation
|
||||||
|
========
|
||||||
|
GitBucket uses [scalatra-forms](https://github.com/takezoe/scalatra-forms) to validate request parameters and map them to the scala object. This is inspired by Play2 form mapping / validation.
|
||||||
|
|
||||||
|
At first, define the mapping as following:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
import jp.sf.amateras.scalatra.forms._
|
||||||
|
|
||||||
|
case class RegisterForm(name: String, description: String)
|
||||||
|
|
||||||
|
val form = mapping(
|
||||||
|
"name" -> text(required, maxlength(40)),
|
||||||
|
"description" -> text()
|
||||||
|
)(RegisterForm.apply)
|
||||||
|
```
|
||||||
|
|
||||||
|
The servlet have to mixed in ```jp.sf.amateras.scalatra.forms.ClientSideValidationFormSupport``` to validate request parameters and take mapped object. It validates request parameters before action. If any errors are detected, it throws an exception.
|
||||||
|
|
||||||
|
```scala
|
||||||
|
class RegisterServlet extends ScalatraServlet with ClientSideValidationFormSupport {
|
||||||
|
post("/register", form) { form: RegisterForm =>
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In the view template, you can add client-side validation by adding ```validate="true"``` to your form. Error messages are set to ```span#error-<fieldname>```.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<form method="POST" action="/register" validate="true">
|
||||||
|
Name: <input type="name" type="text">
|
||||||
|
<span class="error" id="error-name"></span>
|
||||||
|
<br/>
|
||||||
|
Description: <input type="description" type="text">
|
||||||
|
<span class="error" id="error-description"></span>
|
||||||
|
<br/>
|
||||||
|
<input type="submit" value="Register"/>
|
||||||
|
</form>
|
||||||
|
```
|
||||||
|
|
||||||
|
Client-side validation calls ```<form-action>/validate``` to validate form contents. It returns a validation result as JSON. In this case, form action is ```/register```, so ```/register/validate``` is called before submitting a form. ```ClientSideValidationFormSupport``` adds this JSON API automatically.
|
||||||
|
|
||||||
|
For Ajax request, you have to use '''ajaxGet''' or '''ajaxPost''' to define action. It almost same as '''get''' or '''post'''. You can implement actions which handle Ajax request as same as normal actions.
|
||||||
|
Small difference is they return validation errors as JSON.
|
||||||
|
|
||||||
|
```scala
|
||||||
|
ajaxPost("/register", form){ form =>
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can call these actions using jQuery as below:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
$('#register').click(function(e){
|
||||||
|
$.ajax($(this).attr('action'), {
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
name: $('#name').val(),
|
||||||
|
mail: $('#mail').val()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.done(function(data){
|
||||||
|
$('#result').text('Registered!');
|
||||||
|
})
|
||||||
|
.fail(function(data, status){
|
||||||
|
displayErrors($.parseJSON(data.responseText));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
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.
54
plugins.json
Normal file
54
plugins.json
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "notifications",
|
||||||
|
"name": "Notifications Plugin",
|
||||||
|
"description": "Provides notifications feature on GitBucket.",
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"version": "1.4.0",
|
||||||
|
"range": ">=4.19.0",
|
||||||
|
"url": "https://github.com/gitbucket/gitbucket-notifications-plugin/releases/download/1.4.0/gitbucket-notifications-plugin_2.12-1.4.0.jar"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "emoji",
|
||||||
|
"name": "Emoji Plugin",
|
||||||
|
"description": "Provides Emoji support for GitBucket.",
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"version": "4.5.0",
|
||||||
|
"range": ">=4.18.0",
|
||||||
|
"url": "https://github.com/gitbucket/gitbucket-emoji-plugin/releases/download/4.5.0/gitbucket-emoji-plugin_2.12-4.5.0.jar"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "gist",
|
||||||
|
"name": "Gist Plugin",
|
||||||
|
"description": "Provides Gist feature on GitBucket.",
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"version": "4.12.0",
|
||||||
|
"range": ">=4.21.0",
|
||||||
|
"url": "https://github.com/gitbucket/gitbucket-gist-plugin/releases/download/4.12.0/gitbucket-gist-plugin-assembly-4.12.0.jar"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pages",
|
||||||
|
"name": "Pages Plugin",
|
||||||
|
"description": "Project pages for gitbucket",
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"version": "1.6.0",
|
||||||
|
"range": ">=4.19.0",
|
||||||
|
"url": "https://github.com/gitbucket/gitbucket-pages-plugin/releases/download/v1.6.0/gitbucket-pages-plugin_2.12-1.6.0.jar"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
]
|
||||||
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 io._
|
||||||
|
|
||||||
|
object Checksums {
|
||||||
|
private val bufferSize = 2048
|
||||||
|
|
||||||
|
def generate(source:File, target:File, algorithm:String):Unit =
|
||||||
|
sbt.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 ""
|
||||||
|
}
|
||||||
16
project/PluginsJson.scala
Normal file
16
project/PluginsJson.scala
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import com.eclipsesource.json.Json
|
||||||
|
import scala.collection.JavaConverters._
|
||||||
|
|
||||||
|
object PluginsJson {
|
||||||
|
|
||||||
|
def getUrls(json: String): Seq[String] = {
|
||||||
|
val value = Json.parse(json)
|
||||||
|
value.asArray.values.asScala.map { plugin =>
|
||||||
|
val pluginObject = plugin.asObject
|
||||||
|
val latestVersionObject = pluginObject.get("versions").asArray.asScala.head.asObject
|
||||||
|
latestVersionObject.get("url").asString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1 +1 @@
|
|||||||
sbt.version=0.13.5
|
sbt.version=1.1.1
|
||||||
|
|||||||
1
project/build.sbt
Normal file
1
project/build.sbt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
libraryDependencies += "com.eclipsesource.minimal-json" % "minimal-json" % "0.9.4"
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import sbt._
|
|
||||||
import Keys._
|
|
||||||
import org.scalatra.sbt._
|
|
||||||
import com.typesafe.sbteclipse.plugin.EclipsePlugin.EclipseKeys
|
|
||||||
import play.twirl.sbt.SbtTwirl
|
|
||||||
import play.twirl.sbt.Import.TwirlKeys._
|
|
||||||
|
|
||||||
object MyBuild extends Build {
|
|
||||||
val Organization = "jp.sf.amateras"
|
|
||||||
val Name = "gitbucket"
|
|
||||||
val Version = "0.0.1"
|
|
||||||
val ScalaVersion = "2.11.2"
|
|
||||||
val ScalatraVersion = "2.3.0"
|
|
||||||
|
|
||||||
lazy val project = Project (
|
|
||||||
"gitbucket",
|
|
||||||
file(".")
|
|
||||||
)
|
|
||||||
.settings(ScalatraPlugin.scalatraWithJRebel: _*)
|
|
||||||
.settings(
|
|
||||||
sourcesInBase := false,
|
|
||||||
organization := Organization,
|
|
||||||
name := Name,
|
|
||||||
version := Version,
|
|
||||||
scalaVersion := ScalaVersion,
|
|
||||||
resolvers ++= Seq(
|
|
||||||
Classpaths.typesafeReleases,
|
|
||||||
"amateras-repo" at "http://amateras.sourceforge.jp/mvn/"
|
|
||||||
),
|
|
||||||
scalacOptions := Seq("-deprecation", "-language:postfixOps"),
|
|
||||||
libraryDependencies ++= Seq(
|
|
||||||
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "3.4.1.201406201815-r",
|
|
||||||
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "3.4.1.201406201815-r",
|
|
||||||
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
|
||||||
"org.scalatra" %% "scalatra-specs2" % ScalatraVersion % "test",
|
|
||||||
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
|
||||||
"org.json4s" %% "json4s-jackson" % "3.2.10",
|
|
||||||
"jp.sf.amateras" %% "scalatra-forms" % "0.1.0",
|
|
||||||
"commons-io" % "commons-io" % "2.4",
|
|
||||||
"org.pegdown" % "pegdown" % "1.4.1",
|
|
||||||
"org.apache.commons" % "commons-compress" % "1.5",
|
|
||||||
"org.apache.commons" % "commons-email" % "1.3.1",
|
|
||||||
"org.apache.httpcomponents" % "httpclient" % "4.3",
|
|
||||||
"org.apache.sshd" % "apache-sshd" % "0.11.0",
|
|
||||||
"com.typesafe.slick" %% "slick" % "2.1.0",
|
|
||||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
|
||||||
"org.quartz-scheduler" % "quartz" % "2.2.1",
|
|
||||||
"com.h2database" % "h2" % "1.4.180",
|
|
||||||
"ch.qos.logback" % "logback-classic" % "1.0.13" % "runtime",
|
|
||||||
"org.eclipse.jetty" % "jetty-webapp" % "8.1.8.v20121106" % "container;provided",
|
|
||||||
"org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container;provided;test" artifacts Artifact("javax.servlet", "jar", "jar"),
|
|
||||||
"junit" % "junit" % "4.11" % "test",
|
|
||||||
"com.typesafe.play" %% "twirl-compiler" % "1.0.2"
|
|
||||||
),
|
|
||||||
EclipseKeys.withSource := true,
|
|
||||||
javacOptions in compile ++= Seq("-target", "7", "-source", "7"),
|
|
||||||
testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "junitxml", "console"),
|
|
||||||
packageOptions += Package.MainClass("JettyLauncher")
|
|
||||||
).enablePlugins(SbtTwirl)
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.4.0")
|
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
||||||
|
|
||||||
addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.6.0")
|
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.3.13")
|
||||||
|
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.5")
|
||||||
addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.3.5")
|
//addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "4.0.0")
|
||||||
|
//addSbtPlugin("fi.gekkio.sbtplugins" % "sbt-jrebel-plugin" % "0.10.0")
|
||||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.2")
|
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.1")
|
||||||
|
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0")
|
||||||
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.4")
|
addSbtCoursier
|
||||||
|
addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0")
|
||||||
|
|||||||
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")
|
||||||
Binary file not shown.
2
sbt.bat
2
sbt.bat
@@ -1,2 +0,0 @@
|
|||||||
set SCRIPT_DIR=%~dp0
|
|
||||||
java -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar "%SCRIPT_DIR%\sbt-launch-0.13.5.jar" %*
|
|
||||||
2
sbt.sh
2
sbt.sh
@@ -1,2 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
java -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar `dirname $0`/sbt-launch-0.13.5.jar "$@"
|
|
||||||
@@ -1,55 +1,113 @@
|
|||||||
|
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]) {
|
||||||
host = dim[1];
|
case "--host":
|
||||||
} else if(dim[0].equals("--port")) {
|
host = dim[1];
|
||||||
port = Integer.parseInt(dim[1]);
|
break;
|
||||||
} else if(dim[0].equals("--prefix")) {
|
case "--port":
|
||||||
contextPath = dim[1];
|
port = Integer.parseInt(dim[1]);
|
||||||
} else if(dim[0].equals("--gitbucket.home")){
|
break;
|
||||||
System.setProperty("gitbucket.home", dim[1]);
|
case "--prefix":
|
||||||
|
contextPath = dim[1];
|
||||||
|
if (!contextPath.startsWith("/")) {
|
||||||
|
contextPath = "/" + contextPath;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "--max_file_size":
|
||||||
|
System.setProperty("gitbucket.maxFileSize", dim[1]);
|
||||||
|
break;
|
||||||
|
case "--gitbucket.home":
|
||||||
|
System.setProperty("gitbucket.home", dim[1]);
|
||||||
|
break;
|
||||||
|
case "--temp_dir":
|
||||||
|
tmpDirPath = dim[1];
|
||||||
|
break;
|
||||||
|
case "--plugin_dir":
|
||||||
|
System.setProperty("gitbucket.pluginDir", dim[1]);
|
||||||
|
break;
|
||||||
|
case "--validate_password":
|
||||||
|
System.setProperty("gitbucket.validate.password", 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();
|
||||||
|
}
|
||||||
|
} 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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
tmpDir.mkdirs();
|
|
||||||
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 +119,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();
|
||||||
}
|
}
|
||||||
@@ -78,14 +140,11 @@ public class JettyLauncher {
|
|||||||
return new File(System.getProperty("user.home"), ".gitbucket");
|
return new File(System.getProperty("user.home"), ".gitbucket");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void deleteDirectory(File dir){
|
private static Handler addStatisticsHandler(Handler handler) {
|
||||||
for(File file: dir.listFiles()){
|
// The graceful shutdown is implemented via the statistics handler.
|
||||||
if(file.isFile()){
|
// See the following: https://bugs.eclipse.org/bugs/show_bug.cgi?id=420142
|
||||||
file.delete();
|
final StatisticsHandler statisticsHandler = new StatisticsHandler();
|
||||||
} else if(file.isDirectory()){
|
statisticsHandler.setHandler(handler);
|
||||||
deleteDirectory(file);
|
return statisticsHandler;
|
||||||
}
|
|
||||||
}
|
|
||||||
dir.delete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package util;
|
package gitbucket.core.util;
|
||||||
|
|
||||||
import org.eclipse.jgit.api.errors.PatchApplyException;
|
import org.eclipse.jgit.api.errors.PatchApplyException;
|
||||||
import org.eclipse.jgit.diff.RawText;
|
import org.eclipse.jgit.diff.RawText;
|
||||||
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
26
src/main/resources/logback-dev.xml
Normal file
26
src/main/resources/logback-dev.xml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration>
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
||||||
|
<file>gitbucket.log</file>
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</root>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<logger name="service.WebHookService" level="DEBUG" />
|
||||||
|
<logger name="servlet" level="DEBUG" />
|
||||||
|
<logger name="scala.slick.jdbc.JdbcBackend.statement" level="DEBUG" />
|
||||||
|
-->
|
||||||
|
|
||||||
|
</configuration>
|
||||||
@@ -6,12 +6,23 @@
|
|||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
||||||
|
<file>gitbucket.log</file>
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
-->
|
||||||
|
|
||||||
<root level="INFO">
|
<root level="INFO">
|
||||||
<appender-ref ref="STDOUT" />
|
<appender-ref ref="STDOUT" />
|
||||||
</root>
|
</root>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
<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" />
|
||||||
-->
|
-->
|
||||||
|
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -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/takezoe/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);
|
|
||||||
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>
|
||||||
26
src/main/resources/update/gitbucket-core_4.14.sql
Normal file
26
src/main/resources/update/gitbucket-core_4.14.sql
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
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,
|
||||||
|
COALESCE(D.ORDERING, 9999) AS PRIORITY
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
LEFT OUTER JOIN PRIORITY D
|
||||||
|
ON (A.PRIORITY_ID = D.PRIORITY_ID);
|
||||||
38
src/main/resources/update/gitbucket-core_4.14.xml
Normal file
38
src/main/resources/update/gitbucket-core_4.14.xml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<changeSet>
|
||||||
|
<createTable tableName="PRIORITY">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="PRIORITY_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="PRIORITY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="DESCRIPTION" type="varchar(255)" nullable="true"/>
|
||||||
|
<column name="ORDERING" type="int" nullable="false"/>
|
||||||
|
<column name="IS_DEFAULT" type="boolean" nullable="false"/>
|
||||||
|
<column name="COLOR" type="char(6)" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_PRIORITY_PK" tableName="PRIORITY" columnNames="USER_NAME, REPOSITORY_NAME, PRIORITY_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_PRIORITY_FK0" baseTableName="PRIORITY" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
|
||||||
|
<addColumn tableName="ISSUE">
|
||||||
|
<column name="PRIORITY_ID" type="int" nullable="true" />
|
||||||
|
</addColumn>
|
||||||
|
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ISSUE_FK3" baseTableName="ISSUE" baseColumnNames="PRIORITY_ID" referencedTableName="PRIORITY" referencedColumnNames="PRIORITY_ID"/>
|
||||||
|
|
||||||
|
<createTable tableName="ACCOUNT_WEB_HOOK">
|
||||||
|
<column name="USER_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_ACCOUNT_WEB_HOOK_PK" tableName="ACCOUNT_WEB_HOOK" columnNames="USER_NAME, URL"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ACCOUNT_WEB_HOOK_FK0" baseTableName="ACCOUNT_WEB_HOOK" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||||
|
|
||||||
|
<createTable tableName="ACCOUNT_WEB_HOOK_EVENT">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="URL" type="varchar(200)" nullable="false"/>
|
||||||
|
<column name="EVENT" type="varchar(30)" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
</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>
|
||||||
39
src/main/resources/update/gitbucket-core_4.21.xml
Normal file
39
src/main/resources/update/gitbucket-core_4.21.xml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<changeSet>
|
||||||
|
<createTable tableName="RELEASE">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="TAG" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="AUTHOR" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="CONTENT" type="text" nullable="true"/>
|
||||||
|
<column name="REGISTERED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_RELEASE_PK" tableName="RELEASE" columnNames="USER_NAME, REPOSITORY_NAME, TAG"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_RELEASE_FK0" baseTableName="RELEASE" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
|
||||||
|
<createTable tableName="RELEASE_ASSET">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="TAG" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="RELEASE_ASSET_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="FILE_NAME" type="varchar(260)" nullable="false"/>
|
||||||
|
<column name="LABEL" type="varchar(100)" nullable="true"/>
|
||||||
|
<column name="SIZE" type="bigint" nullable="false"/>
|
||||||
|
<column name="UPLOADER" 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_RELEASE_ASSET_PK" tableName="RELEASE_ASSET" columnNames="USER_NAME, REPOSITORY_NAME, TAG, FILE_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_RELEASE_ASSET_FK1" baseTableName="RELEASE_ASSET" baseColumnNames="USER_NAME, REPOSITORY_NAME, TAG" referencedTableName="RELEASE" referencedColumnNames="USER_NAME, REPOSITORY_NAME, TAG"/>
|
||||||
|
|
||||||
|
<createTable tableName="ACCOUNT_FEDERATION">
|
||||||
|
<column name="ISSUER" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="SUBJECT" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
<addPrimaryKey constraintName="IDX_ACCOUNT_FEDERATION_PK" tableName="ACCOUNT_FEDERATION" columnNames="ISSUER, SUBJECT"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ACCOUNT_FEDERATION_FK0" baseTableName="ACCOUNT_FEDERATION" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||||
|
</changeSet>
|
||||||
7
src/main/resources/update/gitbucket-core_4.22.xml
Normal file
7
src/main/resources/update/gitbucket-core_4.22.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<changeSet>
|
||||||
|
<addColumn tableName="REPOSITORY">
|
||||||
|
<column name="MERGE_OPTIONS" type="varchar(200)" nullable="false" defaultValue="merge-commit,squash,rebase"/>
|
||||||
|
<column name="DEFAULT_MERGE_OPTION" type="varchar(100)" nullable="false" defaultValue="merge-commit"/>
|
||||||
|
</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,40 +1,66 @@
|
|||||||
import _root_.servlet.{PluginActionInvokeFilter, BasicAuthenticationFilter, TransactionFilter}
|
|
||||||
import app._
|
|
||||||
//import jp.sf.amateras.scalatra.forms.ValidationJavaScriptProvider
|
|
||||||
import org.scalatra._
|
|
||||||
import javax.servlet._
|
|
||||||
import java.util.EnumSet
|
|
||||||
|
|
||||||
class ScalatraBootstrap extends LifeCycle {
|
import java.util.EnumSet
|
||||||
|
import javax.servlet._
|
||||||
|
|
||||||
|
import gitbucket.core.controller.{ReleaseController, _}
|
||||||
|
import gitbucket.core.plugin.PluginRegistry
|
||||||
|
import gitbucket.core.service.SystemSettingsService
|
||||||
|
import gitbucket.core.servlet._
|
||||||
|
import gitbucket.core.util.Directory
|
||||||
|
import org.scalatra._
|
||||||
|
|
||||||
|
|
||||||
|
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("pluginActionInvokeFilter", new PluginActionInvokeFilter)
|
context.addFilter("gitAuthenticationFilter", new GitAuthenticationFilter)
|
||||||
context.getFilterRegistration("pluginActionInvokeFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
context.getFilterRegistration("gitAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*")
|
||||||
context.addFilter("basicAuthenticationFilter", new BasicAuthenticationFilter)
|
context.addFilter("apiAuthenticationFilter", new ApiAuthenticationFilter)
|
||||||
context.getFilterRegistration("basicAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*")
|
context.getFilterRegistration("apiAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/api/v3/*")
|
||||||
|
|
||||||
// Register controllers
|
// Register controllers
|
||||||
context.mount(new IndexController, "/")
|
context.mount(new PreProcessController, "/*")
|
||||||
context.mount(new SearchController, "/")
|
|
||||||
|
context.addFilter("pluginControllerFilter", new PluginControllerFilter)
|
||||||
|
context.getFilterRegistration("pluginControllerFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
||||||
|
|
||||||
context.mount(new FileUploadController, "/upload")
|
context.mount(new FileUploadController, "/upload")
|
||||||
context.mount(new DashboardController, "/*")
|
|
||||||
context.mount(new UserManagementController, "/*")
|
val filter = new CompositeScalatraFilter()
|
||||||
context.mount(new SystemSettingsController, "/*")
|
filter.mount(new IndexController, "/")
|
||||||
context.mount(new AccountController, "/*")
|
filter.mount(new ApiController, "/api/v3")
|
||||||
context.mount(new RepositoryViewerController, "/*")
|
filter.mount(new SystemSettingsController, "/admin")
|
||||||
context.mount(new WikiController, "/*")
|
filter.mount(new DashboardController, "/*")
|
||||||
context.mount(new LabelsController, "/*")
|
filter.mount(new AccountController, "/*")
|
||||||
context.mount(new MilestonesController, "/*")
|
filter.mount(new RepositoryViewerController, "/*")
|
||||||
context.mount(new IssuesController, "/*")
|
filter.mount(new WikiController, "/*")
|
||||||
context.mount(new PullRequestsController, "/*")
|
filter.mount(new LabelsController, "/*")
|
||||||
context.mount(new RepositorySettingsController, "/*")
|
filter.mount(new PrioritiesController, "/*")
|
||||||
|
filter.mount(new MilestonesController, "/*")
|
||||||
|
filter.mount(new IssuesController, "/*")
|
||||||
|
filter.mount(new PullRequestsController, "/*")
|
||||||
|
filter.mount(new ReleaseController, "/*")
|
||||||
|
filter.mount(new RepositorySettingsController, "/*")
|
||||||
|
|
||||||
|
context.addFilter("compositeScalatraFilter", filter)
|
||||||
|
context.getFilterRegistration("compositeScalatraFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
||||||
|
|
||||||
// Create GITBUCKET_HOME directory if it does not exist
|
// Create GITBUCKET_HOME directory if it does not exist
|
||||||
val dir = new java.io.File(_root_.util.Directory.GitBucketHome)
|
val dir = new java.io.File(Directory.GitBucketHome)
|
||||||
if(!dir.exists){
|
if(!dir.exists){
|
||||||
dir.mkdirs()
|
dir.mkdirs()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
override def destroy(context: ServletContext): Unit = {
|
||||||
|
Database.closeDataSource()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,434 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import service._
|
|
||||||
import util._
|
|
||||||
import util.StringUtil._
|
|
||||||
import util.Directory._
|
|
||||||
import util.ControlUtil._
|
|
||||||
import util.Implicits._
|
|
||||||
import ssh.SshUtil
|
|
||||||
import jp.sf.amateras.scalatra.forms._
|
|
||||||
import org.apache.commons.io.FileUtils
|
|
||||||
import org.scalatra.i18n.Messages
|
|
||||||
import org.eclipse.jgit.api.Git
|
|
||||||
import org.eclipse.jgit.lib.{FileMode, Constants}
|
|
||||||
import org.eclipse.jgit.dircache.DirCache
|
|
||||||
import model.GroupMember
|
|
||||||
|
|
||||||
class AccountController extends AccountControllerBase
|
|
||||||
with AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
|
|
||||||
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
|
|
||||||
|
|
||||||
trait AccountControllerBase extends AccountManagementControllerBase {
|
|
||||||
self: AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
|
|
||||||
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator =>
|
|
||||||
|
|
||||||
case class AccountNewForm(userName: String, password: String, fullName: String, mailAddress: String,
|
|
||||||
url: Option[String], fileId: Option[String])
|
|
||||||
|
|
||||||
case class AccountEditForm(password: Option[String], fullName: String, mailAddress: String,
|
|
||||||
url: Option[String], fileId: Option[String], clearImage: Boolean)
|
|
||||||
|
|
||||||
case class SshKeyForm(title: String, publicKey: String)
|
|
||||||
|
|
||||||
val newForm = mapping(
|
|
||||||
"userName" -> trim(label("User name" , text(required, maxlength(100), identifier, uniqueUserName))),
|
|
||||||
"password" -> trim(label("Password" , text(required, maxlength(20)))),
|
|
||||||
"fullName" -> trim(label("Full Name" , text(required, maxlength(100)))),
|
|
||||||
"mailAddress" -> trim(label("Mail Address" , text(required, maxlength(100), uniqueMailAddress()))),
|
|
||||||
"url" -> trim(label("URL" , optional(text(maxlength(200))))),
|
|
||||||
"fileId" -> trim(label("File ID" , optional(text())))
|
|
||||||
)(AccountNewForm.apply)
|
|
||||||
|
|
||||||
val editForm = mapping(
|
|
||||||
"password" -> trim(label("Password" , optional(text(maxlength(20))))),
|
|
||||||
"fullName" -> trim(label("Full Name" , text(required, maxlength(100)))),
|
|
||||||
"mailAddress" -> trim(label("Mail Address" , text(required, maxlength(100), uniqueMailAddress("userName")))),
|
|
||||||
"url" -> trim(label("URL" , optional(text(maxlength(200))))),
|
|
||||||
"fileId" -> trim(label("File ID" , optional(text()))),
|
|
||||||
"clearImage" -> trim(label("Clear image" , boolean()))
|
|
||||||
)(AccountEditForm.apply)
|
|
||||||
|
|
||||||
val sshKeyForm = mapping(
|
|
||||||
"title" -> trim(label("Title", text(required, maxlength(100)))),
|
|
||||||
"publicKey" -> trim(label("Key" , text(required, validPublicKey)))
|
|
||||||
)(SshKeyForm.apply)
|
|
||||||
|
|
||||||
case class NewGroupForm(groupName: String, url: Option[String], fileId: Option[String], members: String)
|
|
||||||
case class EditGroupForm(groupName: String, url: Option[String], fileId: Option[String], members: String, clearImage: Boolean)
|
|
||||||
|
|
||||||
val newGroupForm = mapping(
|
|
||||||
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier, uniqueUserName))),
|
|
||||||
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
|
||||||
"fileId" -> trim(label("File ID" ,optional(text()))),
|
|
||||||
"members" -> trim(label("Members" ,text(required, members)))
|
|
||||||
)(NewGroupForm.apply)
|
|
||||||
|
|
||||||
val editGroupForm = mapping(
|
|
||||||
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier))),
|
|
||||||
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
|
||||||
"fileId" -> trim(label("File ID" ,optional(text()))),
|
|
||||||
"members" -> trim(label("Members" ,text(required, members))),
|
|
||||||
"clearImage" -> trim(label("Clear image" ,boolean()))
|
|
||||||
)(EditGroupForm.apply)
|
|
||||||
|
|
||||||
case class RepositoryCreationForm(owner: String, name: String, description: Option[String], isPrivate: Boolean, createReadme: Boolean)
|
|
||||||
case class ForkRepositoryForm(owner: String, name: String)
|
|
||||||
|
|
||||||
val newRepositoryForm = mapping(
|
|
||||||
"owner" -> trim(label("Owner" , text(required, maxlength(40), identifier, existsAccount))),
|
|
||||||
"name" -> trim(label("Repository name", text(required, maxlength(40), identifier, uniqueRepository))),
|
|
||||||
"description" -> trim(label("Description" , optional(text()))),
|
|
||||||
"isPrivate" -> trim(label("Repository Type", boolean())),
|
|
||||||
"createReadme" -> trim(label("Create README" , boolean()))
|
|
||||||
)(RepositoryCreationForm.apply)
|
|
||||||
|
|
||||||
val forkRepositoryForm = mapping(
|
|
||||||
"owner" -> trim(label("Repository owner", text(required))),
|
|
||||||
"name" -> trim(label("Repository name", text(required)))
|
|
||||||
)(ForkRepositoryForm.apply)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays user information.
|
|
||||||
*/
|
|
||||||
get("/:userName") {
|
|
||||||
val userName = params("userName")
|
|
||||||
getAccountByUserName(userName).map { account =>
|
|
||||||
params.getOrElse("tab", "repositories") match {
|
|
||||||
// Public Activity
|
|
||||||
case "activity" =>
|
|
||||||
_root_.account.html.activity(account,
|
|
||||||
if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
|
||||||
getActivitiesByUser(userName, true))
|
|
||||||
|
|
||||||
// Members
|
|
||||||
case "members" if(account.isGroupAccount) => {
|
|
||||||
val members = getGroupMembers(account.userName)
|
|
||||||
_root_.account.html.members(account, members.map(_.userName),
|
|
||||||
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Repositories
|
|
||||||
case _ => {
|
|
||||||
val members = getGroupMembers(account.userName)
|
|
||||||
_root_.account.html.repositories(account,
|
|
||||||
if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
|
||||||
getVisibleRepositories(context.loginAccount, context.baseUrl, Some(userName)),
|
|
||||||
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} getOrElse NotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
get("/:userName.atom") {
|
|
||||||
val userName = params("userName")
|
|
||||||
contentType = "application/atom+xml; type=feed"
|
|
||||||
helper.xml.feed(getActivitiesByUser(userName, true))
|
|
||||||
}
|
|
||||||
|
|
||||||
get("/:userName/_avatar"){
|
|
||||||
val userName = params("userName")
|
|
||||||
getAccountByUserName(userName).flatMap(_.image).map { image =>
|
|
||||||
contentType = FileUtil.getMimeType(image)
|
|
||||||
new java.io.File(getUserUploadDir(userName), image)
|
|
||||||
} getOrElse {
|
|
||||||
contentType = "image/png"
|
|
||||||
Thread.currentThread.getContextClassLoader.getResourceAsStream("noimage.png")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get("/:userName/_edit")(oneselfOnly {
|
|
||||||
val userName = params("userName")
|
|
||||||
getAccountByUserName(userName).map { x =>
|
|
||||||
account.html.edit(x, flash.get("info"))
|
|
||||||
} getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/:userName/_edit", editForm)(oneselfOnly { form =>
|
|
||||||
val userName = params("userName")
|
|
||||||
getAccountByUserName(userName).map { account =>
|
|
||||||
updateAccount(account.copy(
|
|
||||||
password = form.password.map(sha1).getOrElse(account.password),
|
|
||||||
fullName = form.fullName,
|
|
||||||
mailAddress = form.mailAddress,
|
|
||||||
url = form.url))
|
|
||||||
|
|
||||||
updateImage(userName, form.fileId, form.clearImage)
|
|
||||||
flash += "info" -> "Account information has been updated."
|
|
||||||
redirect(s"/${userName}/_edit")
|
|
||||||
|
|
||||||
} getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:userName/_delete")(oneselfOnly {
|
|
||||||
val userName = params("userName")
|
|
||||||
|
|
||||||
getAccountByUserName(userName, true).foreach { account =>
|
|
||||||
// Remove repositories
|
|
||||||
getRepositoryNamesOfUser(userName).foreach { repositoryName =>
|
|
||||||
deleteRepository(userName, repositoryName)
|
|
||||||
FileUtils.deleteDirectory(getRepositoryDir(userName, repositoryName))
|
|
||||||
FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
|
|
||||||
FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
|
|
||||||
}
|
|
||||||
// Remove from GROUP_MEMBER, COLLABORATOR and REPOSITORY
|
|
||||||
removeUserRelatedData(userName)
|
|
||||||
|
|
||||||
updateAccount(account.copy(isRemoved = true))
|
|
||||||
}
|
|
||||||
|
|
||||||
session.invalidate
|
|
||||||
redirect("/")
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:userName/_ssh")(oneselfOnly {
|
|
||||||
val userName = params("userName")
|
|
||||||
getAccountByUserName(userName).map { x =>
|
|
||||||
account.html.ssh(x, getPublicKeys(x.userName))
|
|
||||||
} getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/:userName/_ssh", sshKeyForm)(oneselfOnly { form =>
|
|
||||||
val userName = params("userName")
|
|
||||||
addPublicKey(userName, form.title, form.publicKey)
|
|
||||||
redirect(s"/${userName}/_ssh")
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:userName/_ssh/delete/:id")(oneselfOnly {
|
|
||||||
val userName = params("userName")
|
|
||||||
val sshKeyId = params("id").toInt
|
|
||||||
deletePublicKey(userName, sshKeyId)
|
|
||||||
redirect(s"/${userName}/_ssh")
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/register"){
|
|
||||||
if(context.settings.allowAccountRegistration){
|
|
||||||
if(context.loginAccount.isDefined){
|
|
||||||
redirect("/")
|
|
||||||
} else {
|
|
||||||
account.html.register()
|
|
||||||
}
|
|
||||||
} else NotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
post("/register", newForm){ form =>
|
|
||||||
if(context.settings.allowAccountRegistration){
|
|
||||||
createAccount(form.userName, sha1(form.password), form.fullName, form.mailAddress, false, form.url)
|
|
||||||
updateImage(form.userName, form.fileId, false)
|
|
||||||
redirect("/signin")
|
|
||||||
} else NotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
get("/groups/new")(usersOnly {
|
|
||||||
account.html.group(None, List(GroupMember("", context.loginAccount.get.userName, true)))
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/groups/new", newGroupForm)(usersOnly { form =>
|
|
||||||
createGroup(form.groupName, form.url)
|
|
||||||
updateGroupMembers(form.groupName, form.members.split(",").map {
|
|
||||||
_.split(":") match {
|
|
||||||
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
|
||||||
}
|
|
||||||
}.toList)
|
|
||||||
updateImage(form.groupName, form.fileId, false)
|
|
||||||
redirect(s"/${form.groupName}")
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:groupName/_editgroup")(managersOnly {
|
|
||||||
defining(params("groupName")){ groupName =>
|
|
||||||
account.html.group(getAccountByUserName(groupName, true), getGroupMembers(groupName))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:groupName/_deletegroup")(managersOnly {
|
|
||||||
defining(params("groupName")){ groupName =>
|
|
||||||
// Remove from GROUP_MEMBER
|
|
||||||
updateGroupMembers(groupName, Nil)
|
|
||||||
// Remove repositories
|
|
||||||
getRepositoryNamesOfUser(groupName).foreach { repositoryName =>
|
|
||||||
deleteRepository(groupName, repositoryName)
|
|
||||||
FileUtils.deleteDirectory(getRepositoryDir(groupName, repositoryName))
|
|
||||||
FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName))
|
|
||||||
FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
redirect("/")
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/:groupName/_editgroup", editGroupForm)(managersOnly { form =>
|
|
||||||
defining(params("groupName"), form.members.split(",").map {
|
|
||||||
_.split(":") match {
|
|
||||||
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
|
||||||
}
|
|
||||||
}.toList){ case (groupName, members) =>
|
|
||||||
getAccountByUserName(groupName, true).map { account =>
|
|
||||||
updateGroup(groupName, form.url, false)
|
|
||||||
|
|
||||||
// Update GROUP_MEMBER
|
|
||||||
updateGroupMembers(form.groupName, members)
|
|
||||||
// Update COLLABORATOR for group repositories
|
|
||||||
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
|
||||||
removeCollaborators(form.groupName, repositoryName)
|
|
||||||
members.foreach { case (userName, isManager) =>
|
|
||||||
addCollaborator(form.groupName, repositoryName, userName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateImage(form.groupName, form.fileId, form.clearImage)
|
|
||||||
redirect(s"/${form.groupName}")
|
|
||||||
|
|
||||||
} getOrElse NotFound
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the new repository form.
|
|
||||||
*/
|
|
||||||
get("/new")(usersOnly {
|
|
||||||
account.html.newrepo(getGroupsByUserName(context.loginAccount.get.userName))
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create new repository.
|
|
||||||
*/
|
|
||||||
post("/new", newRepositoryForm)(usersOnly { form =>
|
|
||||||
LockUtil.lock(s"${form.owner}/${form.name}"){
|
|
||||||
if(getRepository(form.owner, form.name, context.baseUrl).isEmpty){
|
|
||||||
val ownerAccount = getAccountByUserName(form.owner).get
|
|
||||||
val loginAccount = context.loginAccount.get
|
|
||||||
val loginUserName = loginAccount.userName
|
|
||||||
|
|
||||||
// Insert to the database at first
|
|
||||||
createRepository(form.name, form.owner, form.description, form.isPrivate)
|
|
||||||
|
|
||||||
// Add collaborators for group repository
|
|
||||||
if(ownerAccount.isGroupAccount){
|
|
||||||
getGroupMembers(form.owner).foreach { member =>
|
|
||||||
addCollaborator(form.owner, form.name, member.userName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert default labels
|
|
||||||
insertDefaultLabels(form.owner, form.name)
|
|
||||||
|
|
||||||
// Create the actual repository
|
|
||||||
val gitdir = getRepositoryDir(form.owner, form.name)
|
|
||||||
JGitUtil.initRepository(gitdir)
|
|
||||||
|
|
||||||
if(form.createReadme){
|
|
||||||
using(Git.open(gitdir)){ git =>
|
|
||||||
val builder = DirCache.newInCore.builder()
|
|
||||||
val inserter = git.getRepository.newObjectInserter()
|
|
||||||
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
|
|
||||||
val content = if(form.description.nonEmpty){
|
|
||||||
form.name + "\n" +
|
|
||||||
"===============\n" +
|
|
||||||
"\n" +
|
|
||||||
form.description.get
|
|
||||||
} else {
|
|
||||||
form.name + "\n" +
|
|
||||||
"===============\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.add(JGitUtil.createDirCacheEntry("README.md", FileMode.REGULAR_FILE,
|
|
||||||
inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8"))))
|
|
||||||
builder.finish()
|
|
||||||
|
|
||||||
JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
|
|
||||||
Constants.HEAD, loginAccount.fullName, loginAccount.mailAddress, "Initial commit")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Wiki repository
|
|
||||||
createWikiRepository(loginAccount, form.owner, form.name)
|
|
||||||
|
|
||||||
// Record activity
|
|
||||||
recordCreateRepositoryActivity(form.owner, form.name, loginUserName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// redirect to the repository
|
|
||||||
redirect(s"/${form.owner}/${form.name}")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/fork")(readableUsersOnly { repository =>
|
|
||||||
val loginAccount = context.loginAccount.get
|
|
||||||
val loginUserName = loginAccount.userName
|
|
||||||
|
|
||||||
LockUtil.lock(s"${loginUserName}/${repository.name}"){
|
|
||||||
if(repository.owner == loginUserName || getRepository(loginAccount.userName, repository.name, baseUrl).isDefined){
|
|
||||||
// redirect to the repository if repository already exists
|
|
||||||
redirect(s"/${loginUserName}/${repository.name}")
|
|
||||||
} else {
|
|
||||||
// Insert to the database at first
|
|
||||||
val originUserName = repository.repository.originUserName.getOrElse(repository.owner)
|
|
||||||
val originRepositoryName = repository.repository.originRepositoryName.getOrElse(repository.name)
|
|
||||||
|
|
||||||
createRepository(
|
|
||||||
repositoryName = repository.name,
|
|
||||||
userName = loginUserName,
|
|
||||||
description = repository.repository.description,
|
|
||||||
isPrivate = repository.repository.isPrivate,
|
|
||||||
originRepositoryName = Some(originRepositoryName),
|
|
||||||
originUserName = Some(originUserName),
|
|
||||||
parentRepositoryName = Some(repository.name),
|
|
||||||
parentUserName = Some(repository.owner)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Insert default labels
|
|
||||||
insertDefaultLabels(loginUserName, repository.name)
|
|
||||||
|
|
||||||
// clone repository actually
|
|
||||||
JGitUtil.cloneRepository(
|
|
||||||
getRepositoryDir(repository.owner, repository.name),
|
|
||||||
getRepositoryDir(loginUserName, repository.name))
|
|
||||||
|
|
||||||
// Create Wiki repository
|
|
||||||
JGitUtil.cloneRepository(
|
|
||||||
getWikiRepositoryDir(repository.owner, repository.name),
|
|
||||||
getWikiRepositoryDir(loginUserName, repository.name))
|
|
||||||
|
|
||||||
// Record activity
|
|
||||||
recordForkActivity(repository.owner, repository.name, loginUserName)
|
|
||||||
// redirect to the repository
|
|
||||||
redirect(s"/${loginUserName}/${repository.name}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
private def insertDefaultLabels(userName: String, repositoryName: String): Unit = {
|
|
||||||
createLabel(userName, repositoryName, "bug", "fc2929")
|
|
||||||
createLabel(userName, repositoryName, "duplicate", "cccccc")
|
|
||||||
createLabel(userName, repositoryName, "enhancement", "84b6eb")
|
|
||||||
createLabel(userName, repositoryName, "invalid", "e6e6e6")
|
|
||||||
createLabel(userName, repositoryName, "question", "cc317c")
|
|
||||||
createLabel(userName, repositoryName, "wontfix", "ffffff")
|
|
||||||
}
|
|
||||||
|
|
||||||
private def existsAccount: Constraint = new Constraint(){
|
|
||||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
|
||||||
if(getAccountByUserName(value).isEmpty) Some("User or group does not exist.") else None
|
|
||||||
}
|
|
||||||
|
|
||||||
private def uniqueRepository: Constraint = new Constraint(){
|
|
||||||
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] =
|
|
||||||
params.get("owner").flatMap { userName =>
|
|
||||||
getRepositoryNamesOfUser(userName).find(_ == value).map(_ => "Repository already exists.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def members: Constraint = new Constraint(){
|
|
||||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
|
||||||
if(value.split(",").exists {
|
|
||||||
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
|
|
||||||
}) None else Some("Must select one manager at least.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def validPublicKey: Constraint = new Constraint(){
|
|
||||||
override def validate(name: String, value: String, messages: Messages): Option[String] = SshUtil.str2PublicKey(value) match {
|
|
||||||
case Some(_) => None
|
|
||||||
case None => Some("Key is invalid.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import _root_.util.Directory._
|
|
||||||
import _root_.util.Implicits._
|
|
||||||
import _root_.util.ControlUtil._
|
|
||||||
import _root_.util.{StringUtil, FileUtil, Validations, Keys}
|
|
||||||
import org.scalatra._
|
|
||||||
import org.scalatra.json._
|
|
||||||
import org.json4s._
|
|
||||||
import jp.sf.amateras.scalatra.forms._
|
|
||||||
import org.apache.commons.io.FileUtils
|
|
||||||
import model._
|
|
||||||
import service.{SystemSettingsService, AccountService}
|
|
||||||
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
|
|
||||||
import javax.servlet.{FilterChain, ServletResponse, ServletRequest}
|
|
||||||
import org.scalatra.i18n._
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides generic features for controller implementations.
|
|
||||||
*/
|
|
||||||
abstract class ControllerBase extends ScalatraFilter
|
|
||||||
with ClientSideValidationFormSupport with JacksonJsonSupport with I18nSupport with FlashMapSupport with Validations
|
|
||||||
with SystemSettingsService {
|
|
||||||
|
|
||||||
implicit val jsonFormats = DefaultFormats
|
|
||||||
|
|
||||||
// TODO Scala 2.11
|
|
||||||
// // Don't set content type via Accept header.
|
|
||||||
// override def format(implicit request: HttpServletRequest) = ""
|
|
||||||
|
|
||||||
override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = try {
|
|
||||||
val httpRequest = request.asInstanceOf[HttpServletRequest]
|
|
||||||
val httpResponse = response.asInstanceOf[HttpServletResponse]
|
|
||||||
val context = request.getServletContext.getContextPath
|
|
||||||
val path = httpRequest.getRequestURI.substring(context.length)
|
|
||||||
|
|
||||||
if(path.startsWith("/console/")){
|
|
||||||
val account = httpRequest.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]
|
|
||||||
val baseUrl = this.baseUrl(httpRequest)
|
|
||||||
if(account == null){
|
|
||||||
// Redirect to login form
|
|
||||||
httpResponse.sendRedirect(baseUrl + "/signin?redirect=" + StringUtil.urlEncode(path))
|
|
||||||
} else if(account.isAdmin){
|
|
||||||
// H2 Console (administrators only)
|
|
||||||
chain.doFilter(request, response)
|
|
||||||
} else {
|
|
||||||
// Redirect to dashboard
|
|
||||||
httpResponse.sendRedirect(baseUrl + "/")
|
|
||||||
}
|
|
||||||
} else if(path.startsWith("/git/")){
|
|
||||||
// Git repository
|
|
||||||
chain.doFilter(request, response)
|
|
||||||
} else {
|
|
||||||
// Scalatra actions
|
|
||||||
super.doFilter(request, response, chain)
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
contextCache.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
private val contextCache = new java.lang.ThreadLocal[Context]()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the context object for the request.
|
|
||||||
*/
|
|
||||||
implicit def context: Context = {
|
|
||||||
contextCache.get match {
|
|
||||||
case null => {
|
|
||||||
val context = Context(loadSystemSettings(), LoginAccount, request)
|
|
||||||
contextCache.set(context)
|
|
||||||
context
|
|
||||||
}
|
|
||||||
case context => context
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def LoginAccount: Option[Account] = session.getAs[Account](Keys.Session.LoginAccount)
|
|
||||||
|
|
||||||
def ajaxGet(path : String)(action : => Any) : Route =
|
|
||||||
super.get(path){
|
|
||||||
request.setAttribute(Keys.Request.Ajax, "true")
|
|
||||||
action
|
|
||||||
}
|
|
||||||
|
|
||||||
override def ajaxGet[T](path : String, form : ValueType[T])(action : T => Any) : Route =
|
|
||||||
super.ajaxGet(path, form){ form =>
|
|
||||||
request.setAttribute(Keys.Request.Ajax, "true")
|
|
||||||
action(form)
|
|
||||||
}
|
|
||||||
|
|
||||||
def ajaxPost(path : String)(action : => Any) : Route =
|
|
||||||
super.post(path){
|
|
||||||
request.setAttribute(Keys.Request.Ajax, "true")
|
|
||||||
action
|
|
||||||
}
|
|
||||||
|
|
||||||
override def ajaxPost[T](path : String, form : ValueType[T])(action : T => Any) : Route =
|
|
||||||
super.ajaxPost(path, form){ form =>
|
|
||||||
request.setAttribute(Keys.Request.Ajax, "true")
|
|
||||||
action(form)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected def NotFound() =
|
|
||||||
if(request.hasAttribute(Keys.Request.Ajax)){
|
|
||||||
org.scalatra.NotFound()
|
|
||||||
} else {
|
|
||||||
org.scalatra.NotFound(html.error("Not Found"))
|
|
||||||
}
|
|
||||||
|
|
||||||
protected def Unauthorized()(implicit context: app.Context) =
|
|
||||||
if(request.hasAttribute(Keys.Request.Ajax)){
|
|
||||||
org.scalatra.Unauthorized()
|
|
||||||
} else {
|
|
||||||
if(context.loginAccount.isDefined){
|
|
||||||
org.scalatra.Unauthorized(redirect("/"))
|
|
||||||
} else {
|
|
||||||
if(request.getMethod.toUpperCase == "POST"){
|
|
||||||
org.scalatra.Unauthorized(redirect("/signin"))
|
|
||||||
} else {
|
|
||||||
org.scalatra.Unauthorized(redirect("/signin?redirect=" + StringUtil.urlEncode(
|
|
||||||
defining(request.getQueryString){ queryString =>
|
|
||||||
request.getRequestURI.substring(request.getContextPath.length) + (if(queryString != null) "?" + queryString else "")
|
|
||||||
}
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Scala 2.11
|
|
||||||
override def url(path: String, params: Iterable[(String, Any)] = Iterable.empty,
|
|
||||||
includeContextPath: Boolean = true, includeServletPath: Boolean = true,
|
|
||||||
absolutize: Boolean = true, withSessionId: Boolean = true)
|
|
||||||
(implicit request: HttpServletRequest, response: HttpServletResponse): String =
|
|
||||||
if (path.startsWith("http")) path
|
|
||||||
else baseUrl + super.url(path, params, false, false, false)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Context object for the current request.
|
|
||||||
*/
|
|
||||||
case class Context(settings: SystemSettingsService.SystemSettings, loginAccount: Option[Account], request: HttpServletRequest){
|
|
||||||
|
|
||||||
val path = settings.baseUrl.getOrElse(request.getContextPath)
|
|
||||||
val currentPath = request.getRequestURI.substring(request.getContextPath.length)
|
|
||||||
val baseUrl = settings.baseUrl(request)
|
|
||||||
val host = new java.net.URL(baseUrl).getHost
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get object from cache.
|
|
||||||
*
|
|
||||||
* If object has not been cached with the specified key then retrieves by given action.
|
|
||||||
* Cached object are available during a request.
|
|
||||||
*/
|
|
||||||
def cache[A](key: String)(action: => A): A =
|
|
||||||
defining(Keys.Request.Cache(key)){ cacheKey =>
|
|
||||||
Option(request.getAttribute(cacheKey).asInstanceOf[A]).getOrElse {
|
|
||||||
val newObject = action
|
|
||||||
request.setAttribute(cacheKey, newObject)
|
|
||||||
newObject
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base trait for controllers which manages account information.
|
|
||||||
*/
|
|
||||||
trait AccountManagementControllerBase extends ControllerBase {
|
|
||||||
self: AccountService =>
|
|
||||||
|
|
||||||
protected def updateImage(userName: String, fileId: Option[String], clearImage: Boolean): Unit =
|
|
||||||
if(clearImage){
|
|
||||||
getAccountByUserName(userName).flatMap(_.image).map { image =>
|
|
||||||
new java.io.File(getUserUploadDir(userName), image).delete()
|
|
||||||
updateAvatarImage(userName, None)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fileId.map { fileId =>
|
|
||||||
val filename = "avatar." + FileUtil.getExtension(session.getAndRemove(Keys.Session.Upload(fileId)).get)
|
|
||||||
FileUtils.moveFile(
|
|
||||||
new java.io.File(getTemporaryDir(session.getId), fileId),
|
|
||||||
new java.io.File(getUserUploadDir(userName), filename)
|
|
||||||
)
|
|
||||||
updateAvatarImage(userName, Some(filename))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected def uniqueUserName: Constraint = new Constraint(){
|
|
||||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
|
||||||
getAccountByUserName(value, true).map { _ => "User already exists." }
|
|
||||||
}
|
|
||||||
|
|
||||||
protected def uniqueMailAddress(paramName: String = ""): Constraint = new Constraint(){
|
|
||||||
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] =
|
|
||||||
getAccountByMailAddress(value, true)
|
|
||||||
.filter { x => if(paramName.isEmpty) true else Some(x.userName) != params.get(paramName) }
|
|
||||||
.map { _ => "Mail address is already registered." }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import service._
|
|
||||||
import util.{StringUtil, UsersAuthenticator, Keys}
|
|
||||||
import util.Implicits._
|
|
||||||
import service.IssuesService.IssueSearchCondition
|
|
||||||
|
|
||||||
class DashboardController extends DashboardControllerBase
|
|
||||||
with IssuesService with PullRequestService with RepositoryService with AccountService
|
|
||||||
with UsersAuthenticator
|
|
||||||
|
|
||||||
trait DashboardControllerBase extends ControllerBase {
|
|
||||||
self: IssuesService with PullRequestService with RepositoryService with AccountService
|
|
||||||
with UsersAuthenticator =>
|
|
||||||
|
|
||||||
get("/dashboard/issues")(usersOnly {
|
|
||||||
val q = request.getParameter("q")
|
|
||||||
val account = context.loginAccount.get
|
|
||||||
Option(q).map { q =>
|
|
||||||
val condition = IssueSearchCondition(q, Map[String, Int]())
|
|
||||||
q match {
|
|
||||||
case q if(q.contains("is:pr")) => redirect(s"/dashboard/pulls?q=${StringUtil.urlEncode(q)}")
|
|
||||||
case q if(q.contains(s"author:${account.userName}")) => redirect(s"/dashboard/issues/created_by${condition.toURL}")
|
|
||||||
case q if(q.contains(s"assignee:${account.userName}")) => redirect(s"/dashboard/issues/assigned${condition.toURL}")
|
|
||||||
case q if(q.contains(s"mentions:${account.userName}")) => redirect(s"/dashboard/issues/mentioned${condition.toURL}")
|
|
||||||
case _ => searchIssues("created_by")
|
|
||||||
}
|
|
||||||
} getOrElse {
|
|
||||||
searchIssues("created_by")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/dashboard/issues/assigned")(usersOnly {
|
|
||||||
searchIssues("assigned")
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/dashboard/issues/created_by")(usersOnly {
|
|
||||||
searchIssues("created_by")
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/dashboard/issues/mentioned")(usersOnly {
|
|
||||||
searchIssues("mentioned")
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/dashboard/pulls")(usersOnly {
|
|
||||||
val q = request.getParameter("q")
|
|
||||||
val account = context.loginAccount.get
|
|
||||||
Option(q).map { q =>
|
|
||||||
val condition = IssueSearchCondition(q, Map[String, Int]())
|
|
||||||
q match {
|
|
||||||
case q if(q.contains("is:issue")) => redirect(s"/dashboard/issues?q=${StringUtil.urlEncode(q)}")
|
|
||||||
case q if(q.contains(s"author:${account.userName}")) => redirect(s"/dashboard/pulls/created_by${condition.toURL}")
|
|
||||||
case q if(q.contains(s"assignee:${account.userName}")) => redirect(s"/dashboard/pulls/assigned${condition.toURL}")
|
|
||||||
case q if(q.contains(s"mentions:${account.userName}")) => redirect(s"/dashboard/pulls/mentioned${condition.toURL}")
|
|
||||||
case _ => searchPullRequests("created_by")
|
|
||||||
}
|
|
||||||
} getOrElse {
|
|
||||||
searchPullRequests("created_by")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/dashboard/pulls/created_by")(usersOnly {
|
|
||||||
searchPullRequests("created_by")
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/dashboard/pulls/assigned")(usersOnly {
|
|
||||||
searchPullRequests("assigned")
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/dashboard/pulls/mentioned")(usersOnly {
|
|
||||||
searchPullRequests("mentioned")
|
|
||||||
})
|
|
||||||
|
|
||||||
private def getOrCreateCondition(key: String, filter: String, userName: String) = {
|
|
||||||
val condition = session.putAndGet(key, if(request.hasQueryString){
|
|
||||||
val q = request.getParameter("q")
|
|
||||||
if(q == null){
|
|
||||||
IssueSearchCondition(request)
|
|
||||||
} else {
|
|
||||||
IssueSearchCondition(q, Map[String, Int]())
|
|
||||||
}
|
|
||||||
} else session.getAs[IssueSearchCondition](key).getOrElse(IssueSearchCondition()))
|
|
||||||
|
|
||||||
filter match {
|
|
||||||
case "assigned" => condition.copy(assigned = Some(userName), author = None , mentioned = None)
|
|
||||||
case "mentioned" => condition.copy(assigned = None , author = None , mentioned = Some(userName))
|
|
||||||
case _ => condition.copy(assigned = None , author = Some(userName), mentioned = None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def searchIssues(filter: String) = {
|
|
||||||
import IssuesService._
|
|
||||||
|
|
||||||
val userName = context.loginAccount.get.userName
|
|
||||||
val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName)
|
|
||||||
val userRepos = getUserRepositories(userName, context.baseUrl, true).map(repo => repo.owner -> repo.name)
|
|
||||||
val page = IssueSearchCondition.page(request)
|
|
||||||
|
|
||||||
dashboard.html.issues(
|
|
||||||
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, userRepos: _*),
|
|
||||||
page,
|
|
||||||
countIssue(condition.copy(state = "open" ), false, userRepos: _*),
|
|
||||||
countIssue(condition.copy(state = "closed"), false, userRepos: _*),
|
|
||||||
filter match {
|
|
||||||
case "assigned" => condition.copy(assigned = Some(userName))
|
|
||||||
case "mentioned" => condition.copy(mentioned = Some(userName))
|
|
||||||
case _ => condition.copy(author = Some(userName))
|
|
||||||
},
|
|
||||||
filter,
|
|
||||||
getGroupNames(userName))
|
|
||||||
}
|
|
||||||
|
|
||||||
private def searchPullRequests(filter: String) = {
|
|
||||||
import IssuesService._
|
|
||||||
import PullRequestService._
|
|
||||||
|
|
||||||
val userName = context.loginAccount.get.userName
|
|
||||||
val condition = getOrCreateCondition(Keys.Session.DashboardPulls, filter, userName)
|
|
||||||
val allRepos = getAllRepositories(userName)
|
|
||||||
val page = IssueSearchCondition.page(request)
|
|
||||||
|
|
||||||
dashboard.html.pulls(
|
|
||||||
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, allRepos: _*),
|
|
||||||
page,
|
|
||||||
countIssue(condition.copy(state = "open" ), true, allRepos: _*),
|
|
||||||
countIssue(condition.copy(state = "closed"), true, allRepos: _*),
|
|
||||||
filter match {
|
|
||||||
case "assigned" => condition.copy(assigned = Some(userName))
|
|
||||||
case "mentioned" => condition.copy(mentioned = Some(userName))
|
|
||||||
case _ => condition.copy(author = Some(userName))
|
|
||||||
},
|
|
||||||
filter,
|
|
||||||
getGroupNames(userName))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import util.{Keys, FileUtil}
|
|
||||||
import util.ControlUtil._
|
|
||||||
import util.Directory._
|
|
||||||
import org.scalatra._
|
|
||||||
import org.scalatra.servlet.{MultipartConfig, FileUploadSupport, FileItem}
|
|
||||||
import org.apache.commons.io.FileUtils
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides Ajax based file upload functionality.
|
|
||||||
*
|
|
||||||
* This servlet saves uploaded file.
|
|
||||||
*/
|
|
||||||
class FileUploadController extends ScalatraServlet with FileUploadSupport {
|
|
||||||
|
|
||||||
configureMultipartHandling(MultipartConfig(maxFileSize = Some(3 * 1024 * 1024)))
|
|
||||||
|
|
||||||
post("/image"){
|
|
||||||
execute { (file, fileId) =>
|
|
||||||
FileUtils.writeByteArrayToFile(new java.io.File(getTemporaryDir(session.getId), fileId), file.get)
|
|
||||||
session += Keys.Session.Upload(fileId) -> file.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
post("/image/:owner/:repository"){
|
|
||||||
execute { (file, fileId) =>
|
|
||||||
FileUtils.writeByteArrayToFile(new java.io.File(
|
|
||||||
getAttachedDir(params("owner"), params("repository")),
|
|
||||||
fileId + "." + FileUtil.getExtension(file.getName)), file.get)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def execute(f: (FileItem, String) => Unit) = fileParams.get("file") match {
|
|
||||||
case Some(file) if(FileUtil.isImage(file.name)) =>
|
|
||||||
defining(FileUtil.generateFileId){ fileId =>
|
|
||||||
f(file, fileId)
|
|
||||||
|
|
||||||
Ok(fileId)
|
|
||||||
}
|
|
||||||
case _ => BadRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import util._
|
|
||||||
import util.Implicits._
|
|
||||||
import service._
|
|
||||||
import jp.sf.amateras.scalatra.forms._
|
|
||||||
|
|
||||||
class IndexController extends IndexControllerBase
|
|
||||||
with RepositoryService with ActivityService with AccountService with UsersAuthenticator
|
|
||||||
|
|
||||||
trait IndexControllerBase extends ControllerBase {
|
|
||||||
self: RepositoryService with ActivityService with AccountService with UsersAuthenticator =>
|
|
||||||
|
|
||||||
case class SignInForm(userName: String, password: String)
|
|
||||||
|
|
||||||
val form = mapping(
|
|
||||||
"userName" -> trim(label("Username", text(required))),
|
|
||||||
"password" -> trim(label("Password", text(required)))
|
|
||||||
)(SignInForm.apply)
|
|
||||||
|
|
||||||
get("/"){
|
|
||||||
val loginAccount = context.loginAccount
|
|
||||||
if(loginAccount.isEmpty) {
|
|
||||||
html.index(getRecentActivities(),
|
|
||||||
getVisibleRepositories(loginAccount, context.baseUrl, withoutPhysicalInfo = true),
|
|
||||||
loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl, withoutPhysicalInfo = true) }.getOrElse(Nil)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
val loginUserName = loginAccount.get.userName
|
|
||||||
val loginUserGroups = getGroupsByUserName(loginUserName)
|
|
||||||
var visibleOwnerSet : Set[String] = Set(loginUserName)
|
|
||||||
|
|
||||||
visibleOwnerSet ++= loginUserGroups
|
|
||||||
|
|
||||||
html.index(getRecentActivitiesByOwners(visibleOwnerSet),
|
|
||||||
getVisibleRepositories(loginAccount, context.baseUrl, withoutPhysicalInfo = true),
|
|
||||||
loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl, withoutPhysicalInfo = true) }.getOrElse(Nil)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get("/signin"){
|
|
||||||
val redirect = params.get("redirect")
|
|
||||||
if(redirect.isDefined && redirect.get.startsWith("/")){
|
|
||||||
flash += Keys.Flash.Redirect -> redirect.get
|
|
||||||
}
|
|
||||||
html.signin()
|
|
||||||
}
|
|
||||||
|
|
||||||
post("/signin", form){ form =>
|
|
||||||
authenticate(context.settings, form.userName, form.password) match {
|
|
||||||
case Some(account) => signin(account)
|
|
||||||
case None => redirect("/signin")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get("/signout"){
|
|
||||||
session.invalidate
|
|
||||||
redirect("/")
|
|
||||||
}
|
|
||||||
|
|
||||||
get("/activities.atom"){
|
|
||||||
contentType = "application/atom+xml; type=feed"
|
|
||||||
helper.xml.feed(getRecentActivities())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set account information into HttpSession and redirect.
|
|
||||||
*/
|
|
||||||
private def signin(account: model.Account) = {
|
|
||||||
session.setAttribute(Keys.Session.LoginAccount, account)
|
|
||||||
updateLastLoginDate(account.userName)
|
|
||||||
|
|
||||||
if(LDAPUtil.isDummyMailAddress(account)) {
|
|
||||||
redirect("/" + account.userName + "/_edit")
|
|
||||||
}
|
|
||||||
|
|
||||||
flash.get(Keys.Flash.Redirect).asInstanceOf[Option[String]].map { redirectUrl =>
|
|
||||||
if(redirectUrl.stripSuffix("/") == request.getContextPath){
|
|
||||||
redirect("/")
|
|
||||||
} else {
|
|
||||||
redirect(redirectUrl)
|
|
||||||
}
|
|
||||||
}.getOrElse {
|
|
||||||
redirect("/")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON API for collaborator completion.
|
|
||||||
*/
|
|
||||||
get("/_user/proposals")(usersOnly {
|
|
||||||
contentType = formats("json")
|
|
||||||
org.json4s.jackson.Serialization.write(
|
|
||||||
Map("options" -> getAllUsers().filter(!_.isGroupAccount).map(_.userName).toArray)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON APU for checking user existence.
|
|
||||||
*/
|
|
||||||
post("/_user/existence")(usersOnly {
|
|
||||||
getAccountByUserName(params("userName")).isDefined
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,422 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import jp.sf.amateras.scalatra.forms._
|
|
||||||
|
|
||||||
import service._
|
|
||||||
import IssuesService._
|
|
||||||
import util._
|
|
||||||
import util.Implicits._
|
|
||||||
import util.ControlUtil._
|
|
||||||
import org.scalatra.Ok
|
|
||||||
import model.Issue
|
|
||||||
|
|
||||||
class IssuesController extends IssuesControllerBase
|
|
||||||
with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
|
|
||||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator
|
|
||||||
|
|
||||||
trait IssuesControllerBase extends ControllerBase {
|
|
||||||
self: IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
|
|
||||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator =>
|
|
||||||
|
|
||||||
case class IssueCreateForm(title: String, content: Option[String],
|
|
||||||
assignedUserName: Option[String], milestoneId: Option[Int], labelNames: Option[String])
|
|
||||||
case class CommentForm(issueId: Int, content: String)
|
|
||||||
case class IssueStateForm(issueId: Int, content: Option[String])
|
|
||||||
|
|
||||||
val issueCreateForm = mapping(
|
|
||||||
"title" -> trim(label("Title", text(required))),
|
|
||||||
"content" -> trim(optional(text())),
|
|
||||||
"assignedUserName" -> trim(optional(text())),
|
|
||||||
"milestoneId" -> trim(optional(number())),
|
|
||||||
"labelNames" -> trim(optional(text()))
|
|
||||||
)(IssueCreateForm.apply)
|
|
||||||
|
|
||||||
val issueTitleEditForm = mapping(
|
|
||||||
"title" -> trim(label("Title", text(required)))
|
|
||||||
)(x => x)
|
|
||||||
val issueEditForm = mapping(
|
|
||||||
"content" -> trim(optional(text()))
|
|
||||||
)(x => x)
|
|
||||||
|
|
||||||
val commentForm = mapping(
|
|
||||||
"issueId" -> label("Issue Id", number()),
|
|
||||||
"content" -> trim(label("Comment", text(required)))
|
|
||||||
)(CommentForm.apply)
|
|
||||||
|
|
||||||
val issueStateForm = mapping(
|
|
||||||
"issueId" -> label("Issue Id", number()),
|
|
||||||
"content" -> trim(optional(text()))
|
|
||||||
)(IssueStateForm.apply)
|
|
||||||
|
|
||||||
get("/:owner/:repository/issues")(referrersOnly { repository =>
|
|
||||||
val q = request.getParameter("q")
|
|
||||||
if(Option(q).exists(_.contains("is:pr"))){
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/pulls?q=" + StringUtil.urlEncode(q))
|
|
||||||
} else {
|
|
||||||
searchIssues(repository)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
|
|
||||||
defining(repository.owner, repository.name, params("id")){ case (owner, name, issueId) =>
|
|
||||||
getIssue(owner, name, issueId) map {
|
|
||||||
issues.html.issue(
|
|
||||||
_,
|
|
||||||
getComments(owner, name, issueId.toInt),
|
|
||||||
getIssueLabels(owner, name, issueId.toInt),
|
|
||||||
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
|
|
||||||
getMilestonesWithIssueCount(owner, name),
|
|
||||||
getLabels(owner, name),
|
|
||||||
hasWritePermission(owner, name, context.loginAccount),
|
|
||||||
repository)
|
|
||||||
} getOrElse NotFound
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
|
|
||||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
|
||||||
issues.html.create(
|
|
||||||
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
|
|
||||||
getMilestones(owner, name),
|
|
||||||
getLabels(owner, name),
|
|
||||||
hasWritePermission(owner, name, context.loginAccount),
|
|
||||||
repository)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) =>
|
|
||||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
|
||||||
val writable = hasWritePermission(owner, name, context.loginAccount)
|
|
||||||
val userName = context.loginAccount.get.userName
|
|
||||||
|
|
||||||
// insert issue
|
|
||||||
val issueId = createIssue(owner, name, userName, form.title, form.content,
|
|
||||||
if(writable) form.assignedUserName else None,
|
|
||||||
if(writable) form.milestoneId else None)
|
|
||||||
|
|
||||||
// insert labels
|
|
||||||
if(writable){
|
|
||||||
form.labelNames.map { value =>
|
|
||||||
val labels = getLabels(owner, name)
|
|
||||||
value.split(",").foreach { labelName =>
|
|
||||||
labels.find(_.labelName == labelName).map { label =>
|
|
||||||
registerIssueLabel(owner, name, issueId, label.labelId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// record activity
|
|
||||||
recordCreateIssueActivity(owner, name, userName, issueId, form.title)
|
|
||||||
|
|
||||||
// extract references and create refer comment
|
|
||||||
getIssue(owner, name, issueId.toString).foreach { issue =>
|
|
||||||
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""))
|
|
||||||
}
|
|
||||||
|
|
||||||
// notifications
|
|
||||||
Notifier().toNotify(repository, issueId, form.content.getOrElse("")){
|
|
||||||
Notifier.msgIssue(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
|
|
||||||
}
|
|
||||||
|
|
||||||
redirect(s"/${owner}/${name}/issues/${issueId}")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/edit_title/:id", issueTitleEditForm)(readableUsersOnly { (title, repository) =>
|
|
||||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
|
||||||
getIssue(owner, name, params("id")).map { issue =>
|
|
||||||
if(isEditable(owner, name, issue.openedUserName)){
|
|
||||||
// update issue
|
|
||||||
updateIssue(owner, name, issue.issueId, title, issue.content)
|
|
||||||
// extract references and create refer comment
|
|
||||||
createReferComment(owner, name, issue.copy(title = title), title)
|
|
||||||
|
|
||||||
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
|
||||||
} else Unauthorized
|
|
||||||
} getOrElse NotFound
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (content, repository) =>
|
|
||||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
|
||||||
getIssue(owner, name, params("id")).map { issue =>
|
|
||||||
if(isEditable(owner, name, issue.openedUserName)){
|
|
||||||
// update issue
|
|
||||||
updateIssue(owner, name, issue.issueId, issue.title, content)
|
|
||||||
// extract references and create refer comment
|
|
||||||
createReferComment(owner, name, issue, content.getOrElse(""))
|
|
||||||
|
|
||||||
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
|
||||||
} else Unauthorized
|
|
||||||
} getOrElse NotFound
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
|
|
||||||
handleComment(form.issueId, Some(form.content), repository)() map { case (issue, id) =>
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/${
|
|
||||||
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
|
|
||||||
} getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
|
|
||||||
handleComment(form.issueId, form.content, repository)() map { case (issue, id) =>
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/${
|
|
||||||
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
|
|
||||||
} getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
|
|
||||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
|
||||||
getComment(owner, name, params("id")).map { comment =>
|
|
||||||
if(isEditable(owner, name, comment.commentedUserName)){
|
|
||||||
updateComment(comment.commentId, form.content)
|
|
||||||
redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}")
|
|
||||||
} else Unauthorized
|
|
||||||
} getOrElse NotFound
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issue_comments/delete/:id")(readableUsersOnly { repository =>
|
|
||||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
|
||||||
getComment(owner, name, params("id")).map { comment =>
|
|
||||||
if(isEditable(owner, name, comment.commentedUserName)){
|
|
||||||
Ok(deleteComment(comment.commentId))
|
|
||||||
} else Unauthorized
|
|
||||||
} getOrElse NotFound
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository =>
|
|
||||||
getIssue(repository.owner, repository.name, params("id")) map { x =>
|
|
||||||
if(isEditable(x.userName, x.repositoryName, x.openedUserName)){
|
|
||||||
params.get("dataType") collect {
|
|
||||||
case t if t == "html" => issues.html.editissue(
|
|
||||||
x.content, x.issueId, x.userName, x.repositoryName)
|
|
||||||
} getOrElse {
|
|
||||||
contentType = formats("json")
|
|
||||||
org.json4s.jackson.Serialization.write(
|
|
||||||
Map("title" -> x.title,
|
|
||||||
"content" -> view.Markdown.toHtml(x.content getOrElse "No description given.",
|
|
||||||
repository, false, true, true, isEditable(x.userName, x.repositoryName, x.openedUserName))
|
|
||||||
))
|
|
||||||
}
|
|
||||||
} else Unauthorized
|
|
||||||
} getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository =>
|
|
||||||
getComment(repository.owner, repository.name, params("id")) map { x =>
|
|
||||||
if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
|
|
||||||
params.get("dataType") collect {
|
|
||||||
case t if t == "html" => issues.html.editcomment(
|
|
||||||
x.content, x.commentId, x.userName, x.repositoryName)
|
|
||||||
} getOrElse {
|
|
||||||
contentType = formats("json")
|
|
||||||
org.json4s.jackson.Serialization.write(
|
|
||||||
Map("content" -> view.Markdown.toHtml(x.content,
|
|
||||||
repository, false, true, true, isEditable(x.userName, x.repositoryName, x.commentedUserName))
|
|
||||||
))
|
|
||||||
}
|
|
||||||
} else Unauthorized
|
|
||||||
} getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/:id/label/new")(collaboratorsOnly { repository =>
|
|
||||||
defining(params("id").toInt){ issueId =>
|
|
||||||
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
|
||||||
issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/:id/label/delete")(collaboratorsOnly { repository =>
|
|
||||||
defining(params("id").toInt){ issueId =>
|
|
||||||
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
|
||||||
issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/:id/assign")(collaboratorsOnly { repository =>
|
|
||||||
updateAssignedUserName(repository.owner, repository.name, params("id").toInt, assignedUserName("assignedUserName"))
|
|
||||||
Ok("updated")
|
|
||||||
})
|
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/:id/milestone")(collaboratorsOnly { repository =>
|
|
||||||
updateMilestoneId(repository.owner, repository.name, params("id").toInt, milestoneId("milestoneId"))
|
|
||||||
milestoneId("milestoneId").map { milestoneId =>
|
|
||||||
getMilestonesWithIssueCount(repository.owner, repository.name)
|
|
||||||
.find(_._1.milestoneId == milestoneId).map { case (_, openCount, closeCount) =>
|
|
||||||
issues.milestones.html.progress(openCount + closeCount, closeCount)
|
|
||||||
} getOrElse NotFound
|
|
||||||
} getOrElse Ok()
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/:owner/:repository/issues/batchedit/state")(collaboratorsOnly { repository =>
|
|
||||||
defining(params.get("value")){ action =>
|
|
||||||
action match {
|
|
||||||
case Some("open") => executeBatch(repository) { handleComment(_, None, repository)( _ => Some("reopen")) }
|
|
||||||
case Some("close") => executeBatch(repository) { handleComment(_, None, repository)( _ => Some("close")) }
|
|
||||||
case _ => // TODO BadRequest
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/:owner/:repository/issues/batchedit/label")(collaboratorsOnly { repository =>
|
|
||||||
params("value").toIntOpt.map{ labelId =>
|
|
||||||
executeBatch(repository) { issueId =>
|
|
||||||
getIssueLabel(repository.owner, repository.name, issueId, labelId) getOrElse {
|
|
||||||
registerIssueLabel(repository.owner, repository.name, issueId, labelId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/:owner/:repository/issues/batchedit/assign")(collaboratorsOnly { repository =>
|
|
||||||
defining(assignedUserName("value")){ value =>
|
|
||||||
executeBatch(repository) {
|
|
||||||
updateAssignedUserName(repository.owner, repository.name, _, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/:owner/:repository/issues/batchedit/milestone")(collaboratorsOnly { repository =>
|
|
||||||
defining(milestoneId("value")){ value =>
|
|
||||||
executeBatch(repository) {
|
|
||||||
updateMilestoneId(repository.owner, repository.name, _, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/_attached/:file")(referrersOnly { repository =>
|
|
||||||
(Directory.getAttachedDir(repository.owner, repository.name) match {
|
|
||||||
case dir if(dir.exists && dir.isDirectory) =>
|
|
||||||
dir.listFiles.find(_.getName.startsWith(params("file") + ".")).map { file =>
|
|
||||||
contentType = FileUtil.getMimeType(file.getName)
|
|
||||||
file
|
|
||||||
}
|
|
||||||
case _ => None
|
|
||||||
}) getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
val assignedUserName = (key: String) => params.get(key) filter (_.trim != "")
|
|
||||||
val milestoneId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
|
|
||||||
|
|
||||||
private def isEditable(owner: String, repository: String, author: String)(implicit context: app.Context): Boolean =
|
|
||||||
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
|
||||||
|
|
||||||
private def executeBatch(repository: RepositoryService.RepositoryInfo)(execute: Int => Unit) = {
|
|
||||||
params("checked").split(',') map(_.toInt) foreach execute
|
|
||||||
params("from") match {
|
|
||||||
case "issues" => redirect(s"/${repository.owner}/${repository.name}/issues")
|
|
||||||
case "pulls" => redirect(s"/${repository.owner}/${repository.name}/pulls")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String) = {
|
|
||||||
StringUtil.extractIssueId(message).foreach { issueId =>
|
|
||||||
if(getIssue(owner, repository, issueId).isDefined){
|
|
||||||
createComment(owner, repository, context.loginAccount.get.userName, issueId.toInt,
|
|
||||||
fromIssue.issueId + ":" + fromIssue.title, "refer")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see [[https://github.com/takezoe/gitbucket/wiki/CommentAction]]
|
|
||||||
*/
|
|
||||||
private def handleComment(issueId: Int, content: Option[String], repository: RepositoryService.RepositoryInfo)
|
|
||||||
(getAction: model.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) map { 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
|
|
||||||
.map ( _ -> action.map( _ + "_comment" ).getOrElse("comment") )
|
|
||||||
.getOrElse ( action.get.capitalize -> action.get )
|
|
||||||
match {
|
|
||||||
case (content, action) => createComment(owner, name, userName, issueId, content, action)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// notifications
|
|
||||||
Notifier() match {
|
|
||||||
case f =>
|
|
||||||
content foreach {
|
|
||||||
f.toNotify(repository, issueId, _){
|
|
||||||
Notifier.msgComment(s"${context.baseUrl}/${owner}/${name}/${
|
|
||||||
if(issue.isPullRequest) "pull" else "issues"}/${issueId}#comment-${commentId}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
action foreach {
|
|
||||||
f.toNotify(repository, issueId, _){
|
|
||||||
Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
issue -> commentId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def searchIssues(repository: RepositoryService.RepositoryInfo) = {
|
|
||||||
defining(repository.owner, repository.name){ case (owner, repoName) =>
|
|
||||||
val page = IssueSearchCondition.page(request)
|
|
||||||
val sessionKey = Keys.Session.Issues(owner, repoName)
|
|
||||||
|
|
||||||
// retrieve search condition
|
|
||||||
val condition = session.putAndGet(sessionKey,
|
|
||||||
if(request.hasQueryString){
|
|
||||||
val q = request.getParameter("q")
|
|
||||||
if(q == null){
|
|
||||||
IssueSearchCondition(request)
|
|
||||||
} else {
|
|
||||||
IssueSearchCondition(q, getMilestones(owner, repoName).map(x => (x.title, x.milestoneId)).toMap)
|
|
||||||
}
|
|
||||||
} else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
|
|
||||||
)
|
|
||||||
|
|
||||||
issues.html.list(
|
|
||||||
"issues",
|
|
||||||
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
|
|
||||||
page,
|
|
||||||
(getCollaborators(owner, repoName) :+ owner).sorted,
|
|
||||||
getMilestones(owner, repoName),
|
|
||||||
getLabels(owner, repoName),
|
|
||||||
countIssue(condition.copy(state = "open" ), false, owner -> repoName),
|
|
||||||
countIssue(condition.copy(state = "closed"), false, owner -> repoName),
|
|
||||||
condition,
|
|
||||||
repository,
|
|
||||||
hasWritePermission(owner, repoName, context.loginAccount))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,483 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import util._
|
|
||||||
import util.Directory._
|
|
||||||
import util.Implicits._
|
|
||||||
import util.ControlUtil._
|
|
||||||
import service._
|
|
||||||
import org.eclipse.jgit.api.Git
|
|
||||||
import jp.sf.amateras.scalatra.forms._
|
|
||||||
import org.eclipse.jgit.transport.RefSpec
|
|
||||||
import scala.collection.JavaConverters._
|
|
||||||
import org.eclipse.jgit.lib.{ObjectId, CommitBuilder, PersonIdent}
|
|
||||||
import service.IssuesService._
|
|
||||||
import service.PullRequestService._
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import org.eclipse.jgit.merge.MergeStrategy
|
|
||||||
import org.eclipse.jgit.errors.NoMergeBaseException
|
|
||||||
import service.WebHookService.WebHookPayload
|
|
||||||
import util.JGitUtil.DiffInfo
|
|
||||||
import util.JGitUtil.CommitInfo
|
|
||||||
|
|
||||||
|
|
||||||
class PullRequestsController extends PullRequestsControllerBase
|
|
||||||
with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with LabelsService
|
|
||||||
with CommitsService with ActivityService with WebHookService with ReferrerAuthenticator with CollaboratorsAuthenticator
|
|
||||||
|
|
||||||
trait PullRequestsControllerBase extends ControllerBase {
|
|
||||||
self: RepositoryService with AccountService with IssuesService with MilestonesService with LabelsService
|
|
||||||
with CommitsService with ActivityService with PullRequestService with WebHookService with ReferrerAuthenticator with CollaboratorsAuthenticator =>
|
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(classOf[PullRequestsControllerBase])
|
|
||||||
|
|
||||||
val pullRequestForm = mapping(
|
|
||||||
"title" -> trim(label("Title" , text(required, maxlength(100)))),
|
|
||||||
"content" -> trim(label("Content", optional(text()))),
|
|
||||||
"targetUserName" -> trim(text(required, maxlength(100))),
|
|
||||||
"targetBranch" -> trim(text(required, maxlength(100))),
|
|
||||||
"requestUserName" -> trim(text(required, maxlength(100))),
|
|
||||||
"requestRepositoryName" -> trim(text(required, maxlength(100))),
|
|
||||||
"requestBranch" -> trim(text(required, maxlength(100))),
|
|
||||||
"commitIdFrom" -> trim(text(required, maxlength(40))),
|
|
||||||
"commitIdTo" -> trim(text(required, maxlength(40)))
|
|
||||||
)(PullRequestForm.apply)
|
|
||||||
|
|
||||||
val mergeForm = mapping(
|
|
||||||
"message" -> trim(label("Message", text(required)))
|
|
||||||
)(MergeForm.apply)
|
|
||||||
|
|
||||||
case class PullRequestForm(
|
|
||||||
title: String,
|
|
||||||
content: Option[String],
|
|
||||||
targetUserName: String,
|
|
||||||
targetBranch: String,
|
|
||||||
requestUserName: String,
|
|
||||||
requestRepositoryName: String,
|
|
||||||
requestBranch: String,
|
|
||||||
commitIdFrom: String,
|
|
||||||
commitIdTo: String)
|
|
||||||
|
|
||||||
case class MergeForm(message: String)
|
|
||||||
|
|
||||||
get("/:owner/:repository/pulls")(referrersOnly { repository =>
|
|
||||||
val q = request.getParameter("q")
|
|
||||||
if(Option(q).exists(_.contains("is:issue"))){
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/issues?q=" + StringUtil.urlEncode(q))
|
|
||||||
} else {
|
|
||||||
searchPullRequests(None, repository)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/pull/:id")(referrersOnly { repository =>
|
|
||||||
params("id").toIntOpt.flatMap{ issueId =>
|
|
||||||
val owner = repository.owner
|
|
||||||
val name = repository.name
|
|
||||||
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
|
|
||||||
using(Git.open(getRepositoryDir(owner, name))){ git =>
|
|
||||||
val (commits, diffs) =
|
|
||||||
getRequestCompareInfo(owner, name, pullreq.commitIdFrom, owner, name, pullreq.commitIdTo)
|
|
||||||
|
|
||||||
pulls.html.pullreq(
|
|
||||||
issue, pullreq,
|
|
||||||
(commits.flatten.map(commit => getCommitComments(owner, name, commit.id, true)).flatten.toList ::: getComments(owner, name, issueId))
|
|
||||||
.sortWith((a, b) => a.registeredDate before b.registeredDate),
|
|
||||||
getIssueLabels(owner, name, issueId),
|
|
||||||
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
|
|
||||||
getMilestonesWithIssueCount(owner, name),
|
|
||||||
getLabels(owner, name),
|
|
||||||
commits,
|
|
||||||
diffs,
|
|
||||||
hasWritePermission(owner, name, context.loginAccount),
|
|
||||||
repository)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
ajaxGet("/:owner/:repository/pull/:id/mergeguide")(collaboratorsOnly { repository =>
|
|
||||||
params("id").toIntOpt.flatMap{ issueId =>
|
|
||||||
val owner = repository.owner
|
|
||||||
val name = repository.name
|
|
||||||
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
|
|
||||||
pulls.html.mergeguide(
|
|
||||||
checkConflictInPullRequest(owner, name, pullreq.branch, pullreq.requestUserName, name, pullreq.requestBranch, issueId),
|
|
||||||
pullreq,
|
|
||||||
s"${context.baseUrl}/git/${pullreq.requestUserName}/${pullreq.requestRepositoryName}.git")
|
|
||||||
}
|
|
||||||
} getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/pull/:id/delete/*")(collaboratorsOnly { repository =>
|
|
||||||
params("id").toIntOpt.map { issueId =>
|
|
||||||
val branchName = multiParams("splat").head
|
|
||||||
val userName = context.loginAccount.get.userName
|
|
||||||
if(repository.repository.defaultBranch != branchName){
|
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
|
||||||
git.branchDelete().setForce(true).setBranchNames(branchName).call()
|
|
||||||
recordDeleteBranchActivity(repository.owner, repository.name, userName, branchName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
createComment(repository.owner, repository.name, userName, issueId, branchName, "delete_branch")
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
|
||||||
} getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/:owner/:repository/pull/:id/merge", mergeForm)(collaboratorsOnly { (form, repository) =>
|
|
||||||
params("id").toIntOpt.flatMap { issueId =>
|
|
||||||
val owner = repository.owner
|
|
||||||
val name = repository.name
|
|
||||||
LockUtil.lock(s"${owner}/${name}"){
|
|
||||||
getPullRequest(owner, name, issueId).map { case (issue, pullreq) =>
|
|
||||||
using(Git.open(getRepositoryDir(owner, name))) { git =>
|
|
||||||
// mark issue as merged and close.
|
|
||||||
val loginAccount = context.loginAccount.get
|
|
||||||
createComment(owner, name, loginAccount.userName, issueId, form.message, "merge")
|
|
||||||
createComment(owner, name, loginAccount.userName, issueId, "Close", "close")
|
|
||||||
updateClosed(owner, name, issueId, true)
|
|
||||||
|
|
||||||
// record activity
|
|
||||||
recordMergeActivity(owner, name, loginAccount.userName, issueId, form.message)
|
|
||||||
|
|
||||||
// merge
|
|
||||||
val mergeBaseRefName = s"refs/heads/${pullreq.branch}"
|
|
||||||
val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true)
|
|
||||||
val mergeBaseTip = git.getRepository.resolve(mergeBaseRefName)
|
|
||||||
val mergeTip = git.getRepository.resolve(s"refs/pull/${issueId}/head")
|
|
||||||
val conflicted = try {
|
|
||||||
!merger.merge(mergeBaseTip, mergeTip)
|
|
||||||
} catch {
|
|
||||||
case e: NoMergeBaseException => true
|
|
||||||
}
|
|
||||||
if (conflicted) {
|
|
||||||
throw new RuntimeException("This pull request can't merge automatically.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// creates merge commit
|
|
||||||
val mergeCommit = new CommitBuilder()
|
|
||||||
mergeCommit.setTreeId(merger.getResultTreeId)
|
|
||||||
mergeCommit.setParentIds(Array[ObjectId](mergeBaseTip, mergeTip): _*)
|
|
||||||
val personIdent = new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
|
|
||||||
mergeCommit.setAuthor(personIdent)
|
|
||||||
mergeCommit.setCommitter(personIdent)
|
|
||||||
mergeCommit.setMessage(s"Merge pull request #${issueId} from ${pullreq.requestUserName}/${pullreq.requestBranch}\n\n" +
|
|
||||||
form.message)
|
|
||||||
|
|
||||||
// insertObject and got mergeCommit Object Id
|
|
||||||
val inserter = git.getRepository.newObjectInserter
|
|
||||||
val mergeCommitId = inserter.insert(mergeCommit)
|
|
||||||
inserter.flush()
|
|
||||||
inserter.release()
|
|
||||||
|
|
||||||
// update refs
|
|
||||||
val refUpdate = git.getRepository.updateRef(mergeBaseRefName)
|
|
||||||
refUpdate.setNewObjectId(mergeCommitId)
|
|
||||||
refUpdate.setForceUpdate(false)
|
|
||||||
refUpdate.setRefLogIdent(personIdent)
|
|
||||||
refUpdate.setRefLogMessage("merged", true)
|
|
||||||
refUpdate.update()
|
|
||||||
|
|
||||||
val (commits, _) = getRequestCompareInfo(owner, name, pullreq.commitIdFrom,
|
|
||||||
pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.commitIdTo)
|
|
||||||
|
|
||||||
// close issue by content of pull request
|
|
||||||
val defaultBranch = getRepository(owner, name, context.baseUrl).get.repository.defaultBranch
|
|
||||||
if(pullreq.branch == defaultBranch){
|
|
||||||
commits.flatten.foreach { commit =>
|
|
||||||
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name)
|
|
||||||
}
|
|
||||||
issue.content match {
|
|
||||||
case Some(content) => closeIssuesFromMessage(content, loginAccount.userName, owner, name)
|
|
||||||
case _ =>
|
|
||||||
}
|
|
||||||
closeIssuesFromMessage(form.message, loginAccount.userName, owner, name)
|
|
||||||
}
|
|
||||||
// call web hook
|
|
||||||
getWebHookURLs(owner, name) match {
|
|
||||||
case webHookURLs if(webHookURLs.nonEmpty) =>
|
|
||||||
for(ownerAccount <- getAccountByUserName(owner)){
|
|
||||||
callWebHook(owner, name, webHookURLs,
|
|
||||||
WebHookPayload(git, loginAccount, mergeBaseRefName, repository, commits.flatten.toList, ownerAccount))
|
|
||||||
}
|
|
||||||
case _ =>
|
|
||||||
}
|
|
||||||
|
|
||||||
// notifications
|
|
||||||
Notifier().toNotify(repository, issueId, "merge"){
|
|
||||||
Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/pull/${issueId}")
|
|
||||||
}
|
|
||||||
|
|
||||||
redirect(s"/${owner}/${name}/pull/${issueId}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/compare")(referrersOnly { forkedRepository =>
|
|
||||||
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
|
|
||||||
case (Some(originUserName), Some(originRepositoryName)) => {
|
|
||||||
getRepository(originUserName, originRepositoryName, context.baseUrl).map { originRepository =>
|
|
||||||
using(
|
|
||||||
Git.open(getRepositoryDir(originUserName, originRepositoryName)),
|
|
||||||
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
|
|
||||||
){ (oldGit, newGit) =>
|
|
||||||
val oldBranch = JGitUtil.getDefaultBranch(oldGit, originRepository).get._2
|
|
||||||
val newBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository).get._2
|
|
||||||
|
|
||||||
redirect(s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${originUserName}:${oldBranch}...${newBranch}")
|
|
||||||
}
|
|
||||||
} getOrElse NotFound
|
|
||||||
}
|
|
||||||
case _ => {
|
|
||||||
using(Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))){ git =>
|
|
||||||
JGitUtil.getDefaultBranch(git, forkedRepository).map { case (_, defaultBranch) =>
|
|
||||||
redirect(s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${defaultBranch}...${defaultBranch}")
|
|
||||||
} getOrElse {
|
|
||||||
redirect(s"/${forkedRepository.owner}/${forkedRepository.name}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/compare/*...*")(referrersOnly { forkedRepository =>
|
|
||||||
val Seq(origin, forked) = multiParams("splat")
|
|
||||||
val (originOwner, tmpOriginBranch) = parseCompareIdentifie(origin, forkedRepository.owner)
|
|
||||||
val (forkedOwner, tmpForkedBranch) = parseCompareIdentifie(forked, forkedRepository.owner)
|
|
||||||
|
|
||||||
(for(
|
|
||||||
originRepositoryName <- if(originOwner == forkedOwner){
|
|
||||||
Some(forkedRepository.name)
|
|
||||||
} else {
|
|
||||||
forkedRepository.repository.originRepositoryName.orElse {
|
|
||||||
getForkedRepositories(forkedRepository.owner, forkedRepository.name).find(_._1 == originOwner).map(_._2)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
originRepository <- getRepository(originOwner, originRepositoryName, context.baseUrl)
|
|
||||||
) yield {
|
|
||||||
using(
|
|
||||||
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),
|
|
||||||
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
|
|
||||||
){ case (oldGit, newGit) =>
|
|
||||||
val originBranch = JGitUtil.getDefaultBranch(oldGit, originRepository, tmpOriginBranch).get._2
|
|
||||||
val forkedBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository, tmpForkedBranch).get._2
|
|
||||||
|
|
||||||
val forkedId = JGitUtil.getForkedCommitId(oldGit, newGit,
|
|
||||||
originRepository.owner, originRepository.name, originBranch,
|
|
||||||
forkedRepository.owner, forkedRepository.name, forkedBranch)
|
|
||||||
|
|
||||||
val oldId = oldGit.getRepository.resolve(forkedId)
|
|
||||||
val newId = newGit.getRepository.resolve(forkedBranch)
|
|
||||||
|
|
||||||
val (commits, diffs) = getRequestCompareInfo(
|
|
||||||
originRepository.owner, originRepository.name, oldId.getName,
|
|
||||||
forkedRepository.owner, forkedRepository.name, newId.getName)
|
|
||||||
|
|
||||||
pulls.html.compare(
|
|
||||||
commits,
|
|
||||||
diffs,
|
|
||||||
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
|
|
||||||
case (Some(userName), Some(repositoryName)) => (userName, repositoryName) :: getForkedRepositories(userName, repositoryName)
|
|
||||||
case _ => (forkedRepository.owner, forkedRepository.name) :: getForkedRepositories(forkedRepository.owner, forkedRepository.name)
|
|
||||||
},
|
|
||||||
commits.flatten.map(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false)).flatten.toList,
|
|
||||||
originBranch,
|
|
||||||
forkedBranch,
|
|
||||||
oldId.getName,
|
|
||||||
newId.getName,
|
|
||||||
forkedRepository,
|
|
||||||
originRepository,
|
|
||||||
forkedRepository,
|
|
||||||
hasWritePermission(forkedRepository.owner, forkedRepository.name, context.loginAccount))
|
|
||||||
}
|
|
||||||
}) getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
ajaxGet("/:owner/:repository/compare/*...*/mergecheck")(collaboratorsOnly { forkedRepository =>
|
|
||||||
val Seq(origin, forked) = multiParams("splat")
|
|
||||||
val (originOwner, tmpOriginBranch) = parseCompareIdentifie(origin, forkedRepository.owner)
|
|
||||||
val (forkedOwner, tmpForkedBranch) = parseCompareIdentifie(forked, forkedRepository.owner)
|
|
||||||
|
|
||||||
(for(
|
|
||||||
originRepositoryName <- if(originOwner == forkedOwner){
|
|
||||||
Some(forkedRepository.name)
|
|
||||||
} else {
|
|
||||||
forkedRepository.repository.originRepositoryName.orElse {
|
|
||||||
getForkedRepositories(forkedRepository.owner, forkedRepository.name).find(_._1 == originOwner).map(_._2)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
originRepository <- getRepository(originOwner, originRepositoryName, context.baseUrl)
|
|
||||||
) yield {
|
|
||||||
using(
|
|
||||||
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),
|
|
||||||
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
|
|
||||||
){ case (oldGit, newGit) =>
|
|
||||||
val originBranch = JGitUtil.getDefaultBranch(oldGit, originRepository, tmpOriginBranch).get._2
|
|
||||||
val forkedBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository, tmpForkedBranch).get._2
|
|
||||||
|
|
||||||
pulls.html.mergecheck(
|
|
||||||
checkConflict(originRepository.owner, originRepository.name, originBranch,
|
|
||||||
forkedRepository.owner, forkedRepository.name, forkedBranch))
|
|
||||||
}
|
|
||||||
}) getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/:owner/:repository/pulls/new", pullRequestForm)(referrersOnly { (form, repository) =>
|
|
||||||
val loginUserName = context.loginAccount.get.userName
|
|
||||||
|
|
||||||
val issueId = createIssue(
|
|
||||||
owner = repository.owner,
|
|
||||||
repository = repository.name,
|
|
||||||
loginUser = loginUserName,
|
|
||||||
title = form.title,
|
|
||||||
content = form.content,
|
|
||||||
assignedUserName = None,
|
|
||||||
milestoneId = None,
|
|
||||||
isPullRequest = true)
|
|
||||||
|
|
||||||
createPullRequest(
|
|
||||||
originUserName = repository.owner,
|
|
||||||
originRepositoryName = repository.name,
|
|
||||||
issueId = issueId,
|
|
||||||
originBranch = form.targetBranch,
|
|
||||||
requestUserName = form.requestUserName,
|
|
||||||
requestRepositoryName = form.requestRepositoryName,
|
|
||||||
requestBranch = form.requestBranch,
|
|
||||||
commitIdFrom = form.commitIdFrom,
|
|
||||||
commitIdTo = form.commitIdTo)
|
|
||||||
|
|
||||||
// fetch requested branch
|
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
|
||||||
git.fetch
|
|
||||||
.setRemote(getRepositoryDir(form.requestUserName, form.requestRepositoryName).toURI.toString)
|
|
||||||
.setRefSpecs(new RefSpec(s"refs/heads/${form.requestBranch}:refs/pull/${issueId}/head"))
|
|
||||||
.call
|
|
||||||
}
|
|
||||||
|
|
||||||
// record activity
|
|
||||||
recordPullRequestActivity(repository.owner, repository.name, loginUserName, issueId, form.title)
|
|
||||||
|
|
||||||
// notifications
|
|
||||||
Notifier().toNotify(repository, issueId, form.content.getOrElse("")){
|
|
||||||
Notifier.msgPullRequest(s"${context.baseUrl}/${repository.owner}/${repository.name}/pull/${issueId}")
|
|
||||||
}
|
|
||||||
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether conflict will be caused in merging. Returns true if conflict will be caused.
|
|
||||||
*/
|
|
||||||
private def checkConflict(userName: String, repositoryName: String, branch: String,
|
|
||||||
requestUserName: String, requestRepositoryName: String, requestBranch: String): Boolean = {
|
|
||||||
LockUtil.lock(s"${userName}/${repositoryName}"){
|
|
||||||
using(Git.open(getRepositoryDir(requestUserName, requestRepositoryName))) { git =>
|
|
||||||
val remoteRefName = s"refs/heads/${branch}"
|
|
||||||
val tmpRefName = s"refs/merge-check/${userName}/${branch}"
|
|
||||||
val refSpec = new RefSpec(s"${remoteRefName}:${tmpRefName}").setForceUpdate(true)
|
|
||||||
try {
|
|
||||||
// fetch objects from origin repository branch
|
|
||||||
git.fetch
|
|
||||||
.setRemote(getRepositoryDir(userName, repositoryName).toURI.toString)
|
|
||||||
.setRefSpecs(refSpec)
|
|
||||||
.call
|
|
||||||
|
|
||||||
// merge conflict check
|
|
||||||
val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true)
|
|
||||||
val mergeBaseTip = git.getRepository.resolve(s"refs/heads/${requestBranch}")
|
|
||||||
val mergeTip = git.getRepository.resolve(tmpRefName)
|
|
||||||
try {
|
|
||||||
!merger.merge(mergeBaseTip, mergeTip)
|
|
||||||
} catch {
|
|
||||||
case e: NoMergeBaseException => true
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
val refUpdate = git.getRepository.updateRef(refSpec.getDestination)
|
|
||||||
refUpdate.setForceUpdate(true)
|
|
||||||
refUpdate.delete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether conflict will be caused in merging within pull request. Returns true if conflict will be caused.
|
|
||||||
*/
|
|
||||||
private def checkConflictInPullRequest(userName: String, repositoryName: String, branch: String,
|
|
||||||
requestUserName: String, requestRepositoryName: String, requestBranch: String,
|
|
||||||
issueId: Int): Boolean = {
|
|
||||||
LockUtil.lock(s"${userName}/${repositoryName}") {
|
|
||||||
using(Git.open(getRepositoryDir(userName, repositoryName))) { git =>
|
|
||||||
// merge
|
|
||||||
val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true)
|
|
||||||
val mergeBaseTip = git.getRepository.resolve(s"refs/heads/${branch}")
|
|
||||||
val mergeTip = git.getRepository.resolve(s"refs/pull/${issueId}/head")
|
|
||||||
try {
|
|
||||||
!merger.merge(mergeBaseTip, mergeTip)
|
|
||||||
} catch {
|
|
||||||
case e: NoMergeBaseException => true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses branch identifier and extracts owner and branch name as tuple.
|
|
||||||
*
|
|
||||||
* - "owner:branch" to ("owner", "branch")
|
|
||||||
* - "branch" to ("defaultOwner", "branch")
|
|
||||||
*/
|
|
||||||
private def parseCompareIdentifie(value: String, defaultOwner: String): (String, String) =
|
|
||||||
if(value.contains(':')){
|
|
||||||
val array = value.split(":")
|
|
||||||
(array(0), array(1))
|
|
||||||
} else {
|
|
||||||
(defaultOwner, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def getRequestCompareInfo(userName: String, repositoryName: String, branch: String,
|
|
||||||
requestUserName: String, requestRepositoryName: String, requestCommitId: String): (Seq[Seq[CommitInfo]], Seq[DiffInfo]) =
|
|
||||||
using(
|
|
||||||
Git.open(getRepositoryDir(userName, repositoryName)),
|
|
||||||
Git.open(getRepositoryDir(requestUserName, requestRepositoryName))
|
|
||||||
){ (oldGit, newGit) =>
|
|
||||||
val oldId = oldGit.getRepository.resolve(branch)
|
|
||||||
val newId = newGit.getRepository.resolve(requestCommitId)
|
|
||||||
|
|
||||||
val commits = newGit.log.addRange(oldId, newId).call.iterator.asScala.map { revCommit =>
|
|
||||||
new CommitInfo(revCommit)
|
|
||||||
}.toList.splitWith { (commit1, commit2) =>
|
|
||||||
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
val diffs = JGitUtil.getDiffs(newGit, oldId.getName, newId.getName, true)
|
|
||||||
|
|
||||||
(commits, diffs)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) =
|
|
||||||
defining(repository.owner, repository.name){ case (owner, repoName) =>
|
|
||||||
val page = IssueSearchCondition.page(request)
|
|
||||||
val sessionKey = Keys.Session.Pulls(owner, repoName)
|
|
||||||
|
|
||||||
// retrieve search condition
|
|
||||||
val condition = session.putAndGet(sessionKey,
|
|
||||||
if(request.hasQueryString) IssueSearchCondition(request)
|
|
||||||
else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
|
|
||||||
)
|
|
||||||
|
|
||||||
issues.html.list(
|
|
||||||
"pulls",
|
|
||||||
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName),
|
|
||||||
page,
|
|
||||||
(getCollaborators(owner, repoName) :+ owner).sorted,
|
|
||||||
getMilestones(owner, repoName),
|
|
||||||
getLabels(owner, repoName),
|
|
||||||
countIssue(condition.copy(state = "open" ), true, owner -> repoName),
|
|
||||||
countIssue(condition.copy(state = "closed"), true, owner -> repoName),
|
|
||||||
condition,
|
|
||||||
repository,
|
|
||||||
hasWritePermission(owner, repoName, context.loginAccount))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,274 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import service._
|
|
||||||
import util.Directory._
|
|
||||||
import util.Implicits._
|
|
||||||
import util.{LockUtil, UsersAuthenticator, OwnerAuthenticator}
|
|
||||||
import jp.sf.amateras.scalatra.forms._
|
|
||||||
import org.apache.commons.io.FileUtils
|
|
||||||
import org.scalatra.i18n.Messages
|
|
||||||
import service.WebHookService.WebHookPayload
|
|
||||||
import util.JGitUtil.CommitInfo
|
|
||||||
import util.ControlUtil._
|
|
||||||
import org.eclipse.jgit.api.Git
|
|
||||||
import org.eclipse.jgit.lib.Constants
|
|
||||||
|
|
||||||
class RepositorySettingsController extends RepositorySettingsControllerBase
|
|
||||||
with RepositoryService with AccountService with WebHookService
|
|
||||||
with OwnerAuthenticator with UsersAuthenticator
|
|
||||||
|
|
||||||
trait RepositorySettingsControllerBase extends ControllerBase {
|
|
||||||
self: RepositoryService with AccountService with WebHookService
|
|
||||||
with OwnerAuthenticator with UsersAuthenticator =>
|
|
||||||
|
|
||||||
// for repository options
|
|
||||||
case class OptionsForm(repositoryName: String, description: Option[String], defaultBranch: String, isPrivate: Boolean)
|
|
||||||
|
|
||||||
val optionsForm = mapping(
|
|
||||||
"repositoryName" -> trim(label("Description" , text(required, maxlength(40), identifier, renameRepositoryName))),
|
|
||||||
"description" -> trim(label("Description" , optional(text()))),
|
|
||||||
"defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100)))),
|
|
||||||
"isPrivate" -> trim(label("Repository Type", boolean()))
|
|
||||||
)(OptionsForm.apply)
|
|
||||||
|
|
||||||
// for collaborator addition
|
|
||||||
case class CollaboratorForm(userName: String)
|
|
||||||
|
|
||||||
val collaboratorForm = mapping(
|
|
||||||
"userName" -> trim(label("Username", text(required, collaborator)))
|
|
||||||
)(CollaboratorForm.apply)
|
|
||||||
|
|
||||||
// for web hook url addition
|
|
||||||
case class WebHookForm(url: String)
|
|
||||||
|
|
||||||
val webHookForm = mapping(
|
|
||||||
"url" -> trim(label("url", text(required, webHook)))
|
|
||||||
)(WebHookForm.apply)
|
|
||||||
|
|
||||||
// for transfer ownership
|
|
||||||
case class TransferOwnerShipForm(newOwner: String)
|
|
||||||
|
|
||||||
val transferForm = mapping(
|
|
||||||
"newOwner" -> trim(label("New owner", text(required, transferUser)))
|
|
||||||
)(TransferOwnerShipForm.apply)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redirect to the Options page.
|
|
||||||
*/
|
|
||||||
get("/:owner/:repository/settings")(ownerOnly { repository =>
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/settings/options")
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display the Options page.
|
|
||||||
*/
|
|
||||||
get("/:owner/:repository/settings/options")(ownerOnly {
|
|
||||||
settings.html.options(_, flash.get("info"))
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save the repository options.
|
|
||||||
*/
|
|
||||||
post("/:owner/:repository/settings/options", optionsForm)(ownerOnly { (form, repository) =>
|
|
||||||
val defaultBranch = if(repository.branchList.isEmpty) "master" else form.defaultBranch
|
|
||||||
saveRepositoryOptions(
|
|
||||||
repository.owner,
|
|
||||||
repository.name,
|
|
||||||
form.description,
|
|
||||||
defaultBranch,
|
|
||||||
repository.repository.parentUserName.map { _ =>
|
|
||||||
repository.repository.isPrivate
|
|
||||||
} getOrElse form.isPrivate
|
|
||||||
)
|
|
||||||
// Change repository name
|
|
||||||
if(repository.name != form.repositoryName){
|
|
||||||
// Update database
|
|
||||||
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
|
|
||||||
// Move git repository
|
|
||||||
defining(getRepositoryDir(repository.owner, repository.name)){ dir =>
|
|
||||||
FileUtils.moveDirectory(dir, getRepositoryDir(repository.owner, form.repositoryName))
|
|
||||||
}
|
|
||||||
// Move wiki repository
|
|
||||||
defining(getWikiRepositoryDir(repository.owner, repository.name)){ dir =>
|
|
||||||
FileUtils.moveDirectory(dir, getWikiRepositoryDir(repository.owner, form.repositoryName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Change repository HEAD
|
|
||||||
using(Git.open(getRepositoryDir(repository.owner, form.repositoryName))) { git =>
|
|
||||||
git.getRepository.updateRef(Constants.HEAD, true).link(Constants.R_HEADS + defaultBranch)
|
|
||||||
}
|
|
||||||
flash += "info" -> "Repository settings has been updated."
|
|
||||||
redirect(s"/${repository.owner}/${form.repositoryName}/settings/options")
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display the Collaborators page.
|
|
||||||
*/
|
|
||||||
get("/:owner/:repository/settings/collaborators")(ownerOnly { repository =>
|
|
||||||
settings.html.collaborators(
|
|
||||||
getCollaborators(repository.owner, repository.name),
|
|
||||||
getAccountByUserName(repository.owner).get.isGroupAccount,
|
|
||||||
repository)
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the collaborator.
|
|
||||||
*/
|
|
||||||
post("/:owner/:repository/settings/collaborators/add", collaboratorForm)(ownerOnly { (form, repository) =>
|
|
||||||
if(!getAccountByUserName(repository.owner).get.isGroupAccount){
|
|
||||||
addCollaborator(repository.owner, repository.name, form.userName)
|
|
||||||
}
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the collaborator.
|
|
||||||
*/
|
|
||||||
get("/:owner/:repository/settings/collaborators/remove")(ownerOnly { repository =>
|
|
||||||
if(!getAccountByUserName(repository.owner).get.isGroupAccount){
|
|
||||||
removeCollaborator(repository.owner, repository.name, params("name"))
|
|
||||||
}
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display the web hook page.
|
|
||||||
*/
|
|
||||||
get("/:owner/:repository/settings/hooks")(ownerOnly { repository =>
|
|
||||||
settings.html.hooks(getWebHookURLs(repository.owner, repository.name), flash.get("url"), repository, flash.get("info"))
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the web hook URL.
|
|
||||||
*/
|
|
||||||
post("/:owner/:repository/settings/hooks/add", webHookForm)(ownerOnly { (form, repository) =>
|
|
||||||
addWebHookURL(repository.owner, repository.name, form.url)
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete the web hook URL.
|
|
||||||
*/
|
|
||||||
get("/:owner/:repository/settings/hooks/delete")(ownerOnly { repository =>
|
|
||||||
deleteWebHookURL(repository.owner, repository.name, params("url"))
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send the test request to registered web hook URLs.
|
|
||||||
*/
|
|
||||||
post("/:owner/:repository/settings/hooks/test", webHookForm)(ownerOnly { (form, repository) =>
|
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
|
||||||
import scala.collection.JavaConverters._
|
|
||||||
val commits = git.log
|
|
||||||
.add(git.getRepository.resolve(repository.repository.defaultBranch))
|
|
||||||
.setMaxCount(3)
|
|
||||||
.call.iterator.asScala.map(new CommitInfo(_))
|
|
||||||
|
|
||||||
getAccountByUserName(repository.owner).foreach { ownerAccount =>
|
|
||||||
callWebHook(repository.owner, repository.name,
|
|
||||||
List(model.WebHook(repository.owner, repository.name, form.url)),
|
|
||||||
WebHookPayload(git, ownerAccount, "refs/heads/" + repository.repository.defaultBranch, repository, commits.toList, ownerAccount)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
flash += "url" -> form.url
|
|
||||||
flash += "info" -> "Test payload deployed!"
|
|
||||||
}
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display the danger zone.
|
|
||||||
*/
|
|
||||||
get("/:owner/:repository/settings/danger")(ownerOnly {
|
|
||||||
settings.html.danger(_)
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transfer repository ownership.
|
|
||||||
*/
|
|
||||||
post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) =>
|
|
||||||
// Change repository owner
|
|
||||||
if(repository.owner != form.newOwner){
|
|
||||||
LockUtil.lock(s"${repository.owner}/${repository.name}"){
|
|
||||||
// Update database
|
|
||||||
renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
|
|
||||||
// Move git repository
|
|
||||||
defining(getRepositoryDir(repository.owner, repository.name)){ dir =>
|
|
||||||
FileUtils.moveDirectory(dir, getRepositoryDir(form.newOwner, repository.name))
|
|
||||||
}
|
|
||||||
// Move wiki repository
|
|
||||||
defining(getWikiRepositoryDir(repository.owner, repository.name)){ dir =>
|
|
||||||
FileUtils.moveDirectory(dir, getWikiRepositoryDir(form.newOwner, repository.name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
redirect(s"/${form.newOwner}/${repository.name}")
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete the repository.
|
|
||||||
*/
|
|
||||||
post("/:owner/:repository/settings/delete")(ownerOnly { repository =>
|
|
||||||
LockUtil.lock(s"${repository.owner}/${repository.name}"){
|
|
||||||
deleteRepository(repository.owner, repository.name)
|
|
||||||
|
|
||||||
FileUtils.deleteDirectory(getRepositoryDir(repository.owner, repository.name))
|
|
||||||
FileUtils.deleteDirectory(getWikiRepositoryDir(repository.owner, repository.name))
|
|
||||||
FileUtils.deleteDirectory(getTemporaryDir(repository.owner, repository.name))
|
|
||||||
}
|
|
||||||
redirect(s"/${repository.owner}")
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides duplication check for web hook url.
|
|
||||||
*/
|
|
||||||
private def webHook: Constraint = new Constraint(){
|
|
||||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
|
||||||
getWebHookURLs(params("owner"), params("repository")).map(_.url).find(_ == value).map(_ => "URL had been registered already.")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides Constraint to validate the collaborator name.
|
|
||||||
*/
|
|
||||||
private def collaborator: Constraint = new Constraint(){
|
|
||||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
|
||||||
getAccountByUserName(value) match {
|
|
||||||
case None => Some("User does not exist.")
|
|
||||||
case Some(x) if(x.isGroupAccount)
|
|
||||||
=> Some("User does not exist.")
|
|
||||||
case Some(x) if(x.userName == params("owner") || getCollaborators(params("owner"), params("repository")).contains(x.userName))
|
|
||||||
=> Some("User can access this repository already.")
|
|
||||||
case _ => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Duplicate check for the rename repository name.
|
|
||||||
*/
|
|
||||||
private def renameRepositoryName: Constraint = new Constraint(){
|
|
||||||
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] =
|
|
||||||
params.get("repository").filter(_ != value).flatMap { _ =>
|
|
||||||
params.get("owner").flatMap { userName =>
|
|
||||||
getRepositoryNamesOfUser(userName).find(_ == value).map(_ => "Repository already exists.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides Constraint to validate the repository transfer user.
|
|
||||||
*/
|
|
||||||
private def transferUser: Constraint = new Constraint(){
|
|
||||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
|
||||||
getAccountByUserName(value) match {
|
|
||||||
case None => Some("User does not exist.")
|
|
||||||
case Some(x) => if(x.userName == params("owner")){
|
|
||||||
Some("This is current repository owner.")
|
|
||||||
} else {
|
|
||||||
params.get("repository").flatMap { repositoryName =>
|
|
||||||
getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map{ _ => "User already has same repository." }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,553 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import _root_.util.JGitUtil.CommitInfo
|
|
||||||
import util.Directory._
|
|
||||||
import util.Implicits._
|
|
||||||
import _root_.util.ControlUtil._
|
|
||||||
import _root_.util._
|
|
||||||
import service._
|
|
||||||
import org.scalatra._
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
import org.eclipse.jgit.api.{ArchiveCommand, Git}
|
|
||||||
import org.eclipse.jgit.archive.{TgzFormat, ZipFormat}
|
|
||||||
import org.eclipse.jgit.lib._
|
|
||||||
import org.apache.commons.io.FileUtils
|
|
||||||
import org.eclipse.jgit.treewalk._
|
|
||||||
import jp.sf.amateras.scalatra.forms._
|
|
||||||
import org.eclipse.jgit.dircache.DirCache
|
|
||||||
import org.eclipse.jgit.revwalk.RevCommit
|
|
||||||
import service.WebHookService.WebHookPayload
|
|
||||||
|
|
||||||
class RepositoryViewerController extends RepositoryViewerControllerBase
|
|
||||||
with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
|
|
||||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The repository viewer.
|
|
||||||
*/
|
|
||||||
trait RepositoryViewerControllerBase extends ControllerBase {
|
|
||||||
self: RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
|
|
||||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator =>
|
|
||||||
|
|
||||||
ArchiveCommand.registerFormat("zip", new ZipFormat)
|
|
||||||
ArchiveCommand.registerFormat("tar.gz", new TgzFormat)
|
|
||||||
|
|
||||||
case class EditorForm(
|
|
||||||
branch: String,
|
|
||||||
path: String,
|
|
||||||
content: String,
|
|
||||||
message: Option[String],
|
|
||||||
charset: String,
|
|
||||||
lineSeparator: String,
|
|
||||||
newFileName: String,
|
|
||||||
oldFileName: Option[String]
|
|
||||||
)
|
|
||||||
|
|
||||||
case class DeleteForm(
|
|
||||||
branch: String,
|
|
||||||
path: String,
|
|
||||||
message: Option[String],
|
|
||||||
fileName: String
|
|
||||||
)
|
|
||||||
|
|
||||||
case class CommentForm(
|
|
||||||
fileName: Option[String],
|
|
||||||
oldLineNumber: Option[Int],
|
|
||||||
newLineNumber: Option[Int],
|
|
||||||
content: String,
|
|
||||||
pullRequest: Boolean
|
|
||||||
)
|
|
||||||
|
|
||||||
val editorForm = mapping(
|
|
||||||
"branch" -> trim(label("Branch", text(required))),
|
|
||||||
"path" -> trim(label("Path", text())),
|
|
||||||
"content" -> trim(label("Content", text(required))),
|
|
||||||
"message" -> trim(label("Message", optional(text()))),
|
|
||||||
"charset" -> trim(label("Charset", text(required))),
|
|
||||||
"lineSeparator" -> trim(label("Line Separator", text(required))),
|
|
||||||
"newFileName" -> trim(label("Filename", text(required))),
|
|
||||||
"oldFileName" -> trim(label("Old filename", optional(text())))
|
|
||||||
)(EditorForm.apply)
|
|
||||||
|
|
||||||
val deleteForm = mapping(
|
|
||||||
"branch" -> trim(label("Branch", text(required))),
|
|
||||||
"path" -> trim(label("Path", text())),
|
|
||||||
"message" -> trim(label("Message", optional(text()))),
|
|
||||||
"fileName" -> trim(label("Filename", text(required)))
|
|
||||||
)(DeleteForm.apply)
|
|
||||||
|
|
||||||
val commentForm = mapping(
|
|
||||||
"fileName" -> trim(label("Filename", optional(text()))),
|
|
||||||
"oldLineNumber" -> trim(label("Old line number", optional(number()))),
|
|
||||||
"newLineNumber" -> trim(label("New line number", optional(number()))),
|
|
||||||
"content" -> trim(label("Content", text(required))),
|
|
||||||
"pullRequest" -> trim(label("In pull request", boolean()))
|
|
||||||
)(CommentForm.apply)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns converted HTML from Markdown for preview.
|
|
||||||
*/
|
|
||||||
post("/:owner/:repository/_preview")(referrersOnly { repository =>
|
|
||||||
contentType = "text/html"
|
|
||||||
view.helpers.markdown(params("content"), repository,
|
|
||||||
params("enableWikiLink").toBoolean,
|
|
||||||
params("enableRefsLink").toBoolean,
|
|
||||||
params("enableTaskList").toBoolean,
|
|
||||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays the file list of the repository root and the default branch.
|
|
||||||
*/
|
|
||||||
get("/:owner/:repository")(referrersOnly {
|
|
||||||
fileList(_)
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays the file list of the specified path and branch.
|
|
||||||
*/
|
|
||||||
get("/:owner/:repository/tree/*")(referrersOnly { repository =>
|
|
||||||
val (id, path) = splitPath(repository, multiParams("splat").head)
|
|
||||||
if(path.isEmpty){
|
|
||||||
fileList(repository, id)
|
|
||||||
} else {
|
|
||||||
fileList(repository, id, path)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays the commit list of the specified resource.
|
|
||||||
*/
|
|
||||||
get("/:owner/:repository/commits/*")(referrersOnly { repository =>
|
|
||||||
val (branchName, path) = splitPath(repository, multiParams("splat").head)
|
|
||||||
val page = params.get("page").flatMap(_.toIntOpt).getOrElse(1)
|
|
||||||
|
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
|
||||||
JGitUtil.getCommitLog(git, branchName, page, 30, path) match {
|
|
||||||
case Right((logs, hasNext)) =>
|
|
||||||
repo.html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository,
|
|
||||||
logs.splitWith{ (commit1, commit2) =>
|
|
||||||
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
|
||||||
}, page, hasNext, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
|
||||||
case Left(_) => NotFound
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/new/*")(collaboratorsOnly { repository =>
|
|
||||||
val (branch, path) = splitPath(repository, multiParams("splat").head)
|
|
||||||
repo.html.editor(branch, repository, if(path.length == 0) Nil else path.split("/").toList,
|
|
||||||
None, JGitUtil.ContentInfo("text", None, Some("UTF-8")))
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/edit/*")(collaboratorsOnly { repository =>
|
|
||||||
val (branch, path) = splitPath(repository, multiParams("splat").head)
|
|
||||||
|
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
|
||||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
|
|
||||||
|
|
||||||
getPathObjectId(git, path, revCommit).map { objectId =>
|
|
||||||
val paths = path.split("/")
|
|
||||||
repo.html.editor(branch, repository, paths.take(paths.size - 1).toList, Some(paths.last),
|
|
||||||
JGitUtil.getContentInfo(git, path, objectId))
|
|
||||||
} getOrElse NotFound
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/remove/*")(collaboratorsOnly { repository =>
|
|
||||||
val (branch, path) = splitPath(repository, multiParams("splat").head)
|
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
|
||||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
|
|
||||||
|
|
||||||
getPathObjectId(git, path, revCommit).map { objectId =>
|
|
||||||
val paths = path.split("/")
|
|
||||||
repo.html.delete(branch, repository, paths.take(paths.size - 1).toList, paths.last,
|
|
||||||
JGitUtil.getContentInfo(git, path, objectId))
|
|
||||||
} getOrElse NotFound
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/:owner/:repository/create", editorForm)(collaboratorsOnly { (form, repository) =>
|
|
||||||
commitFile(repository, form.branch, form.path, Some(form.newFileName), None,
|
|
||||||
StringUtil.convertLineSeparator(form.content, form.lineSeparator), form.charset,
|
|
||||||
form.message.getOrElse(s"Create ${form.newFileName}"))
|
|
||||||
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/blob/${form.branch}/${
|
|
||||||
if(form.path.length == 0) form.newFileName else s"${form.path}/${form.newFileName}"
|
|
||||||
}")
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/:owner/:repository/update", editorForm)(collaboratorsOnly { (form, repository) =>
|
|
||||||
commitFile(repository, form.branch, form.path, Some(form.newFileName), form.oldFileName,
|
|
||||||
StringUtil.convertLineSeparator(form.content, form.lineSeparator), form.charset,
|
|
||||||
if(form.oldFileName.exists(_ == form.newFileName)){
|
|
||||||
form.message.getOrElse(s"Update ${form.newFileName}")
|
|
||||||
} else {
|
|
||||||
form.message.getOrElse(s"Rename ${form.oldFileName.get} to ${form.newFileName}")
|
|
||||||
})
|
|
||||||
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/blob/${form.branch}/${
|
|
||||||
if(form.path.length == 0) form.newFileName else s"${form.path}/${form.newFileName}"
|
|
||||||
}")
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/:owner/:repository/remove", deleteForm)(collaboratorsOnly { (form, repository) =>
|
|
||||||
commitFile(repository, form.branch, form.path, None, Some(form.fileName), "", "",
|
|
||||||
form.message.getOrElse(s"Delete ${form.fileName}"))
|
|
||||||
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}${if(form.path.length == 0) "" else form.path}")
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays the file content of the specified branch or commit.
|
|
||||||
*/
|
|
||||||
get("/:owner/:repository/blob/*")(referrersOnly { repository =>
|
|
||||||
val (id, path) = splitPath(repository, multiParams("splat").head)
|
|
||||||
val raw = params.get("raw").getOrElse("false").toBoolean
|
|
||||||
|
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
|
||||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
|
||||||
val lastModifiedCommit = JGitUtil.getLastModifiedCommit(git, revCommit, path)
|
|
||||||
getPathObjectId(git, path, revCommit).map { objectId =>
|
|
||||||
if(raw){
|
|
||||||
// Download
|
|
||||||
defining(JGitUtil.getContentFromId(git, objectId, false).get){ bytes =>
|
|
||||||
contentType = FileUtil.getContentType(path, bytes)
|
|
||||||
bytes
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
repo.html.blob(id, repository, path.split("/").toList, JGitUtil.getContentInfo(git, path, objectId),
|
|
||||||
new JGitUtil.CommitInfo(lastModifiedCommit), hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
|
||||||
}
|
|
||||||
} getOrElse NotFound
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays details of the specified commit.
|
|
||||||
*/
|
|
||||||
get("/:owner/:repository/commit/:id")(referrersOnly { repository =>
|
|
||||||
val id = params("id")
|
|
||||||
|
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
|
||||||
defining(JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))){ revCommit =>
|
|
||||||
JGitUtil.getDiffs(git, id) match { case (diffs, oldCommitId) =>
|
|
||||||
repo.html.commit(id, new JGitUtil.CommitInfo(revCommit),
|
|
||||||
JGitUtil.getBranchesOfCommit(git, revCommit.getName),
|
|
||||||
JGitUtil.getTagsOfCommit(git, revCommit.getName),
|
|
||||||
getCommitComments(repository.owner, repository.name, id, false),
|
|
||||||
repository, diffs, oldCommitId, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/:owner/:repository/commit/:id/comment/new", commentForm)(readableUsersOnly { (form, repository) =>
|
|
||||||
val id = params("id")
|
|
||||||
createCommitComment(repository.owner, repository.name, id, context.loginAccount.get.userName, form.content,
|
|
||||||
form.fileName, form.oldLineNumber, form.newLineNumber, form.pullRequest)
|
|
||||||
recordCommentCommitActivity(repository.owner, repository.name, context.loginAccount.get.userName, id, form.content)
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/commit/${id}")
|
|
||||||
})
|
|
||||||
|
|
||||||
ajaxGet("/:owner/:repository/commit/:id/comment/_form")(readableUsersOnly { repository =>
|
|
||||||
val id = params("id")
|
|
||||||
val fileName = params.get("fileName")
|
|
||||||
val oldLineNumber = params.get("oldLineNumber") flatMap {b => Some(b.toInt)}
|
|
||||||
val newLineNumber = params.get("newLineNumber") flatMap {b => Some(b.toInt)}
|
|
||||||
val pullRequest = params.get("pullRequest")
|
|
||||||
repo.html.commentform(
|
|
||||||
commitId = id,
|
|
||||||
fileName, oldLineNumber, newLineNumber, pullRequest.map(_.toBoolean).getOrElse(false),
|
|
||||||
hasWritePermission = hasWritePermission(repository.owner, repository.name, context.loginAccount),
|
|
||||||
repository = repository
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/commit/:id/comment/_data/new", commentForm)(readableUsersOnly { (form, repository) =>
|
|
||||||
val id = params("id")
|
|
||||||
val commentId = createCommitComment(repository.owner, repository.name, id, context.loginAccount.get.userName,
|
|
||||||
form.content, form.fileName, form.oldLineNumber, form.newLineNumber, form.pullRequest)
|
|
||||||
recordCommentCommitActivity(repository.owner, repository.name, context.loginAccount.get.userName, id, form.content)
|
|
||||||
helper.html.commitcomment(getCommitComment(repository.owner, repository.name, commentId.toString).get,
|
|
||||||
hasWritePermission(repository.owner, repository.name, context.loginAccount), repository)
|
|
||||||
})
|
|
||||||
|
|
||||||
ajaxGet("/:owner/:repository/commit_comments/_data/:id")(readableUsersOnly { repository =>
|
|
||||||
getCommitComment(repository.owner, repository.name, params("id")) map { x =>
|
|
||||||
if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
|
|
||||||
params.get("dataType") collect {
|
|
||||||
case t if t == "html" => repo.html.editcomment(
|
|
||||||
x.content, x.commentId, x.userName, x.repositoryName)
|
|
||||||
} getOrElse {
|
|
||||||
contentType = formats("json")
|
|
||||||
org.json4s.jackson.Serialization.write(
|
|
||||||
Map("content" -> view.Markdown.toHtml(x.content,
|
|
||||||
repository, false, true, true, isEditable(x.userName, x.repositoryName, x.commentedUserName))
|
|
||||||
))
|
|
||||||
}
|
|
||||||
} else Unauthorized
|
|
||||||
} getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/commit_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
|
|
||||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
|
||||||
getCommitComment(owner, name, params("id")).map { comment =>
|
|
||||||
if(isEditable(owner, name, comment.commentedUserName)){
|
|
||||||
updateCommitComment(comment.commentId, form.content)
|
|
||||||
redirect(s"/${owner}/${name}/commit_comments/_data/${comment.commentId}")
|
|
||||||
} else Unauthorized
|
|
||||||
} getOrElse NotFound
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/commit_comments/delete/:id")(readableUsersOnly { repository =>
|
|
||||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
|
||||||
getCommitComment(owner, name, params("id")).map { comment =>
|
|
||||||
if(isEditable(owner, name, comment.commentedUserName)){
|
|
||||||
Ok(deleteCommitComment(comment.commentId))
|
|
||||||
} else Unauthorized
|
|
||||||
} getOrElse NotFound
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays branches.
|
|
||||||
*/
|
|
||||||
get("/:owner/:repository/branches")(referrersOnly { repository =>
|
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
|
||||||
// retrieve latest update date of each branch
|
|
||||||
val branchInfo = repository.branchList.map { branchName =>
|
|
||||||
val revCommit = git.log.add(git.getRepository.resolve(branchName)).setMaxCount(1).call.iterator.next
|
|
||||||
(branchName, revCommit.getCommitterIdent.getWhen)
|
|
||||||
}
|
|
||||||
repo.html.branches(branchInfo, hasWritePermission(repository.owner, repository.name, context.loginAccount), repository)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a branch.
|
|
||||||
*/
|
|
||||||
post("/:owner/:repository/branches")(collaboratorsOnly { repository =>
|
|
||||||
val newBranchName = params.getOrElse("new", halt(400))
|
|
||||||
val fromBranchName = params.getOrElse("from", halt(400))
|
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
|
||||||
JGitUtil.createBranch(git, fromBranchName, newBranchName)
|
|
||||||
} match {
|
|
||||||
case Right(message) =>
|
|
||||||
flash += "info" -> message
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/tree/${StringUtil.urlEncode(newBranchName).replace("%2F", "/")}")
|
|
||||||
case Left(message) =>
|
|
||||||
flash += "error" -> message
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/tree/${fromBranchName}")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes branch.
|
|
||||||
*/
|
|
||||||
get("/:owner/:repository/delete/*")(collaboratorsOnly { repository =>
|
|
||||||
val branchName = multiParams("splat").head
|
|
||||||
val userName = context.loginAccount.get.userName
|
|
||||||
if(repository.repository.defaultBranch != branchName){
|
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
|
||||||
git.branchDelete().setForce(true).setBranchNames(branchName).call()
|
|
||||||
recordDeleteBranchActivity(repository.owner, repository.name, userName, branchName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/branches")
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays tags.
|
|
||||||
*/
|
|
||||||
get("/:owner/:repository/tags")(referrersOnly {
|
|
||||||
repo.html.tags(_)
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Download repository contents as an archive.
|
|
||||||
*/
|
|
||||||
get("/:owner/:repository/archive/*")(referrersOnly { repository =>
|
|
||||||
multiParams("splat").head match {
|
|
||||||
case name if name.endsWith(".zip") =>
|
|
||||||
archiveRepository(name, ".zip", repository)
|
|
||||||
case name if name.endsWith(".tar.gz") =>
|
|
||||||
archiveRepository(name, ".tar.gz", repository)
|
|
||||||
case _ => BadRequest
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/network/members")(referrersOnly { repository =>
|
|
||||||
repo.html.forked(
|
|
||||||
getRepository(
|
|
||||||
repository.repository.originUserName.getOrElse(repository.owner),
|
|
||||||
repository.repository.originRepositoryName.getOrElse(repository.name),
|
|
||||||
context.baseUrl),
|
|
||||||
getForkedRepositories(
|
|
||||||
repository.repository.originUserName.getOrElse(repository.owner),
|
|
||||||
repository.repository.originRepositoryName.getOrElse(repository.name)),
|
|
||||||
repository)
|
|
||||||
})
|
|
||||||
|
|
||||||
private def splitPath(repository: service.RepositoryService.RepositoryInfo, path: String): (String, String) = {
|
|
||||||
val id = repository.branchList.collectFirst {
|
|
||||||
case branch if(path == branch || path.startsWith(branch + "/")) => branch
|
|
||||||
} orElse repository.tags.collectFirst {
|
|
||||||
case tag if(path == tag.name || path.startsWith(tag.name + "/")) => tag.name
|
|
||||||
} getOrElse path.split("/")(0)
|
|
||||||
|
|
||||||
(id, path.substring(id.length).stripPrefix("/"))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private val readmeFiles = view.helpers.renderableSuffixes.map(suffix => s"readme${suffix}") ++ Seq("readme.txt", "readme")
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides HTML of the file list.
|
|
||||||
*
|
|
||||||
* @param repository the repository information
|
|
||||||
* @param revstr the branch name or commit id(optional)
|
|
||||||
* @param path the directory path (optional)
|
|
||||||
* @return HTML of the file list
|
|
||||||
*/
|
|
||||||
private def fileList(repository: RepositoryService.RepositoryInfo, revstr: String = "", path: String = ".") = {
|
|
||||||
if(repository.commitCount == 0){
|
|
||||||
repo.html.guide(repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
|
||||||
} else {
|
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
|
||||||
// get specified commit
|
|
||||||
JGitUtil.getDefaultBranch(git, repository, revstr).map { case (objectId, revision) =>
|
|
||||||
defining(JGitUtil.getRevCommitFromId(git, objectId)) { revCommit =>
|
|
||||||
val lastModifiedCommit = if(path == ".") revCommit else JGitUtil.getLastModifiedCommit(git, revCommit, path)
|
|
||||||
// get files
|
|
||||||
val files = JGitUtil.getFileList(git, revision, path)
|
|
||||||
val parentPath = if (path == ".") Nil else path.split("/").toList
|
|
||||||
// process README.md or README.markdown
|
|
||||||
val readme = files.find { file =>
|
|
||||||
readmeFiles.contains(file.name.toLowerCase)
|
|
||||||
}.map { file =>
|
|
||||||
val path = (file.name :: parentPath.reverse).reverse
|
|
||||||
path -> StringUtil.convertFromByteArray(JGitUtil.getContentFromId(
|
|
||||||
Git.open(getRepositoryDir(repository.owner, repository.name)), file.id, true).get)
|
|
||||||
}
|
|
||||||
|
|
||||||
repo.html.files(revision, repository,
|
|
||||||
if(path == ".") Nil else path.split("/").toList, // current path
|
|
||||||
new JGitUtil.CommitInfo(lastModifiedCommit), // last modified commit
|
|
||||||
files, readme, hasWritePermission(repository.owner, repository.name, context.loginAccount),
|
|
||||||
flash.get("info"), flash.get("error"))
|
|
||||||
}
|
|
||||||
} getOrElse NotFound
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def commitFile(repository: service.RepositoryService.RepositoryInfo,
|
|
||||||
branch: String, path: String, newFileName: Option[String], oldFileName: Option[String],
|
|
||||||
content: String, charset: String, message: String) = {
|
|
||||||
|
|
||||||
val newPath = newFileName.map { newFileName => if(path.length == 0) newFileName else s"${path}/${newFileName}" }
|
|
||||||
val oldPath = oldFileName.map { oldFileName => if(path.length == 0) oldFileName else s"${path}/${oldFileName}" }
|
|
||||||
|
|
||||||
LockUtil.lock(s"${repository.owner}/${repository.name}"){
|
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
|
||||||
val loginAccount = context.loginAccount.get
|
|
||||||
val builder = DirCache.newInCore.builder()
|
|
||||||
val inserter = git.getRepository.newObjectInserter()
|
|
||||||
val headName = s"refs/heads/${branch}"
|
|
||||||
val headTip = git.getRepository.resolve(headName)
|
|
||||||
|
|
||||||
JGitUtil.processTree(git, headTip){ (path, tree) =>
|
|
||||||
if(!newPath.exists(_ == path) && !oldPath.exists(_ == path)){
|
|
||||||
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newPath.foreach { newPath =>
|
|
||||||
builder.add(JGitUtil.createDirCacheEntry(newPath, FileMode.REGULAR_FILE,
|
|
||||||
inserter.insert(Constants.OBJ_BLOB, content.getBytes(charset))))
|
|
||||||
}
|
|
||||||
builder.finish()
|
|
||||||
|
|
||||||
val commitId = JGitUtil.createNewCommit(git, inserter, headTip, builder.getDirCache.writeTree(inserter),
|
|
||||||
headName, loginAccount.fullName, loginAccount.mailAddress, message)
|
|
||||||
|
|
||||||
inserter.flush()
|
|
||||||
inserter.release()
|
|
||||||
|
|
||||||
// update refs
|
|
||||||
val refUpdate = git.getRepository.updateRef(headName)
|
|
||||||
refUpdate.setNewObjectId(commitId)
|
|
||||||
refUpdate.setForceUpdate(false)
|
|
||||||
refUpdate.setRefLogIdent(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress))
|
|
||||||
//refUpdate.setRefLogMessage("merged", true)
|
|
||||||
refUpdate.update()
|
|
||||||
|
|
||||||
// record activity
|
|
||||||
recordPushActivity(repository.owner, repository.name, loginAccount.userName, branch,
|
|
||||||
List(new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))))
|
|
||||||
|
|
||||||
// close issue by commit message
|
|
||||||
closeIssuesFromMessage(message, loginAccount.userName, repository.owner, repository.name)
|
|
||||||
|
|
||||||
// call web hook
|
|
||||||
val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
|
||||||
getWebHookURLs(repository.owner, repository.name) match {
|
|
||||||
case webHookURLs if(webHookURLs.nonEmpty) =>
|
|
||||||
for(ownerAccount <- getAccountByUserName(repository.owner)){
|
|
||||||
callWebHook(repository.owner, repository.name, webHookURLs,
|
|
||||||
WebHookPayload(git, loginAccount, headName, repository, List(commit), ownerAccount))
|
|
||||||
}
|
|
||||||
case _ =>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def archiveRepository(name: String, suffix: String, repository: RepositoryService.RepositoryInfo): File = {
|
|
||||||
val revision = name.stripSuffix(suffix)
|
|
||||||
val workDir = getDownloadWorkDir(repository.owner, repository.name, session.getId)
|
|
||||||
if(workDir.exists) {
|
|
||||||
FileUtils.deleteDirectory(workDir)
|
|
||||||
}
|
|
||||||
workDir.mkdirs
|
|
||||||
|
|
||||||
val file = new File(workDir, repository.name + "-" +
|
|
||||||
(if(revision.length == 40) revision.substring(0, 10) else revision).replace('/', '_') + suffix)
|
|
||||||
|
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
|
||||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(revision))
|
|
||||||
using(new java.io.FileOutputStream(file)) { out =>
|
|
||||||
git.archive
|
|
||||||
.setFormat(suffix.tail)
|
|
||||||
.setTree(revCommit.getTree)
|
|
||||||
.setOutputStream(out)
|
|
||||||
.call()
|
|
||||||
}
|
|
||||||
contentType = "application/octet-stream"
|
|
||||||
response.setHeader("Content-Disposition", s"attachment; filename=${file.getName}")
|
|
||||||
file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def isEditable(owner: String, repository: String, author: String)(implicit context: app.Context): Boolean =
|
|
||||||
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import util._
|
|
||||||
import ControlUtil._
|
|
||||||
import Implicits._
|
|
||||||
import service._
|
|
||||||
import jp.sf.amateras.scalatra.forms._
|
|
||||||
|
|
||||||
class SearchController extends SearchControllerBase
|
|
||||||
with RepositoryService with AccountService with ActivityService with RepositorySearchService with IssuesService with ReferrerAuthenticator
|
|
||||||
|
|
||||||
trait SearchControllerBase extends ControllerBase { self: RepositoryService
|
|
||||||
with ActivityService with RepositorySearchService with ReferrerAuthenticator =>
|
|
||||||
|
|
||||||
val searchForm = mapping(
|
|
||||||
"query" -> trim(text(required)),
|
|
||||||
"owner" -> trim(text(required)),
|
|
||||||
"repository" -> trim(text(required))
|
|
||||||
)(SearchForm.apply)
|
|
||||||
|
|
||||||
case class SearchForm(query: String, owner: String, repository: String)
|
|
||||||
|
|
||||||
post("/search", searchForm){ form =>
|
|
||||||
redirect(s"/${form.owner}/${form.repository}/search?q=${StringUtil.urlEncode(form.query)}")
|
|
||||||
}
|
|
||||||
|
|
||||||
get("/:owner/:repository/search")(referrersOnly { repository =>
|
|
||||||
defining(params("q").trim, params.getOrElse("type", "code")){ case (query, target) =>
|
|
||||||
val page = try {
|
|
||||||
val i = params.getOrElse("page", "1").toInt
|
|
||||||
if(i <= 0) 1 else i
|
|
||||||
} catch {
|
|
||||||
case e: NumberFormatException => 1
|
|
||||||
}
|
|
||||||
|
|
||||||
target.toLowerCase match {
|
|
||||||
case "issue" => search.html.issues(
|
|
||||||
searchIssues(repository.owner, repository.name, query),
|
|
||||||
countFiles(repository.owner, repository.name, query),
|
|
||||||
query, page, repository)
|
|
||||||
|
|
||||||
case _ => search.html.code(
|
|
||||||
searchFiles(repository.owner, repository.name, query),
|
|
||||||
countIssues(repository.owner, repository.name, query),
|
|
||||||
query, page, repository)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import service.{AccountService, SystemSettingsService}
|
|
||||||
import SystemSettingsService._
|
|
||||||
import util.AdminAuthenticator
|
|
||||||
import util.Directory._
|
|
||||||
import util.ControlUtil._
|
|
||||||
import jp.sf.amateras.scalatra.forms._
|
|
||||||
import ssh.SshServer
|
|
||||||
import org.apache.commons.io.FileUtils
|
|
||||||
import java.io.FileInputStream
|
|
||||||
import plugin.{Plugin, PluginSystem}
|
|
||||||
import org.scalatra.Ok
|
|
||||||
import util.Implicits._
|
|
||||||
|
|
||||||
class SystemSettingsController extends SystemSettingsControllerBase
|
|
||||||
with AccountService with AdminAuthenticator
|
|
||||||
|
|
||||||
trait SystemSettingsControllerBase extends ControllerBase {
|
|
||||||
self: AccountService with AdminAuthenticator =>
|
|
||||||
|
|
||||||
private val form = mapping(
|
|
||||||
"baseUrl" -> trim(label("Base URL", optional(text()))),
|
|
||||||
"information" -> trim(label("Information", optional(text()))),
|
|
||||||
"allowAccountRegistration" -> trim(label("Account registration", boolean())),
|
|
||||||
"gravatar" -> trim(label("Gravatar", boolean())),
|
|
||||||
"notification" -> trim(label("Notification", boolean())),
|
|
||||||
"ssh" -> trim(label("SSH access", boolean())),
|
|
||||||
"sshPort" -> trim(label("SSH port", optional(number()))),
|
|
||||||
"smtp" -> optionalIfNotChecked("notification", mapping(
|
|
||||||
"host" -> trim(label("SMTP Host", text(required))),
|
|
||||||
"port" -> trim(label("SMTP Port", optional(number()))),
|
|
||||||
"user" -> trim(label("SMTP User", optional(text()))),
|
|
||||||
"password" -> trim(label("SMTP Password", optional(text()))),
|
|
||||||
"ssl" -> trim(label("Enable SSL", optional(boolean()))),
|
|
||||||
"fromAddress" -> trim(label("FROM Address", optional(text()))),
|
|
||||||
"fromName" -> trim(label("FROM Name", optional(text())))
|
|
||||||
)(Smtp.apply)),
|
|
||||||
"ldapAuthentication" -> trim(label("LDAP", boolean())),
|
|
||||||
"ldap" -> optionalIfNotChecked("ldapAuthentication", mapping(
|
|
||||||
"host" -> trim(label("LDAP host", text(required))),
|
|
||||||
"port" -> trim(label("LDAP port", optional(number()))),
|
|
||||||
"bindDN" -> trim(label("Bind DN", optional(text()))),
|
|
||||||
"bindPassword" -> trim(label("Bind Password", optional(text()))),
|
|
||||||
"baseDN" -> trim(label("Base DN", text(required))),
|
|
||||||
"userNameAttribute" -> trim(label("User name attribute", text(required))),
|
|
||||||
"additionalFilterCondition"-> trim(label("Additional filter condition", optional(text()))),
|
|
||||||
"fullNameAttribute" -> trim(label("Full name attribute", optional(text()))),
|
|
||||||
"mailAttribute" -> trim(label("Mail address attribute", optional(text()))),
|
|
||||||
"tls" -> trim(label("Enable TLS", optional(boolean()))),
|
|
||||||
"keystore" -> trim(label("Keystore", optional(text())))
|
|
||||||
)(Ldap.apply))
|
|
||||||
)(SystemSettings.apply).verifying { settings =>
|
|
||||||
if(settings.ssh && settings.baseUrl.isEmpty){
|
|
||||||
Seq("baseUrl" -> "Base URL is required if SSH access is enabled.")
|
|
||||||
} else Nil
|
|
||||||
}
|
|
||||||
|
|
||||||
private val pluginForm = mapping(
|
|
||||||
"pluginId" -> list(trim(label("", text())))
|
|
||||||
)(PluginForm.apply)
|
|
||||||
|
|
||||||
case class PluginForm(pluginIds: List[String])
|
|
||||||
|
|
||||||
get("/admin/system")(adminOnly {
|
|
||||||
admin.html.system(flash.get("info"))
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/admin/system", form)(adminOnly { form =>
|
|
||||||
saveSystemSettings(form)
|
|
||||||
|
|
||||||
if(form.ssh && SshServer.isActive && context.settings.sshPort != form.sshPort){
|
|
||||||
SshServer.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
if(form.ssh && !SshServer.isActive && form.baseUrl.isDefined){
|
|
||||||
SshServer.start(request.getServletContext,
|
|
||||||
form.sshPort.getOrElse(SystemSettingsService.DefaultSshPort),
|
|
||||||
form.baseUrl.get)
|
|
||||||
} else if(!form.ssh && SshServer.isActive){
|
|
||||||
SshServer.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
flash += "info" -> "System settings has been updated."
|
|
||||||
redirect("/admin/system")
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/admin/plugins")(adminOnly {
|
|
||||||
if(enablePluginSystem){
|
|
||||||
val installedPlugins = plugin.PluginSystem.plugins
|
|
||||||
val updatablePlugins = getAvailablePlugins(installedPlugins).filter(_.status == "updatable")
|
|
||||||
admin.plugins.html.installed(installedPlugins, updatablePlugins)
|
|
||||||
} else NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/admin/plugins/_update", pluginForm)(adminOnly { form =>
|
|
||||||
if(enablePluginSystem){
|
|
||||||
deletePlugins(form.pluginIds)
|
|
||||||
installPlugins(form.pluginIds)
|
|
||||||
redirect("/admin/plugins")
|
|
||||||
} else NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/admin/plugins/_delete", pluginForm)(adminOnly { form =>
|
|
||||||
if(enablePluginSystem){
|
|
||||||
deletePlugins(form.pluginIds)
|
|
||||||
redirect("/admin/plugins")
|
|
||||||
} else NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/admin/plugins/available")(adminOnly {
|
|
||||||
if(enablePluginSystem){
|
|
||||||
val installedPlugins = plugin.PluginSystem.plugins
|
|
||||||
val availablePlugins = getAvailablePlugins(installedPlugins).filter(_.status == "available")
|
|
||||||
admin.plugins.html.available(availablePlugins)
|
|
||||||
} else NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/admin/plugins/_install", pluginForm)(adminOnly { form =>
|
|
||||||
if(enablePluginSystem){
|
|
||||||
installPlugins(form.pluginIds)
|
|
||||||
redirect("/admin/plugins")
|
|
||||||
} else NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/admin/plugins/console")(adminOnly {
|
|
||||||
if(enablePluginSystem){
|
|
||||||
admin.plugins.html.console()
|
|
||||||
} else NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/admin/plugins/console")(adminOnly {
|
|
||||||
if(enablePluginSystem){
|
|
||||||
val script = request.getParameter("script")
|
|
||||||
val result = plugin.ScalaPlugin.eval(script)
|
|
||||||
Ok()
|
|
||||||
} else NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
// TODO Move these methods to PluginSystem or Service?
|
|
||||||
private def deletePlugins(pluginIds: List[String]): Unit = {
|
|
||||||
pluginIds.foreach { pluginId =>
|
|
||||||
plugin.PluginSystem.uninstall(pluginId)
|
|
||||||
val dir = new java.io.File(PluginHome, pluginId)
|
|
||||||
if(dir.exists && dir.isDirectory){
|
|
||||||
FileUtils.deleteQuietly(dir)
|
|
||||||
PluginSystem.uninstall(pluginId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def installPlugins(pluginIds: List[String]): Unit = {
|
|
||||||
val dir = getPluginCacheDir()
|
|
||||||
val installedPlugins = plugin.PluginSystem.plugins
|
|
||||||
getAvailablePlugins(installedPlugins).filter(x => pluginIds.contains(x.id)).foreach { plugin =>
|
|
||||||
val pluginDir = new java.io.File(PluginHome, plugin.id)
|
|
||||||
if(pluginDir.exists){
|
|
||||||
FileUtils.deleteDirectory(pluginDir)
|
|
||||||
}
|
|
||||||
FileUtils.copyDirectory(new java.io.File(dir, plugin.repository + "/" + plugin.id), pluginDir)
|
|
||||||
PluginSystem.installPlugin(plugin.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def getAvailablePlugins(installedPlugins: List[Plugin]): List[SystemSettingsControllerBase.AvailablePlugin] = {
|
|
||||||
val repositoryRoot = getPluginCacheDir()
|
|
||||||
|
|
||||||
if(repositoryRoot.exists && repositoryRoot.isDirectory){
|
|
||||||
PluginSystem.repositories.flatMap { repo =>
|
|
||||||
val repoDir = new java.io.File(repositoryRoot, repo.id)
|
|
||||||
if(repoDir.exists && repoDir.isDirectory){
|
|
||||||
repoDir.listFiles.filter(d => d.isDirectory && !d.getName.startsWith(".")).map { plugin =>
|
|
||||||
val propertyFile = new java.io.File(plugin, "plugin.properties")
|
|
||||||
val properties = new java.util.Properties()
|
|
||||||
if(propertyFile.exists && propertyFile.isFile){
|
|
||||||
using(new FileInputStream(propertyFile)){ in =>
|
|
||||||
properties.load(in)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SystemSettingsControllerBase.AvailablePlugin(
|
|
||||||
repository = repo.id,
|
|
||||||
id = properties.getProperty("id"),
|
|
||||||
version = properties.getProperty("version"),
|
|
||||||
author = properties.getProperty("author"),
|
|
||||||
url = properties.getProperty("url"),
|
|
||||||
description = properties.getProperty("description"),
|
|
||||||
status = installedPlugins.find(_.id == properties.getProperty("id")) match {
|
|
||||||
case Some(x) if(PluginSystem.isUpdatable(x.version, properties.getProperty("version")))=> "updatable"
|
|
||||||
case Some(x) => "installed"
|
|
||||||
case None => "available"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else Nil
|
|
||||||
}
|
|
||||||
} else Nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object SystemSettingsControllerBase {
|
|
||||||
case class AvailablePlugin(repository: String, id: String, version: String,
|
|
||||||
author: String, url: String, description: String, status: String)
|
|
||||||
}
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import service._
|
|
||||||
import util.AdminAuthenticator
|
|
||||||
import util.StringUtil._
|
|
||||||
import util.ControlUtil._
|
|
||||||
import util.Directory._
|
|
||||||
import util.Implicits._
|
|
||||||
import jp.sf.amateras.scalatra.forms._
|
|
||||||
import org.scalatra.i18n.Messages
|
|
||||||
import org.apache.commons.io.FileUtils
|
|
||||||
|
|
||||||
class UserManagementController extends UserManagementControllerBase
|
|
||||||
with AccountService with RepositoryService with AdminAuthenticator
|
|
||||||
|
|
||||||
trait UserManagementControllerBase extends AccountManagementControllerBase {
|
|
||||||
self: AccountService with RepositoryService with AdminAuthenticator =>
|
|
||||||
|
|
||||||
case class NewUserForm(userName: String, password: String, fullName: String,
|
|
||||||
mailAddress: String, isAdmin: Boolean,
|
|
||||||
url: Option[String], fileId: Option[String])
|
|
||||||
|
|
||||||
case class EditUserForm(userName: String, password: Option[String], fullName: String,
|
|
||||||
mailAddress: String, isAdmin: Boolean, url: Option[String],
|
|
||||||
fileId: Option[String], clearImage: Boolean, isRemoved: Boolean)
|
|
||||||
|
|
||||||
case class NewGroupForm(groupName: String, url: Option[String], fileId: Option[String],
|
|
||||||
members: String)
|
|
||||||
|
|
||||||
case class EditGroupForm(groupName: String, url: Option[String], fileId: Option[String],
|
|
||||||
members: String, clearImage: Boolean, isRemoved: Boolean)
|
|
||||||
|
|
||||||
val newUserForm = mapping(
|
|
||||||
"userName" -> trim(label("Username" ,text(required, maxlength(100), identifier, uniqueUserName))),
|
|
||||||
"password" -> trim(label("Password" ,text(required, maxlength(20)))),
|
|
||||||
"fullName" -> trim(label("Full Name" ,text(required, maxlength(100)))),
|
|
||||||
"mailAddress" -> trim(label("Mail Address" ,text(required, maxlength(100), uniqueMailAddress()))),
|
|
||||||
"isAdmin" -> trim(label("User Type" ,boolean())),
|
|
||||||
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
|
||||||
"fileId" -> trim(label("File ID" ,optional(text())))
|
|
||||||
)(NewUserForm.apply)
|
|
||||||
|
|
||||||
val editUserForm = mapping(
|
|
||||||
"userName" -> trim(label("Username" ,text(required, maxlength(100), identifier))),
|
|
||||||
"password" -> trim(label("Password" ,optional(text(maxlength(20))))),
|
|
||||||
"fullName" -> trim(label("Full Name" ,text(required, maxlength(100)))),
|
|
||||||
"mailAddress" -> trim(label("Mail Address" ,text(required, maxlength(100), uniqueMailAddress("userName")))),
|
|
||||||
"isAdmin" -> trim(label("User Type" ,boolean())),
|
|
||||||
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
|
||||||
"fileId" -> trim(label("File ID" ,optional(text()))),
|
|
||||||
"clearImage" -> trim(label("Clear image" ,boolean())),
|
|
||||||
"removed" -> trim(label("Disable" ,boolean(disableByNotYourself("userName"))))
|
|
||||||
)(EditUserForm.apply)
|
|
||||||
|
|
||||||
val newGroupForm = mapping(
|
|
||||||
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier, uniqueUserName))),
|
|
||||||
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
|
||||||
"fileId" -> trim(label("File ID" ,optional(text()))),
|
|
||||||
"members" -> trim(label("Members" ,text(required, members)))
|
|
||||||
)(NewGroupForm.apply)
|
|
||||||
|
|
||||||
val editGroupForm = mapping(
|
|
||||||
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier))),
|
|
||||||
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
|
||||||
"fileId" -> trim(label("File ID" ,optional(text()))),
|
|
||||||
"members" -> trim(label("Members" ,text(required, members))),
|
|
||||||
"clearImage" -> trim(label("Clear image" ,boolean())),
|
|
||||||
"removed" -> trim(label("Disable" ,boolean()))
|
|
||||||
)(EditGroupForm.apply)
|
|
||||||
|
|
||||||
get("/admin/users")(adminOnly {
|
|
||||||
val includeRemoved = params.get("includeRemoved").map(_.toBoolean).getOrElse(false)
|
|
||||||
val users = getAllUsers(includeRemoved)
|
|
||||||
val members = users.collect { case account if(account.isGroupAccount) =>
|
|
||||||
account.userName -> getGroupMembers(account.userName).map(_.userName)
|
|
||||||
}.toMap
|
|
||||||
|
|
||||||
admin.users.html.list(users, members, includeRemoved)
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/admin/users/_newuser")(adminOnly {
|
|
||||||
admin.users.html.user(None)
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/admin/users/_newuser", newUserForm)(adminOnly { form =>
|
|
||||||
createAccount(form.userName, sha1(form.password), form.fullName, form.mailAddress, form.isAdmin, form.url)
|
|
||||||
updateImage(form.userName, form.fileId, false)
|
|
||||||
redirect("/admin/users")
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/admin/users/:userName/_edituser")(adminOnly {
|
|
||||||
val userName = params("userName")
|
|
||||||
admin.users.html.user(getAccountByUserName(userName, true))
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/admin/users/:name/_edituser", editUserForm)(adminOnly { form =>
|
|
||||||
val userName = params("userName")
|
|
||||||
getAccountByUserName(userName, true).map { account =>
|
|
||||||
|
|
||||||
if(form.isRemoved){
|
|
||||||
// Remove repositories
|
|
||||||
getRepositoryNamesOfUser(userName).foreach { repositoryName =>
|
|
||||||
deleteRepository(userName, repositoryName)
|
|
||||||
FileUtils.deleteDirectory(getRepositoryDir(userName, repositoryName))
|
|
||||||
FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
|
|
||||||
FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
|
|
||||||
}
|
|
||||||
// Remove from GROUP_MEMBER, COLLABORATOR and REPOSITORY
|
|
||||||
removeUserRelatedData(userName)
|
|
||||||
}
|
|
||||||
|
|
||||||
updateAccount(account.copy(
|
|
||||||
password = form.password.map(sha1).getOrElse(account.password),
|
|
||||||
fullName = form.fullName,
|
|
||||||
mailAddress = form.mailAddress,
|
|
||||||
isAdmin = form.isAdmin,
|
|
||||||
url = form.url,
|
|
||||||
isRemoved = form.isRemoved))
|
|
||||||
|
|
||||||
updateImage(userName, form.fileId, form.clearImage)
|
|
||||||
redirect("/admin/users")
|
|
||||||
|
|
||||||
} getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/admin/users/_newgroup")(adminOnly {
|
|
||||||
admin.users.html.group(None, Nil)
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/admin/users/_newgroup", newGroupForm)(adminOnly { form =>
|
|
||||||
createGroup(form.groupName, form.url)
|
|
||||||
updateGroupMembers(form.groupName, form.members.split(",").map {
|
|
||||||
_.split(":") match {
|
|
||||||
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
|
||||||
}
|
|
||||||
}.toList)
|
|
||||||
updateImage(form.groupName, form.fileId, false)
|
|
||||||
redirect("/admin/users")
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/admin/users/:groupName/_editgroup")(adminOnly {
|
|
||||||
defining(params("groupName")){ groupName =>
|
|
||||||
admin.users.html.group(getAccountByUserName(groupName, true), getGroupMembers(groupName))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/admin/users/:groupName/_editgroup", editGroupForm)(adminOnly { form =>
|
|
||||||
defining(params("groupName"), form.members.split(",").map {
|
|
||||||
_.split(":") match {
|
|
||||||
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
|
||||||
}
|
|
||||||
}.toList){ case (groupName, members) =>
|
|
||||||
getAccountByUserName(groupName, true).map { account =>
|
|
||||||
updateGroup(groupName, form.url, form.isRemoved)
|
|
||||||
|
|
||||||
if(form.isRemoved){
|
|
||||||
// Remove from GROUP_MEMBER
|
|
||||||
updateGroupMembers(form.groupName, Nil)
|
|
||||||
// Remove repositories
|
|
||||||
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
|
||||||
deleteRepository(groupName, repositoryName)
|
|
||||||
FileUtils.deleteDirectory(getRepositoryDir(groupName, repositoryName))
|
|
||||||
FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName))
|
|
||||||
FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Update GROUP_MEMBER
|
|
||||||
updateGroupMembers(form.groupName, members)
|
|
||||||
// Update COLLABORATOR for group repositories
|
|
||||||
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
|
||||||
removeCollaborators(form.groupName, repositoryName)
|
|
||||||
members.foreach { case (userName, isManager) =>
|
|
||||||
addCollaborator(form.groupName, repositoryName, userName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateImage(form.groupName, form.fileId, form.clearImage)
|
|
||||||
redirect("/admin/users")
|
|
||||||
|
|
||||||
} getOrElse NotFound
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
private def members: Constraint = new Constraint(){
|
|
||||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
|
||||||
if(value.split(",").exists {
|
|
||||||
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
|
|
||||||
}) None else Some("Must select one manager at least.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected def disableByNotYourself(paramName: String): Constraint = new Constraint() {
|
|
||||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
|
||||||
params.get(paramName).flatMap { userName =>
|
|
||||||
if(userName == context.loginAccount.get.userName)
|
|
||||||
Some("You can't disable your account yourself")
|
|
||||||
else
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import service._
|
|
||||||
import util._
|
|
||||||
import util.Directory._
|
|
||||||
import util.ControlUtil._
|
|
||||||
import util.Implicits._
|
|
||||||
import jp.sf.amateras.scalatra.forms._
|
|
||||||
import org.eclipse.jgit.api.Git
|
|
||||||
import org.scalatra.i18n.Messages
|
|
||||||
import java.util.ResourceBundle
|
|
||||||
|
|
||||||
class WikiController extends WikiControllerBase
|
|
||||||
with WikiService with RepositoryService with AccountService with ActivityService with CollaboratorsAuthenticator with ReferrerAuthenticator
|
|
||||||
|
|
||||||
trait WikiControllerBase extends ControllerBase {
|
|
||||||
self: WikiService with RepositoryService with ActivityService with CollaboratorsAuthenticator with ReferrerAuthenticator =>
|
|
||||||
|
|
||||||
case class WikiPageEditForm(pageName: String, content: String, message: Option[String], currentPageName: String, id: String)
|
|
||||||
|
|
||||||
val newForm = mapping(
|
|
||||||
"pageName" -> trim(label("Page name" , text(required, maxlength(40), pagename, unique))),
|
|
||||||
"content" -> trim(label("Content" , text(required, conflictForNew))),
|
|
||||||
"message" -> trim(label("Message" , optional(text()))),
|
|
||||||
"currentPageName" -> trim(label("Current page name" , text())),
|
|
||||||
"id" -> trim(label("Latest commit id" , text()))
|
|
||||||
)(WikiPageEditForm.apply)
|
|
||||||
|
|
||||||
val editForm = mapping(
|
|
||||||
"pageName" -> trim(label("Page name" , text(required, maxlength(40), pagename))),
|
|
||||||
"content" -> trim(label("Content" , text(required, conflictForEdit))),
|
|
||||||
"message" -> trim(label("Message" , optional(text()))),
|
|
||||||
"currentPageName" -> trim(label("Current page name" , text(required))),
|
|
||||||
"id" -> trim(label("Latest commit id" , text(required)))
|
|
||||||
)(WikiPageEditForm.apply)
|
|
||||||
|
|
||||||
get("/:owner/:repository/wiki")(referrersOnly { repository =>
|
|
||||||
getWikiPage(repository.owner, repository.name, "Home").map { page =>
|
|
||||||
wiki.html.page("Home", page, getWikiPageList(repository.owner, repository.name),
|
|
||||||
repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
|
||||||
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/Home/_edit")
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/:page")(referrersOnly { repository =>
|
|
||||||
val pageName = StringUtil.urlDecode(params("page"))
|
|
||||||
|
|
||||||
getWikiPage(repository.owner, repository.name, pageName).map { page =>
|
|
||||||
wiki.html.page(pageName, page, getWikiPageList(repository.owner, repository.name),
|
|
||||||
repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
|
||||||
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_edit")
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/:page/_history")(referrersOnly { repository =>
|
|
||||||
val pageName = StringUtil.urlDecode(params("page"))
|
|
||||||
|
|
||||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
|
||||||
JGitUtil.getCommitLog(git, "master", path = pageName + ".md") match {
|
|
||||||
case Right((logs, hasNext)) => wiki.html.history(Some(pageName), logs, repository)
|
|
||||||
case Left(_) => NotFound
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/:page/_compare/:commitId")(referrersOnly { repository =>
|
|
||||||
val pageName = StringUtil.urlDecode(params("page"))
|
|
||||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
|
||||||
|
|
||||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
|
||||||
wiki.html.compare(Some(pageName), from, to, JGitUtil.getDiffs(git, from, to, true).filter(_.newPath == pageName + ".md"), repository,
|
|
||||||
hasWritePermission(repository.owner, repository.name, context.loginAccount), flash.get("info"))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_compare/:commitId")(referrersOnly { repository =>
|
|
||||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
|
||||||
|
|
||||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
|
||||||
wiki.html.compare(None, from, to, JGitUtil.getDiffs(git, from, to, true), repository,
|
|
||||||
hasWritePermission(repository.owner, repository.name, context.loginAccount), flash.get("info"))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/:page/_revert/:commitId")(collaboratorsOnly { repository =>
|
|
||||||
val pageName = StringUtil.urlDecode(params("page"))
|
|
||||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
|
||||||
|
|
||||||
if(revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, Some(pageName))){
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}")
|
|
||||||
} else {
|
|
||||||
flash += "info" -> "This patch was not able to be reversed."
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/${from}...${to}")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_revert/:commitId")(collaboratorsOnly { repository =>
|
|
||||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
|
||||||
|
|
||||||
if(revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, None)){
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki/")
|
|
||||||
} else {
|
|
||||||
flash += "info" -> "This patch was not able to be reversed."
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki/_compare/${from}...${to}")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/:page/_edit")(collaboratorsOnly { repository =>
|
|
||||||
val pageName = StringUtil.urlDecode(params("page"))
|
|
||||||
wiki.html.edit(pageName, getWikiPage(repository.owner, repository.name, pageName), repository)
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/:owner/:repository/wiki/_edit", editForm)(collaboratorsOnly { (form, repository) =>
|
|
||||||
defining(context.loginAccount.get){ loginAccount =>
|
|
||||||
saveWikiPage(repository.owner, repository.name, form.currentPageName, form.pageName,
|
|
||||||
form.content, loginAccount, form.message.getOrElse(""), Some(form.id)).map { commitId =>
|
|
||||||
updateLastActivityDate(repository.owner, repository.name)
|
|
||||||
recordEditWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName, commitId)
|
|
||||||
}
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_new")(collaboratorsOnly {
|
|
||||||
wiki.html.edit("", None, _)
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/:owner/:repository/wiki/_new", newForm)(collaboratorsOnly { (form, repository) =>
|
|
||||||
defining(context.loginAccount.get){ loginAccount =>
|
|
||||||
saveWikiPage(repository.owner, repository.name, form.currentPageName, form.pageName,
|
|
||||||
form.content, loginAccount, form.message.getOrElse(""), None)
|
|
||||||
|
|
||||||
updateLastActivityDate(repository.owner, repository.name)
|
|
||||||
recordCreateWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName)
|
|
||||||
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/:page/_delete")(collaboratorsOnly { repository =>
|
|
||||||
val pageName = StringUtil.urlDecode(params("page"))
|
|
||||||
|
|
||||||
defining(context.loginAccount.get){ loginAccount =>
|
|
||||||
deleteWikiPage(repository.owner, repository.name, pageName, loginAccount.fullName, loginAccount.mailAddress, s"Destroyed ${pageName}")
|
|
||||||
updateLastActivityDate(repository.owner, repository.name)
|
|
||||||
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_pages")(referrersOnly { repository =>
|
|
||||||
wiki.html.pages(getWikiPageList(repository.owner, repository.name), repository,
|
|
||||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
|
|
||||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
|
||||||
JGitUtil.getCommitLog(git, "master") match {
|
|
||||||
case Right((logs, hasNext)) => wiki.html.history(None, logs, repository)
|
|
||||||
case Left(_) => NotFound
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_blob/*")(referrersOnly { repository =>
|
|
||||||
val path = multiParams("splat").head
|
|
||||||
|
|
||||||
getFileContent(repository.owner, repository.name, path).map { bytes =>
|
|
||||||
contentType = FileUtil.getContentType(path, bytes)
|
|
||||||
bytes
|
|
||||||
} getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
private def unique: Constraint = new Constraint(){
|
|
||||||
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] =
|
|
||||||
getWikiPageList(params("owner"), params("repository")).find(_ == value).map(_ => "Page already exists.")
|
|
||||||
}
|
|
||||||
|
|
||||||
private def pagename: Constraint = new Constraint(){
|
|
||||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
|
||||||
if(value.exists("\\/:*?\"<>|".contains(_))){
|
|
||||||
Some(s"${name} contains invalid character.")
|
|
||||||
} else if(value.startsWith("_") || value.startsWith("-")){
|
|
||||||
Some(s"${name} starts with invalid character.")
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def conflictForNew: Constraint = new Constraint(){
|
|
||||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
|
||||||
targetWikiPage.map { _ =>
|
|
||||||
"Someone has created the wiki since you started. Please reload this page and re-apply your changes."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def conflictForEdit: Constraint = new Constraint(){
|
|
||||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
|
||||||
targetWikiPage.filter(_.id != params("id")).map{ _ =>
|
|
||||||
"Someone has edited the wiki since you started. Please reload this page and re-apply your changes."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def targetWikiPage = getWikiPage(params("owner"), params("repository"), params("pageName"))
|
|
||||||
|
|
||||||
}
|
|
||||||
60
src/main/scala/gitbucket/core/GitBucketCoreModule.scala
Normal file
60
src/main/scala/gitbucket/core/GitBucketCoreModule.scala
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
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"),
|
||||||
|
new Version("4.14.0",
|
||||||
|
new LiquibaseMigration("update/gitbucket-core_4.14.xml"),
|
||||||
|
new SqlMigration("update/gitbucket-core_4.14.sql")
|
||||||
|
),
|
||||||
|
new Version("4.14.1"),
|
||||||
|
new Version("4.15.0"),
|
||||||
|
new Version("4.16.0"),
|
||||||
|
new Version("4.17.0"),
|
||||||
|
new Version("4.18.0"),
|
||||||
|
new Version("4.19.0"),
|
||||||
|
new Version("4.19.1"),
|
||||||
|
new Version("4.19.2"),
|
||||||
|
new Version("4.19.3"),
|
||||||
|
new Version("4.20.0"),
|
||||||
|
new Version("4.21.0",
|
||||||
|
new LiquibaseMigration("update/gitbucket-core_4.21.xml")
|
||||||
|
),
|
||||||
|
new Version("4.21.1"),
|
||||||
|
new Version("4.21.2"),
|
||||||
|
new Version("4.22.0",
|
||||||
|
new LiquibaseMigration("update/gitbucket-core_4.22.xml")
|
||||||
|
)
|
||||||
|
)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user