mirror of
https://github.com/go-gitea/gitea.git
synced 2025-11-25 06:30:46 +09:00
Compare commits
1188 Commits
v1.24.0-de
...
v1.25.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eaa916a786 | ||
|
|
91901c2a60 | ||
|
|
20cf4b7849 | ||
|
|
5e7207d428 | ||
|
|
e3bfee80dd | ||
|
|
f93e2cf301 | ||
|
|
1b01d6de82 | ||
|
|
d67cd622d0 | ||
|
|
15f3e9d5a5 | ||
|
|
01fa8b2b7e | ||
|
|
1d9ae7ac23 | ||
|
|
01873a99c1 | ||
|
|
ce70863793 | ||
|
|
327f2207dc | ||
|
|
db876d8f17 | ||
|
|
2b71bf283b | ||
|
|
1ca4fef611 | ||
|
|
70ee6b9029 | ||
|
|
e5b404ec53 | ||
|
|
5842cd23a6 | ||
|
|
289bd9694b | ||
|
|
154d7521a5 | ||
|
|
24189dcced | ||
|
|
f84bf259ad | ||
|
|
470b21056a | ||
|
|
61011f1648 | ||
|
|
7ea9722c1d | ||
|
|
297f63af42 | ||
|
|
6a55749359 | ||
|
|
8116742e2d | ||
|
|
0a9cbf3228 | ||
|
|
74dfadb543 | ||
|
|
8ffc1fbfbf | ||
|
|
e95378329b | ||
|
|
fddf6cd63f | ||
|
|
d253e2055b | ||
|
|
e194d89c74 | ||
|
|
04b6f90889 | ||
|
|
65a37572f3 | ||
|
|
f85cd7aeb5 | ||
|
|
cf644d565d | ||
|
|
88a8571b93 | ||
|
|
45a88e09af | ||
|
|
6aa1a1e54d | ||
|
|
18cc3160b5 | ||
|
|
123c8d2b81 | ||
|
|
b2f2f8528a | ||
|
|
0925089b5e | ||
|
|
c84d17b1bb | ||
|
|
cb338a2ba1 | ||
|
|
16f4f0d473 | ||
|
|
387a4e72f7 | ||
|
|
ac6d38e4b7 | ||
|
|
6df51d4ef5 | ||
|
|
46f695ac65 | ||
|
|
4af1d58c86 | ||
|
|
f71df88a6b | ||
|
|
18b178e63f | ||
|
|
1644b8743c | ||
|
|
53a2aaee35 | ||
|
|
5ae9bb4df9 | ||
|
|
ae2e8c1f00 | ||
|
|
602af1499e | ||
|
|
f4512426a1 | ||
|
|
a3458c669a | ||
|
|
609d88f029 | ||
|
|
3c78598217 | ||
|
|
b7bb0fa538 | ||
|
|
6de2151607 | ||
|
|
a99761d466 | ||
|
|
8d1c04bda4 | ||
|
|
aa57531aac | ||
|
|
006fe2a907 | ||
|
|
d94faf6d7e | ||
|
|
6c8879b832 | ||
|
|
94a6da3bc8 | ||
|
|
f09bea7af1 | ||
|
|
0b706b0825 | ||
|
|
198f37e33c | ||
|
|
9a0ec53ee3 | ||
|
|
90cb5f9a1f | ||
|
|
2f3da6d6b3 | ||
|
|
4730bb596c | ||
|
|
6033c47f90 | ||
|
|
9332ff291b | ||
|
|
fe5afcb022 | ||
|
|
8c8eb4b6f9 | ||
|
|
69e595cdd8 | ||
|
|
e612b9744c | ||
|
|
4fe1066a17 | ||
|
|
688abac5ca | ||
|
|
325e059a50 | ||
|
|
866c636f52 | ||
|
|
7a474d1c95 | ||
|
|
84812e42df | ||
|
|
16e1207449 | ||
|
|
9bb08aa822 | ||
|
|
f04b9aaa1c | ||
|
|
89d7929711 | ||
|
|
593a52c2f0 | ||
|
|
4d43d85941 | ||
|
|
2653ac95e0 | ||
|
|
b76e69fee7 | ||
|
|
274f4aea7e | ||
|
|
e35e724e42 | ||
|
|
fb247f640e | ||
|
|
47df15cabc | ||
|
|
2802f96e97 | ||
|
|
b9efbe9fe6 | ||
|
|
e4cb48a7e0 | ||
|
|
4c369c0a92 | ||
|
|
bde1f82850 | ||
|
|
87362b4dc1 | ||
|
|
801da7fdab | ||
|
|
38ad58575a | ||
|
|
0c31499498 | ||
|
|
664522ec6b | ||
|
|
c290682521 | ||
|
|
b8f1c9f048 | ||
|
|
1640e9a490 | ||
|
|
89b4be057b | ||
|
|
09d1f359d5 | ||
|
|
998b6b8889 | ||
|
|
07347634aa | ||
|
|
5fe3296055 | ||
|
|
3e3c36bc30 | ||
|
|
e9655df082 | ||
|
|
879b896656 | ||
|
|
361e59fd53 | ||
|
|
ca6c0dee2e | ||
|
|
e96ef97989 | ||
|
|
086ff87e58 | ||
|
|
8a534132c8 | ||
|
|
ec75bdbe68 | ||
|
|
1717af72c4 | ||
|
|
d2e994db2c | ||
|
|
4e1b8db1fc | ||
|
|
ea96ff6b0c | ||
|
|
fd7ebaf427 | ||
|
|
dca375aad2 | ||
|
|
fe7883c54f | ||
|
|
0c6326e6ab | ||
|
|
1f50048ac9 | ||
|
|
aef4a3514c | ||
|
|
09c3189ab7 | ||
|
|
e19d0e4f56 | ||
|
|
dca1af7cad | ||
|
|
fb056bf4ed | ||
|
|
7636d581d9 | ||
|
|
0cbaa0b662 | ||
|
|
7aef7ea2d4 | ||
|
|
89cc9663f8 | ||
|
|
60246730b5 | ||
|
|
e837c998b7 | ||
|
|
da5ce5c8e7 | ||
|
|
c4fbccc4ec | ||
|
|
c7b99c8cc7 | ||
|
|
ed8d4dc37a | ||
|
|
3a969a58c2 | ||
|
|
a2d88cd597 | ||
|
|
e1fd9e3cd1 | ||
|
|
4b19e292b9 | ||
|
|
0739595dcd | ||
|
|
e844a41248 | ||
|
|
c0f24bd803 | ||
|
|
709535c506 | ||
|
|
c4b70c57bc | ||
|
|
9790f128cc | ||
|
|
463016b317 | ||
|
|
57b8441745 | ||
|
|
4ea8f33ae6 | ||
|
|
d782cad7f8 | ||
|
|
2872984919 | ||
|
|
6619b1ed2b | ||
|
|
08a7e65c84 | ||
|
|
621f2fcadb | ||
|
|
04017f259b | ||
|
|
9549c6641a | ||
|
|
5cca69517d | ||
|
|
1740d36581 | ||
|
|
d9a2dfd95e | ||
|
|
4ff8cdf826 | ||
|
|
ee4459488a | ||
|
|
a2e8bf5261 | ||
|
|
e02b47d2f3 | ||
|
|
90a48e96c7 | ||
|
|
345045888d | ||
|
|
87b28b3e83 | ||
|
|
9b5a3e9c9c | ||
|
|
d328e00775 | ||
|
|
a8349c4dc3 | ||
|
|
c4c1a4bd18 | ||
|
|
ef613ee760 | ||
|
|
793815adf7 | ||
|
|
ee9cd03f17 | ||
|
|
28a7cc4621 | ||
|
|
de4ab41728 | ||
|
|
93cf656974 | ||
|
|
becd15f743 | ||
|
|
920d62c0a7 | ||
|
|
de570b7dde | ||
|
|
1692652d65 | ||
|
|
25ed31e220 | ||
|
|
c760e3b2b5 | ||
|
|
67d623580b | ||
|
|
8125633aa3 | ||
|
|
be2a6b4414 | ||
|
|
1f676b36b1 | ||
|
|
5050976de7 | ||
|
|
5b1ab35ced | ||
|
|
5d94c9dd21 | ||
|
|
e7d6f74450 | ||
|
|
e01c921ee4 | ||
|
|
f4d7701189 | ||
|
|
84d31bc842 | ||
|
|
c3f5ea3b1f | ||
|
|
2e8a4a09d5 | ||
|
|
85b5877bb0 | ||
|
|
b7d8fade72 | ||
|
|
3778538a1c | ||
|
|
1b4d0800b4 | ||
|
|
5fd7fd3edb | ||
|
|
2a8ecfb002 | ||
|
|
82c9589faa | ||
|
|
54fe47fbca | ||
|
|
c72174a43d | ||
|
|
c10c4203ee | ||
|
|
43831ff0ca | ||
|
|
245add3085 | ||
|
|
65cd3f5309 | ||
|
|
f201dde945 | ||
|
|
86aafea3fb | ||
|
|
3531e9dbfd | ||
|
|
c4f5b2b531 | ||
|
|
8f91bfe9d8 | ||
|
|
13b9659952 | ||
|
|
3e8aa52446 | ||
|
|
2f138f7a03 | ||
|
|
39f145ae72 | ||
|
|
8ee96039aa | ||
|
|
de1114b4e8 | ||
|
|
891a827158 | ||
|
|
639ac0026c | ||
|
|
f0da1de7e3 | ||
|
|
37958e486a | ||
|
|
bc78a9a38a | ||
|
|
fc4cb07beb | ||
|
|
e1e4815a1c | ||
|
|
0d00ec7eed | ||
|
|
cd3fb95d4c | ||
|
|
7413e8583d | ||
|
|
a301079626 | ||
|
|
558005a5ea | ||
|
|
990ae2bfa8 | ||
|
|
4bad298cd7 | ||
|
|
692c90ea1d | ||
|
|
d08459820d | ||
|
|
b861d86f80 | ||
|
|
ece0ce6854 | ||
|
|
7cc47da78c | ||
|
|
6599efb3b1 | ||
|
|
6090d70915 | ||
|
|
1352080ef7 | ||
|
|
56eccb4995 | ||
|
|
b46623f6a5 | ||
|
|
7a15334656 | ||
|
|
a5a3d9b101 | ||
|
|
6ab6d4e17f | ||
|
|
32152a0ac0 | ||
|
|
f35dcfd489 | ||
|
|
36a19f2569 | ||
|
|
4b174e44a8 | ||
|
|
091b3e696d | ||
|
|
af0196c145 | ||
|
|
ea809a5220 | ||
|
|
b6d6402a1b | ||
|
|
4321747342 | ||
|
|
4669c64164 | ||
|
|
1e86b7dad0 | ||
|
|
211135b4bb | ||
|
|
bb0c84e8c3 | ||
|
|
55f350542c | ||
|
|
2cc3368610 | ||
|
|
f1b78f3cdd | ||
|
|
d3d357a4a4 | ||
|
|
4e10adc871 | ||
|
|
3763c2ae28 | ||
|
|
08682212ab | ||
|
|
f88800d545 | ||
|
|
ddfa2e4a3e | ||
|
|
6b42ea1e54 | ||
|
|
95a935aca0 | ||
|
|
ba943fb773 | ||
|
|
9dafcc5c9e | ||
|
|
e0745eb14d | ||
|
|
3533263ced | ||
|
|
429efc8b4f | ||
|
|
479757f61b | ||
|
|
58759aeca0 | ||
|
|
63ee6783b8 | ||
|
|
c05082669b | ||
|
|
6033c67a1a | ||
|
|
555735936f | ||
|
|
70685a9489 | ||
|
|
41678e1a57 | ||
|
|
71e151cc22 | ||
|
|
d6d643fe86 | ||
|
|
8cbec63cc7 | ||
|
|
6455c8202b | ||
|
|
97fc87af89 | ||
|
|
6fe5c4c4d9 | ||
|
|
dd1fd89185 | ||
|
|
1d4ad5aa2b | ||
|
|
35f0b5a3ec | ||
|
|
90f96c301e | ||
|
|
6596b92140 | ||
|
|
f3364ec57f | ||
|
|
8dbf13b1cb | ||
|
|
a94e472788 | ||
|
|
09bb19ad01 | ||
|
|
176962c03e | ||
|
|
f74a13610d | ||
|
|
662db4a69c | ||
|
|
95964dd2ca | ||
|
|
c077b71647 | ||
|
|
10cf2023bf | ||
|
|
26491caf8c | ||
|
|
8df59fa11c | ||
|
|
e17dfce61b | ||
|
|
69fc5619c4 | ||
|
|
1e50cec0b3 | ||
|
|
aa9d86745a | ||
|
|
9854df3e87 | ||
|
|
eb36a4554e | ||
|
|
376bf01769 | ||
|
|
0771a79bf0 | ||
|
|
750af1c981 | ||
|
|
c67a8397ff | ||
|
|
75aa23a665 | ||
|
|
1839110ea6 | ||
|
|
35a8e6f8e9 | ||
|
|
d23c911997 | ||
|
|
04783f548d | ||
|
|
dbd9c69909 | ||
|
|
7be1a5e585 | ||
|
|
0e629c545a | ||
|
|
63fb25382b | ||
|
|
22a84a72cd | ||
|
|
9a23fe131c | ||
|
|
eb87e9d3b6 | ||
|
|
6a97ab0af4 | ||
|
|
a789a8cc7a | ||
|
|
229235f99d | ||
|
|
840ee8bd54 | ||
|
|
327048c106 | ||
|
|
29b28002aa | ||
|
|
618e2d8106 | ||
|
|
485d8f1121 | ||
|
|
181db69e0c | ||
|
|
a46b16f10f | ||
|
|
1748045285 | ||
|
|
f114c388ff | ||
|
|
94c6d46faa | ||
|
|
7436c6297d | ||
|
|
ddd1e6ca83 | ||
|
|
0548c10293 | ||
|
|
7de114a332 | ||
|
|
4fc626daa1 | ||
|
|
81adb01713 | ||
|
|
0990eb44ce | ||
|
|
40dec17b5c | ||
|
|
90eb831418 | ||
|
|
1c28c470f8 | ||
|
|
e0f3b30895 | ||
|
|
719b151058 | ||
|
|
4f32d32812 | ||
|
|
cda90eca31 | ||
|
|
d462ce149d | ||
|
|
b8c9a0c323 | ||
|
|
7346ae7cd4 | ||
|
|
0ea958dc58 | ||
|
|
67083437cd | ||
|
|
b18c047d62 | ||
|
|
8efc4ca334 | ||
|
|
46a1d52235 | ||
|
|
a2ae7c69da | ||
|
|
7954f25290 | ||
|
|
416ff1fd31 | ||
|
|
0e6c1224e5 | ||
|
|
b38813878c | ||
|
|
08c634b7b7 | ||
|
|
dfea75371c | ||
|
|
1f35435b81 | ||
|
|
71e4740946 | ||
|
|
ecc6685c20 | ||
|
|
a14db5c5e3 | ||
|
|
ee334886f3 | ||
|
|
1376cf7481 | ||
|
|
f214bb40a3 | ||
|
|
224aa64cd9 | ||
|
|
1e644e39f9 | ||
|
|
037f72bdb3 | ||
|
|
4cbb482554 | ||
|
|
439ebe7031 | ||
|
|
3a37d63d61 | ||
|
|
24ce2058e8 | ||
|
|
6b8b580218 | ||
|
|
bbee652e29 | ||
|
|
637070e07b | ||
|
|
0d3e9956cd | ||
|
|
28debdbe00 | ||
|
|
dcc9206a59 | ||
|
|
bc28654b49 | ||
|
|
d21ce9fa07 | ||
|
|
8fed27bf6a | ||
|
|
65986f423f | ||
|
|
18bafcc378 | ||
|
|
8d135ef5cf | ||
|
|
d5893ee260 | ||
|
|
06ccb3a1d4 | ||
|
|
94db956e31 | ||
|
|
c9505a26b9 | ||
|
|
fbc3796f9e | ||
|
|
d5afdccde8 | ||
|
|
17cfae82a5 | ||
|
|
1610a63bfd | ||
|
|
e9f5105e95 | ||
|
|
2c341b6803 | ||
|
|
0082cb51fa | ||
|
|
92e7e98c56 | ||
|
|
7b39c82587 | ||
|
|
b408bf2f0b | ||
|
|
c6b2cbd75d | ||
|
|
9165ea8713 | ||
|
|
7a59f5a825 | ||
|
|
6d0b24064a | ||
|
|
f6041441ee | ||
|
|
1fe652cd26 | ||
|
|
a9a705f4db | ||
|
|
1e0758a9f1 | ||
|
|
cc942e2a86 | ||
|
|
7fa5a88831 | ||
|
|
f6f6aedd4f | ||
|
|
aa2b3b2b1f | ||
|
|
47d69b7749 | ||
|
|
b38f2d31fd | ||
|
|
74a0178c6a | ||
|
|
3f7dbbdaf1 | ||
|
|
5b22af4373 | ||
|
|
9e0e107d23 | ||
|
|
e5781cec75 | ||
|
|
108db0b04f | ||
|
|
497b83b75d | ||
|
|
79cc369892 | ||
|
|
fe57ee3074 | ||
|
|
4e471487fb | ||
|
|
375dab1111 | ||
|
|
2a1585b32e | ||
|
|
c5e78fc7ad | ||
|
|
f48c0135a6 | ||
|
|
e8d8984f7c | ||
|
|
d5bbaee64e | ||
|
|
82ea2387e4 | ||
|
|
74858dc5ae | ||
|
|
bb6377d080 | ||
|
|
7149c9c55d | ||
|
|
07d802a815 | ||
|
|
0cec4b84e2 | ||
|
|
c6e2093f42 | ||
|
|
4cb0c641ce | ||
|
|
b0936f4f41 | ||
|
|
498088c053 | ||
|
|
24a51059d7 | ||
|
|
9f10885b21 | ||
|
|
688da55f54 | ||
|
|
ab9691291d | ||
|
|
50d9565088 | ||
|
|
11ee7ff3bf | ||
|
|
9b295e984a | ||
|
|
9d4ebc1f2c | ||
|
|
8365365c9c | ||
|
|
4dd833ca9e | ||
|
|
b595f81b79 | ||
|
|
06ccda06c4 | ||
|
|
0d1d57c5bf | ||
|
|
14bb8f7845 | ||
|
|
73f640fc15 | ||
|
|
28dec9a27d | ||
|
|
0534eddd16 | ||
|
|
d06eb8d801 | ||
|
|
9cfcc079c7 | ||
|
|
ec10c6ba5a | ||
|
|
d89eed998f | ||
|
|
972381097c | ||
|
|
b6c0667474 | ||
|
|
e92c4f1808 | ||
|
|
6fbf0e6738 | ||
|
|
59df03b554 | ||
|
|
7b518bc6c7 | ||
|
|
c24f4b3d29 | ||
|
|
bf338bb9e2 | ||
|
|
319d03fbc0 | ||
|
|
dd500ce559 | ||
|
|
b6bf128f1e | ||
|
|
1e2f3514b9 | ||
|
|
a0595add72 | ||
|
|
5cb4cbf044 | ||
|
|
b5fd3e7210 | ||
|
|
4011e2245b | ||
|
|
355e9a9d54 | ||
|
|
0902d42fc7 | ||
|
|
34281bc198 | ||
|
|
780e92ea99 | ||
|
|
b07e03956a | ||
|
|
4a98ab0540 | ||
|
|
9b8609e017 | ||
|
|
0f63a5ef48 | ||
|
|
ad271444e9 | ||
|
|
8b16ab719c | ||
|
|
2ecd73d2e5 | ||
|
|
179068fddb | ||
|
|
44aadc37c9 | ||
|
|
f63822fe64 | ||
|
|
71a1187209 | ||
|
|
4c611bf280 | ||
|
|
2fbc8f9e87 | ||
|
|
82071ee730 | ||
|
|
bbfc21e74f | ||
|
|
020e774b91 | ||
|
|
dd886d729f | ||
|
|
2a660b4a1b | ||
|
|
6bd8fe5353 | ||
|
|
a2024953c5 | ||
|
|
6b2c506e05 | ||
|
|
12bf0b8e42 | ||
|
|
712fccadd6 | ||
|
|
833c2a432b | ||
|
|
62f73491f3 | ||
|
|
51aafb4278 | ||
|
|
41f3d062a2 | ||
|
|
180aa00abf | ||
|
|
3446f14ba0 | ||
|
|
cbb2e52911 | ||
|
|
3e49fba578 | ||
|
|
e67f74efc8 | ||
|
|
ba5c3f8087 | ||
|
|
ce6699db01 | ||
|
|
1f52304f90 | ||
|
|
7bd2ce7109 | ||
|
|
648df8a5e1 | ||
|
|
2b76993415 | ||
|
|
44d7d2973a | ||
|
|
0148d03f21 | ||
|
|
4ed07244b9 | ||
|
|
533b8b2d3d | ||
|
|
0376c09fc2 | ||
|
|
bf8f111f53 | ||
|
|
44ece1e6f3 | ||
|
|
27ff5e2e84 | ||
|
|
8cea1aeea5 | ||
|
|
a9343896f4 | ||
|
|
d1ad8e1e80 | ||
|
|
e73c1139ac | ||
|
|
e625250ffc | ||
|
|
58d2a87c6c | ||
|
|
b758241f28 | ||
|
|
8aee07a064 | ||
|
|
c2c04ffff7 | ||
|
|
04fab1818b | ||
|
|
4832cb9e27 | ||
|
|
1b1d53ab89 | ||
|
|
310fdeb453 | ||
|
|
714245844f | ||
|
|
9705adb27f | ||
|
|
9a071a596f | ||
|
|
e947f309b1 | ||
|
|
d1a3bd6814 | ||
|
|
af6be75adb | ||
|
|
6d3c6741ec | ||
|
|
166ec1b4c3 | ||
|
|
c9aa9068b3 | ||
|
|
eda6d65818 | ||
|
|
f0544dbfca | ||
|
|
8b7c0d8f8d | ||
|
|
aeb7005245 | ||
|
|
21b43fce08 | ||
|
|
ba0deab616 | ||
|
|
2683adfcb4 | ||
|
|
dd0caf7e16 | ||
|
|
42f45f1489 | ||
|
|
bde014e46d | ||
|
|
a4f22a3e80 | ||
|
|
e8c42ae601 | ||
|
|
dcfa42dd8f | ||
|
|
f1cd90d3bd | ||
|
|
3a651cfd02 | ||
|
|
d2d381fd4b | ||
|
|
2b99a58f54 | ||
|
|
18a673bad1 | ||
|
|
421d0e5849 | ||
|
|
3b5aaa836e | ||
|
|
921d3a394d | ||
|
|
33df23c1f0 | ||
|
|
c57304ac3f | ||
|
|
3a9fcac11b | ||
|
|
58a3952458 | ||
|
|
f6474cf2e9 | ||
|
|
8a6df00c53 | ||
|
|
bec9233c29 | ||
|
|
34349c085c | ||
|
|
a2651c14ce | ||
|
|
4dca869ed1 | ||
|
|
d0688cb2b3 | ||
|
|
aada0370e7 | ||
|
|
f4196a8843 | ||
|
|
93a2def96b | ||
|
|
5015992db5 | ||
|
|
7a587bc2d3 | ||
|
|
ae0af8ea5b | ||
|
|
d725b78824 | ||
|
|
4a3ab5a2cd | ||
|
|
4ddf94dee5 | ||
|
|
fa49cd719f | ||
|
|
02e49a0f47 | ||
|
|
fac6b87dd2 | ||
|
|
4a5af4edca | ||
|
|
f8edc29f5d | ||
|
|
1b2d8df13d | ||
|
|
c0898f7ed9 | ||
|
|
32b97b3ce8 | ||
|
|
fd7c364ca6 | ||
|
|
a100ac3306 | ||
|
|
90b509aafb | ||
|
|
07c6087878 | ||
|
|
8ca51abadd | ||
|
|
e45450b744 | ||
|
|
8c9d2bdee3 | ||
|
|
bcc38eb35f | ||
|
|
3fe082a5a3 | ||
|
|
e94f8d56f1 | ||
|
|
a62ed19da6 | ||
|
|
e1c2d05bde | ||
|
|
ee6929d96b | ||
|
|
7a09bc904a | ||
|
|
013b2686fe | ||
|
|
6cee3bfa96 | ||
|
|
e8b54d9e44 | ||
|
|
8fed70afdc | ||
|
|
f8d549436e | ||
|
|
ba921fd903 | ||
|
|
f94ee4fd3c | ||
|
|
45c45934aa | ||
|
|
c27d87a9ac | ||
|
|
285950a222 | ||
|
|
55a69ae4c6 | ||
|
|
6ed1b26c58 | ||
|
|
88352e0b25 | ||
|
|
e2ac789b49 | ||
|
|
0668cce4e8 | ||
|
|
92dfec704f | ||
|
|
dcf94c9e1b | ||
|
|
ee3c82f874 | ||
|
|
56e42be36d | ||
|
|
86c1a33369 | ||
|
|
d54418a7d3 | ||
|
|
4d2323183d | ||
|
|
a2e8a289b2 | ||
|
|
342432e52a | ||
|
|
741b53eb30 | ||
|
|
0fde8ecd55 | ||
|
|
0fd5392087 | ||
|
|
2f43544c01 | ||
|
|
2a9c5c9e3d | ||
|
|
b59705fa34 | ||
|
|
d7a6133825 | ||
|
|
0b847f4584 | ||
|
|
2a59dfbd47 | ||
|
|
5564c39105 | ||
|
|
5023238088 | ||
|
|
cddd19efc8 | ||
|
|
49899070cd | ||
|
|
bf9500b3f2 | ||
|
|
0d2607a303 | ||
|
|
58d0a3f4c2 | ||
|
|
b542769102 | ||
|
|
321cbcb45a | ||
|
|
894821d522 | ||
|
|
c1b9ecca34 | ||
|
|
053592d847 | ||
|
|
a7594969b6 | ||
|
|
96e7369515 | ||
|
|
3c95b0758f | ||
|
|
0c6957ef8d | ||
|
|
e0ad72e223 | ||
|
|
c7b85f7070 | ||
|
|
d70be9d0fe | ||
|
|
d28a7f9fea | ||
|
|
2089401653 | ||
|
|
20c7392e99 | ||
|
|
d6e94fa4e4 | ||
|
|
356b707dde | ||
|
|
41c946a66f | ||
|
|
32258e0f22 | ||
|
|
51d86adb6d | ||
|
|
536f4c6de8 | ||
|
|
3fe449c21a | ||
|
|
82bc8b8ce6 | ||
|
|
8745129c9c | ||
|
|
b1e326d09e | ||
|
|
25b6f38865 | ||
|
|
08510adefe | ||
|
|
30ee082e48 | ||
|
|
189873719b | ||
|
|
0fee4f1392 | ||
|
|
e1bba9c1a2 | ||
|
|
1c5c13a442 | ||
|
|
d1a755e5b7 | ||
|
|
b5d2e31d6d | ||
|
|
b956cee06f | ||
|
|
279473f467 | ||
|
|
0da7318cf3 | ||
|
|
a4df01b580 | ||
|
|
ef0970506f | ||
|
|
4a7ab0abf0 | ||
|
|
5407382b43 | ||
|
|
a9e8ac0fe0 | ||
|
|
e25f860735 | ||
|
|
39fc2e7285 | ||
|
|
dcbf6c2d13 | ||
|
|
1b4adc0255 | ||
|
|
6e73ecf667 | ||
|
|
8f051d598c | ||
|
|
9d7c02f9f7 | ||
|
|
fdaf1cca65 | ||
|
|
e1feb438a4 | ||
|
|
9d89dfe142 | ||
|
|
9ebae4a2aa | ||
|
|
18bd70054b | ||
|
|
7fd44a85ca | ||
|
|
1ea5216f4a | ||
|
|
01c8f092a0 | ||
|
|
52663113d4 | ||
|
|
c88e71c1d2 | ||
|
|
0056fdb942 | ||
|
|
f11ac6bf3c | ||
|
|
10513df1bf | ||
|
|
30b13942f0 | ||
|
|
3e2e7bf4e5 | ||
|
|
7e8168f555 | ||
|
|
92f997ce6b | ||
|
|
926f0a19be | ||
|
|
65e2411394 | ||
|
|
55cc649d3d | ||
|
|
a0e0a30d23 | ||
|
|
45c4139134 | ||
|
|
0dfa94edc8 | ||
|
|
cb6b33c9cd | ||
|
|
b094f9b75d | ||
|
|
9c673d066c | ||
|
|
1e7248047c | ||
|
|
de2d472d90 | ||
|
|
5d65b9060b | ||
|
|
d879ec6d92 | ||
|
|
8a0f7f0975 | ||
|
|
403775e74e | ||
|
|
cd10456664 | ||
|
|
3996518ed4 | ||
|
|
91610a987e | ||
|
|
651ef66966 | ||
|
|
f61f30153b | ||
|
|
608ccc32e5 | ||
|
|
a92d5f65ce | ||
|
|
e47bba046c | ||
|
|
7fa47de7e9 | ||
|
|
657239b480 | ||
|
|
c102492e5a | ||
|
|
34e5df6d30 | ||
|
|
ae63568ce3 | ||
|
|
31ddbe1444 | ||
|
|
7290bfaccb | ||
|
|
3e53b01143 | ||
|
|
3f1f808b9e | ||
|
|
6f13331754 | ||
|
|
4c4c56c7cd | ||
|
|
4ed71eb754 | ||
|
|
6422f05a4e | ||
|
|
1b2dffff8e | ||
|
|
b8c2afdc5f | ||
|
|
582ad338d7 | ||
|
|
ef18655215 | ||
|
|
e7cf62f2f7 | ||
|
|
b0ee340969 | ||
|
|
0453177b61 | ||
|
|
df7b61ce9a | ||
|
|
7cdde20c73 | ||
|
|
f0f10413ae | ||
|
|
95efc7b35b | ||
|
|
b9913d9568 | ||
|
|
65bb837fa2 | ||
|
|
ae3a18e01a | ||
|
|
6c8fb8d455 | ||
|
|
75e85c25c1 | ||
|
|
ca0ce003ab | ||
|
|
163bbca0eb | ||
|
|
ffb276578f | ||
|
|
14be462646 | ||
|
|
ad204f9c5a | ||
|
|
f0f1737d4d | ||
|
|
43c8d85f19 | ||
|
|
216243eee2 | ||
|
|
dbed39d632 | ||
|
|
02657e85a4 | ||
|
|
27bf63ad20 | ||
|
|
5cbdf83f70 | ||
|
|
2c8bdd2233 | ||
|
|
b684f51d20 | ||
|
|
698ae7aa5b | ||
|
|
f3ada61097 | ||
|
|
aca21549f2 | ||
|
|
ae4a3d7708 | ||
|
|
6e475dc7e0 | ||
|
|
4e56d5f39f | ||
|
|
aba96f65cd | ||
|
|
7a8eed13b9 | ||
|
|
303af554c9 | ||
|
|
8362a41559 | ||
|
|
f52e31f5ce | ||
|
|
b7aac5ef9a | ||
|
|
01156f9cc4 | ||
|
|
8db8a3b0d1 | ||
|
|
0e477b590d | ||
|
|
793c0e1fa6 | ||
|
|
6c5951dabd | ||
|
|
74c8e95e87 | ||
|
|
2aa59ba9e5 | ||
|
|
b35a9da14c | ||
|
|
38ccc8e3e4 | ||
|
|
7535af20da | ||
|
|
76b7f95a27 | ||
|
|
2cd2ae07a7 | ||
|
|
555d64d024 | ||
|
|
adf7018bfd | ||
|
|
56a0a9c750 | ||
|
|
8ae46d9684 | ||
|
|
f991807f7e | ||
|
|
9e75c54559 | ||
|
|
ead716d204 | ||
|
|
a61014ea47 | ||
|
|
4adb7cad8b | ||
|
|
f2fbb897f3 | ||
|
|
a25081f380 | ||
|
|
3ee5ee2029 | ||
|
|
cd225d7034 | ||
|
|
e6759f356d | ||
|
|
cc1fdc84ca | ||
|
|
3bbc482879 | ||
|
|
21af8150b7 | ||
|
|
40faa6dc78 | ||
|
|
c2e23d3301 | ||
|
|
84d2159ef6 | ||
|
|
ce65613690 | ||
|
|
748b731612 | ||
|
|
241f799edf | ||
|
|
9f560d47c9 | ||
|
|
15e020eec8 | ||
|
|
7df09e31fa | ||
|
|
f5a81f9636 | ||
|
|
f35850f48e | ||
|
|
69de5a65c2 | ||
|
|
5df9fd3e9c | ||
|
|
50a5d6bf5d | ||
|
|
3bbacac62c | ||
|
|
37c4f3760c | ||
|
|
58c124cc4f | ||
|
|
62389dd08b | ||
|
|
950abfe8ee | ||
|
|
fc1b383da9 | ||
|
|
2b8cfb557d | ||
|
|
01bf8da02e | ||
|
|
57997f1518 | ||
|
|
1ba7cbbfd6 | ||
|
|
8aede14b1d | ||
|
|
70327d6a92 | ||
|
|
f232d8f530 | ||
|
|
b426e383fe | ||
|
|
d88b012525 | ||
|
|
fba365b425 | ||
|
|
42d817e814 | ||
|
|
3e39583bb5 | ||
|
|
e741448a14 | ||
|
|
bcd1317d17 | ||
|
|
f58f5bb3d8 | ||
|
|
06f1065636 | ||
|
|
245ac321c3 | ||
|
|
e9b98aef44 | ||
|
|
217ffe5492 | ||
|
|
b3302748fa | ||
|
|
c422f179dd | ||
|
|
e3adb686bb | ||
|
|
30993e9508 | ||
|
|
085f273d19 | ||
|
|
72518a8dab | ||
|
|
704b65e012 | ||
|
|
523751dc82 | ||
|
|
06088ec672 | ||
|
|
a52720b5b4 | ||
|
|
063c23e1bc | ||
|
|
1ec8d80fa3 | ||
|
|
466cc725bc | ||
|
|
a1f1bccd7a | ||
|
|
dbc18f400a | ||
|
|
0070ffe560 | ||
|
|
40426addfa | ||
|
|
943cc4f989 | ||
|
|
a025fa70ab | ||
|
|
fa0c8ae50f | ||
|
|
7e596bd7a9 | ||
|
|
6999651b6d | ||
|
|
a6819570be | ||
|
|
09a3b07f10 | ||
|
|
d0f4e92563 | ||
|
|
a4676db7dd | ||
|
|
3c46cd6aae | ||
|
|
3ebfc77e83 | ||
|
|
ed84f3737a | ||
|
|
50873c1925 | ||
|
|
5dddcc1773 | ||
|
|
34692a20b1 | ||
|
|
869f8fdbe4 | ||
|
|
aec0b7ec34 | ||
|
|
fcfe1fb0fc | ||
|
|
9cd88ef8c7 | ||
|
|
47bf836310 | ||
|
|
040c830dec | ||
|
|
5b83203f37 | ||
|
|
1ab5938e82 | ||
|
|
0e8738b4b6 | ||
|
|
4f3cc26b4e | ||
|
|
05e9063013 | ||
|
|
5a7b42dac7 | ||
|
|
b57d9f41d4 | ||
|
|
f24d73ab5f | ||
|
|
f88dbf86b3 | ||
|
|
48183d2b05 | ||
|
|
75940a0191 | ||
|
|
256b94e9e9 | ||
|
|
ac2d97cb61 | ||
|
|
dc7ddaee2a | ||
|
|
4ffc54f59a | ||
|
|
a89c735303 | ||
|
|
8c4f0f02ef | ||
|
|
a9577e0808 | ||
|
|
8f433132e1 | ||
|
|
121e4c9624 | ||
|
|
8cd10f7f3b | ||
|
|
182e3896bf | ||
|
|
77d14fb6d3 | ||
|
|
dcd3014567 | ||
|
|
6ca91f555a | ||
|
|
c79adf00b8 | ||
|
|
26b51aa032 | ||
|
|
517a367abe | ||
|
|
fae69bc6d4 | ||
|
|
3e7ec826d3 | ||
|
|
9875f9b9b8 | ||
|
|
e663c4a7f0 | ||
|
|
2cc65e356e | ||
|
|
078ef6db89 | ||
|
|
2c1ff8701a | ||
|
|
6a516a0d14 | ||
|
|
7da8a01d39 | ||
|
|
06ff9b6256 | ||
|
|
642e8c1122 | ||
|
|
9e028d8d57 | ||
|
|
3e1b63f75b | ||
|
|
e94f37f95e | ||
|
|
594b8350b1 | ||
|
|
340d9ec42d | ||
|
|
a0b3d9add0 | ||
|
|
d64c849d16 | ||
|
|
c7f4ca2653 | ||
|
|
6fe4d1c038 | ||
|
|
abe743df79 | ||
|
|
3682231f17 | ||
|
|
7069369e03 | ||
|
|
2cb3946496 | ||
|
|
46d1e91aed | ||
|
|
2e42e96ce2 | ||
|
|
6cc1067884 | ||
|
|
39de2955fd | ||
|
|
6073e2f1bb | ||
|
|
13dbd260b7 | ||
|
|
076d122f34 | ||
|
|
1928918c35 | ||
|
|
b7614e2d2f | ||
|
|
dc2308a959 | ||
|
|
f250ee6360 | ||
|
|
fffc8550ab | ||
|
|
55e0756c68 | ||
|
|
1342d48433 | ||
|
|
3b839f8dc0 | ||
|
|
7582eb0419 | ||
|
|
cfc6e21f06 | ||
|
|
ab347fd0f7 | ||
|
|
2483a93fbc | ||
|
|
4b21a6c792 | ||
|
|
b15d01b0ce | ||
|
|
6659a381ea | ||
|
|
d0962ce3da | ||
|
|
f6dbf0e7b3 | ||
|
|
fcd096231a | ||
|
|
cbf933eb4e | ||
|
|
4d399e717d | ||
|
|
1299fdb084 | ||
|
|
5eff19a77a | ||
|
|
6410c34b7f | ||
|
|
3a749fc816 | ||
|
|
4672ddcdd7 | ||
|
|
a98a836e76 | ||
|
|
ecd463c2f1 | ||
|
|
58ac17c005 | ||
|
|
98d7e04767 | ||
|
|
a90af22003 | ||
|
|
348b7074c8 | ||
|
|
2ea929a952 | ||
|
|
81352542fd | ||
|
|
604365efd7 | ||
|
|
c0751ef116 | ||
|
|
be4e961240 | ||
|
|
9024b79933 | ||
|
|
a068462ac0 | ||
|
|
a7e750414c | ||
|
|
fd7d393c67 | ||
|
|
8c6d7076b7 | ||
|
|
5c150ce9b0 | ||
|
|
d7ec23febf | ||
|
|
d3083d2198 | ||
|
|
e5f3c16587 | ||
|
|
189e7409b7 | ||
|
|
65aae0912a | ||
|
|
8f8ad8e272 | ||
|
|
5b31077b68 | ||
|
|
39d51e7c82 | ||
|
|
2298ff2152 | ||
|
|
5f679c1c59 | ||
|
|
a5043af8ea | ||
|
|
68ca73b716 | ||
|
|
88366f280e | ||
|
|
ba5e3a5161 | ||
|
|
2a02734f93 | ||
|
|
fa9191b7b9 | ||
|
|
e177239529 | ||
|
|
9c00e065a1 | ||
|
|
124079871b | ||
|
|
386c1ed908 | ||
|
|
67aeb1f896 | ||
|
|
a8e7caedfa | ||
|
|
ec84687df9 | ||
|
|
32d45ee069 | ||
|
|
0d7d2ed39d | ||
|
|
34dfc25b83 | ||
|
|
98637fe76e | ||
|
|
485d184a5c | ||
|
|
4a18c72262 | ||
|
|
1a95d9d6a1 | ||
|
|
80e4f4c4eb | ||
|
|
ef736b7e27 | ||
|
|
40765b5d45 | ||
|
|
3c1c508421 | ||
|
|
cf60734a4d | ||
|
|
4237736029 | ||
|
|
3078826d01 | ||
|
|
df9d1fe8c5 | ||
|
|
3d544a3ad3 | ||
|
|
2b064b8637 | ||
|
|
188e0ee8e4 | ||
|
|
68972a9947 | ||
|
|
a739c784d9 | ||
|
|
83b7e12d4c | ||
|
|
e709cc76da | ||
|
|
9ac536a904 | ||
|
|
45973a100b | ||
|
|
9882917bce | ||
|
|
4f386e2c5e | ||
|
|
2852708fdf | ||
|
|
233b7959e0 | ||
|
|
c1167709ed | ||
|
|
85c756e279 | ||
|
|
d030cace1a | ||
|
|
2564c15cb0 | ||
|
|
57eb9d0b64 | ||
|
|
92a2900a2d | ||
|
|
6c89de494a | ||
|
|
20c7fba601 | ||
|
|
a0853e2278 | ||
|
|
58c092cfea | ||
|
|
e5c576e92b | ||
|
|
54bd220520 | ||
|
|
0387195abb | ||
|
|
c09656e0e0 | ||
|
|
4a254856b3 | ||
|
|
a54cc05d2a | ||
|
|
a87168869a | ||
|
|
fe32ffe181 | ||
|
|
d45456b1b5 | ||
|
|
8eecca3478 | ||
|
|
1e2c8eb494 | ||
|
|
dafadf0028 | ||
|
|
f4ccbd38dc | ||
|
|
344c89ea34 | ||
|
|
232867cff6 | ||
|
|
cd1b5488a3 | ||
|
|
1dbf0d7f08 | ||
|
|
a96776b3cb | ||
|
|
ff96873e3e | ||
|
|
0ed160ffea | ||
|
|
e95b946f6d | ||
|
|
64bebc9402 | ||
|
|
94048f3035 | ||
|
|
a92f5057ae | ||
|
|
3d3ece36d2 | ||
|
|
e69da2cd07 | ||
|
|
e435b1900a | ||
|
|
254314be5f | ||
|
|
14ed553fae | ||
|
|
079a1ffe8f | ||
|
|
ea198f9ea8 | ||
|
|
a7b2707be9 | ||
|
|
2d1a171dc7 | ||
|
|
3c00e89129 | ||
|
|
df98452c0d | ||
|
|
44b4fb21a4 | ||
|
|
7bb7ba1b5b | ||
|
|
550abdbc24 | ||
|
|
9bfa9f450d | ||
|
|
594edad213 | ||
|
|
5feb1a6bff | ||
|
|
f44712f22b | ||
|
|
1a7591d7f9 | ||
|
|
abaeae0b9c | ||
|
|
973363fec3 | ||
|
|
ca31d478ee | ||
|
|
65e45fdcfd | ||
|
|
b7260400f8 | ||
|
|
2a828e2798 | ||
|
|
b8b690feb9 | ||
|
|
6d5aa9218e | ||
|
|
781c6df40f | ||
|
|
02c64e48b7 | ||
|
|
7553ae1a57 | ||
|
|
bd5d1341d4 | ||
|
|
89f31f79fd | ||
|
|
462ce31530 | ||
|
|
b5f9a2d7c0 | ||
|
|
fb75151fb1 | ||
|
|
6279646ee4 | ||
|
|
daf2776db7 | ||
|
|
a09ea2f483 | ||
|
|
a163c53a60 | ||
|
|
afe314fa77 | ||
|
|
35c86af164 | ||
|
|
5d6d62493b | ||
|
|
143946834a | ||
|
|
c0e80dbe26 | ||
|
|
09a0041965 | ||
|
|
23687a0a71 | ||
|
|
751fe8b714 | ||
|
|
4774151e53 | ||
|
|
1c9b022c4d | ||
|
|
7580bd98c1 | ||
|
|
52b319bc00 | ||
|
|
c66de245c4 | ||
|
|
f47fb4fbaf | ||
|
|
626b27bea5 | ||
|
|
581e52b3e7 | ||
|
|
141d782c1a | ||
|
|
dc8f59baa5 | ||
|
|
5ec8df02f6 | ||
|
|
420cda18e6 | ||
|
|
857abed3a9 | ||
|
|
2beaedc417 | ||
|
|
f9f62b4c4c | ||
|
|
e4c4629465 | ||
|
|
b945742293 | ||
|
|
195fccd617 | ||
|
|
e6186bc447 | ||
|
|
2d7e6e9482 | ||
|
|
9f9bff0f04 |
@@ -22,20 +22,25 @@ groups:
|
|||||||
name: FEATURES
|
name: FEATURES
|
||||||
labels:
|
labels:
|
||||||
- type/feature
|
- type/feature
|
||||||
-
|
|
||||||
name: API
|
|
||||||
labels:
|
|
||||||
- modifies/api
|
|
||||||
-
|
-
|
||||||
name: ENHANCEMENTS
|
name: ENHANCEMENTS
|
||||||
labels:
|
labels:
|
||||||
- type/enhancement
|
- type/enhancement
|
||||||
- type/refactoring
|
-
|
||||||
- topic/ui
|
name: PERFORMANCE
|
||||||
|
labels:
|
||||||
|
- performance/memory
|
||||||
|
- performance/speed
|
||||||
|
- performance/bigrepo
|
||||||
|
- performance/cpu
|
||||||
-
|
-
|
||||||
name: BUGFIXES
|
name: BUGFIXES
|
||||||
labels:
|
labels:
|
||||||
- type/bug
|
- type/bug
|
||||||
|
-
|
||||||
|
name: API
|
||||||
|
labels:
|
||||||
|
- modifies/api
|
||||||
-
|
-
|
||||||
name: TESTING
|
name: TESTING
|
||||||
labels:
|
labels:
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "Gitea DevContainer",
|
"name": "Gitea DevContainer",
|
||||||
"image": "mcr.microsoft.com/devcontainers/go:1.23-bookworm",
|
"image": "mcr.microsoft.com/devcontainers/go:1.25-trixie",
|
||||||
|
"containerEnv": {
|
||||||
|
// override "local" from packaged version
|
||||||
|
"GOTOOLCHAIN": "auto"
|
||||||
|
},
|
||||||
"features": {
|
"features": {
|
||||||
// installs nodejs into container
|
// installs nodejs into container
|
||||||
"ghcr.io/devcontainers/features/node:1": {
|
"ghcr.io/devcontainers/features/node:1": {
|
||||||
"version": "20"
|
"version": "latest"
|
||||||
},
|
},
|
||||||
"ghcr.io/devcontainers/features/git-lfs:1.2.2": {},
|
"ghcr.io/devcontainers/features/git-lfs:1.2.5": {},
|
||||||
"ghcr.io/devcontainers-contrib/features/poetry:2": {},
|
"ghcr.io/jsburckhardt/devcontainer-features/uv:1": {},
|
||||||
"ghcr.io/devcontainers/features/python:1": {
|
"ghcr.io/devcontainers/features/python:1": {
|
||||||
"version": "3.12"
|
"version": "3.13"
|
||||||
},
|
},
|
||||||
"ghcr.io/warrenbuckley/codespace-features/sqlite:1": {}
|
"ghcr.io/warrenbuckley/codespace-features/sqlite:1": {}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -36,15 +36,6 @@ _testmain.go
|
|||||||
coverage.all
|
coverage.all
|
||||||
cpu.out
|
cpu.out
|
||||||
|
|
||||||
/modules/migration/bindata.go
|
|
||||||
/modules/migration/bindata.go.hash
|
|
||||||
/modules/options/bindata.go
|
|
||||||
/modules/options/bindata.go.hash
|
|
||||||
/modules/public/bindata.go
|
|
||||||
/modules/public/bindata.go.hash
|
|
||||||
/modules/templates/bindata.go
|
|
||||||
/modules/templates/bindata.go.hash
|
|
||||||
|
|
||||||
*.db
|
*.db
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
@@ -74,23 +65,12 @@ cpu.out
|
|||||||
/yarn.lock
|
/yarn.lock
|
||||||
/yarn-error.log
|
/yarn-error.log
|
||||||
/npm-debug.log*
|
/npm-debug.log*
|
||||||
|
/pnpm-debug.log*
|
||||||
/public/assets/js
|
/public/assets/js
|
||||||
/public/assets/css
|
/public/assets/css
|
||||||
/public/assets/fonts
|
/public/assets/fonts
|
||||||
/public/assets/img/avatar
|
/public/assets/img/avatar
|
||||||
/vendor
|
/vendor
|
||||||
/web_src/fomantic/node_modules
|
|
||||||
/web_src/fomantic/build/*
|
|
||||||
!/web_src/fomantic/build/semantic.js
|
|
||||||
!/web_src/fomantic/build/semantic.css
|
|
||||||
!/web_src/fomantic/build/themes
|
|
||||||
/web_src/fomantic/build/themes/*
|
|
||||||
!/web_src/fomantic/build/themes/default
|
|
||||||
/web_src/fomantic/build/themes/default/assets/*
|
|
||||||
!/web_src/fomantic/build/themes/default/assets/fonts
|
|
||||||
/web_src/fomantic/build/themes/default/assets/fonts/*
|
|
||||||
!/web_src/fomantic/build/themes/default/assets/fonts/icons.woff2
|
|
||||||
!/web_src/fomantic/build/themes/default/assets/fonts/outline-icons.woff2
|
|
||||||
/VERSION
|
/VERSION
|
||||||
/.air
|
/.air
|
||||||
/.go-licenses
|
/.go-licenses
|
||||||
|
|||||||
@@ -12,15 +12,23 @@ insert_final_newline = true
|
|||||||
[*.{go,tmpl,html}]
|
[*.{go,tmpl,html}]
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
|
|
||||||
|
[go.*]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
[templates/custom/*.tmpl]
|
[templates/custom/*.tmpl]
|
||||||
insert_final_newline = false
|
insert_final_newline = false
|
||||||
|
|
||||||
[templates/swagger/v1_json.tmpl]
|
[templates/swagger/v1_json.tmpl]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
|
insert_final_newline = false
|
||||||
|
|
||||||
[templates/user/auth/oidc_wellknown.tmpl]
|
[templates/user/auth/oidc_wellknown.tmpl]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
|
|
||||||
|
[templates/shared/actions/runner_badge_*.tmpl]
|
||||||
|
# editconfig lint requires these XML-like files to have charset defined, but the files don't have.
|
||||||
|
charset = unset
|
||||||
|
|
||||||
[Makefile]
|
[Makefile]
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
|
|
||||||
|
|||||||
967
.eslintrc.yaml
967
.eslintrc.yaml
@@ -1,967 +0,0 @@
|
|||||||
root: true
|
|
||||||
reportUnusedDisableDirectives: true
|
|
||||||
|
|
||||||
ignorePatterns:
|
|
||||||
- /web_src/js/vendor
|
|
||||||
- /web_src/fomantic
|
|
||||||
- /public/assets/js
|
|
||||||
|
|
||||||
parser: "@typescript-eslint/parser"
|
|
||||||
|
|
||||||
parserOptions:
|
|
||||||
sourceType: module
|
|
||||||
ecmaVersion: latest
|
|
||||||
project: true
|
|
||||||
extraFileExtensions: [".vue"]
|
|
||||||
parser: "@typescript-eslint/parser" # for vue plugin - https://eslint.vuejs.org/user-guide/#how-to-use-a-custom-parser
|
|
||||||
|
|
||||||
settings:
|
|
||||||
import-x/extensions: [".js", ".ts"]
|
|
||||||
import-x/parsers:
|
|
||||||
"@typescript-eslint/parser": [".js", ".ts"]
|
|
||||||
import-x/resolver:
|
|
||||||
typescript: true
|
|
||||||
|
|
||||||
plugins:
|
|
||||||
- "@eslint-community/eslint-plugin-eslint-comments"
|
|
||||||
- "@stylistic/eslint-plugin-js"
|
|
||||||
- "@typescript-eslint/eslint-plugin"
|
|
||||||
- eslint-plugin-array-func
|
|
||||||
- eslint-plugin-github
|
|
||||||
- eslint-plugin-import-x
|
|
||||||
- eslint-plugin-no-jquery
|
|
||||||
- eslint-plugin-no-use-extend-native
|
|
||||||
- eslint-plugin-regexp
|
|
||||||
- eslint-plugin-sonarjs
|
|
||||||
- eslint-plugin-unicorn
|
|
||||||
- eslint-plugin-vitest
|
|
||||||
- eslint-plugin-vitest-globals
|
|
||||||
- eslint-plugin-wc
|
|
||||||
|
|
||||||
env:
|
|
||||||
es2024: true
|
|
||||||
node: true
|
|
||||||
|
|
||||||
overrides:
|
|
||||||
- files: ["web_src/**/*"]
|
|
||||||
globals:
|
|
||||||
__webpack_public_path__: true
|
|
||||||
process: false # https://github.com/webpack/webpack/issues/15833
|
|
||||||
- files: ["web_src/**/*", "docs/**/*"]
|
|
||||||
env:
|
|
||||||
browser: true
|
|
||||||
node: false
|
|
||||||
- files: ["web_src/**/*worker.*"]
|
|
||||||
env:
|
|
||||||
worker: true
|
|
||||||
rules:
|
|
||||||
no-restricted-globals: [2, addEventListener, blur, close, closed, confirm, defaultStatus, defaultstatus, error, event, external, find, focus, frameElement, frames, history, innerHeight, innerWidth, isFinite, isNaN, length, locationbar, menubar, moveBy, moveTo, name, onblur, onerror, onfocus, onload, onresize, onunload, open, opener, opera, outerHeight, outerWidth, pageXOffset, pageYOffset, parent, print, removeEventListener, resizeBy, resizeTo, screen, screenLeft, screenTop, screenX, screenY, scroll, scrollbars, scrollBy, scrollTo, scrollX, scrollY, status, statusbar, stop, toolbar, top]
|
|
||||||
- files: ["*.config.*"]
|
|
||||||
rules:
|
|
||||||
import-x/no-unused-modules: [0]
|
|
||||||
- files: ["**/*.d.ts"]
|
|
||||||
rules:
|
|
||||||
import-x/no-unused-modules: [0]
|
|
||||||
"@typescript-eslint/consistent-type-definitions": [0]
|
|
||||||
"@typescript-eslint/consistent-type-imports": [0]
|
|
||||||
- files: ["web_src/js/types.ts"]
|
|
||||||
rules:
|
|
||||||
import-x/no-unused-modules: [0]
|
|
||||||
- files: ["**/*.test.*", "web_src/js/test/setup.ts"]
|
|
||||||
env:
|
|
||||||
vitest-globals/env: true
|
|
||||||
rules:
|
|
||||||
vitest/consistent-test-filename: [0]
|
|
||||||
vitest/consistent-test-it: [0]
|
|
||||||
vitest/expect-expect: [0]
|
|
||||||
vitest/max-expects: [0]
|
|
||||||
vitest/max-nested-describe: [0]
|
|
||||||
vitest/no-alias-methods: [0]
|
|
||||||
vitest/no-commented-out-tests: [0]
|
|
||||||
vitest/no-conditional-expect: [0]
|
|
||||||
vitest/no-conditional-in-test: [0]
|
|
||||||
vitest/no-conditional-tests: [0]
|
|
||||||
vitest/no-disabled-tests: [0]
|
|
||||||
vitest/no-done-callback: [0]
|
|
||||||
vitest/no-duplicate-hooks: [0]
|
|
||||||
vitest/no-focused-tests: [0]
|
|
||||||
vitest/no-hooks: [0]
|
|
||||||
vitest/no-identical-title: [2]
|
|
||||||
vitest/no-interpolation-in-snapshots: [0]
|
|
||||||
vitest/no-large-snapshots: [0]
|
|
||||||
vitest/no-mocks-import: [0]
|
|
||||||
vitest/no-restricted-matchers: [0]
|
|
||||||
vitest/no-restricted-vi-methods: [0]
|
|
||||||
vitest/no-standalone-expect: [0]
|
|
||||||
vitest/no-test-prefixes: [0]
|
|
||||||
vitest/no-test-return-statement: [0]
|
|
||||||
vitest/prefer-called-with: [0]
|
|
||||||
vitest/prefer-comparison-matcher: [0]
|
|
||||||
vitest/prefer-each: [0]
|
|
||||||
vitest/prefer-equality-matcher: [0]
|
|
||||||
vitest/prefer-expect-resolves: [0]
|
|
||||||
vitest/prefer-hooks-in-order: [0]
|
|
||||||
vitest/prefer-hooks-on-top: [2]
|
|
||||||
vitest/prefer-lowercase-title: [0]
|
|
||||||
vitest/prefer-mock-promise-shorthand: [0]
|
|
||||||
vitest/prefer-snapshot-hint: [0]
|
|
||||||
vitest/prefer-spy-on: [0]
|
|
||||||
vitest/prefer-strict-equal: [0]
|
|
||||||
vitest/prefer-to-be: [0]
|
|
||||||
vitest/prefer-to-be-falsy: [0]
|
|
||||||
vitest/prefer-to-be-object: [0]
|
|
||||||
vitest/prefer-to-be-truthy: [0]
|
|
||||||
vitest/prefer-to-contain: [0]
|
|
||||||
vitest/prefer-to-have-length: [0]
|
|
||||||
vitest/prefer-todo: [0]
|
|
||||||
vitest/require-hook: [0]
|
|
||||||
vitest/require-to-throw-message: [0]
|
|
||||||
vitest/require-top-level-describe: [0]
|
|
||||||
vitest/valid-describe-callback: [2]
|
|
||||||
vitest/valid-expect: [2]
|
|
||||||
vitest/valid-title: [2]
|
|
||||||
- files: ["web_src/js/modules/fetch.ts", "web_src/js/standalone/**/*"]
|
|
||||||
rules:
|
|
||||||
no-restricted-syntax: [2, WithStatement, ForInStatement, LabeledStatement, SequenceExpression]
|
|
||||||
- files: ["**/*.vue"]
|
|
||||||
plugins:
|
|
||||||
- eslint-plugin-vue
|
|
||||||
- eslint-plugin-vue-scoped-css
|
|
||||||
extends:
|
|
||||||
- plugin:vue/vue3-recommended
|
|
||||||
- plugin:vue-scoped-css/vue3-recommended
|
|
||||||
rules:
|
|
||||||
vue/attributes-order: [0]
|
|
||||||
vue/html-closing-bracket-spacing: [2, {startTag: never, endTag: never, selfClosingTag: never}]
|
|
||||||
vue/max-attributes-per-line: [0]
|
|
||||||
vue/singleline-html-element-content-newline: [0]
|
|
||||||
- files: ["tests/e2e/**"]
|
|
||||||
plugins:
|
|
||||||
- eslint-plugin-playwright
|
|
||||||
extends: plugin:playwright/recommended
|
|
||||||
|
|
||||||
rules:
|
|
||||||
"@eslint-community/eslint-comments/disable-enable-pair": [2]
|
|
||||||
"@eslint-community/eslint-comments/no-aggregating-enable": [2]
|
|
||||||
"@eslint-community/eslint-comments/no-duplicate-disable": [2]
|
|
||||||
"@eslint-community/eslint-comments/no-restricted-disable": [0]
|
|
||||||
"@eslint-community/eslint-comments/no-unlimited-disable": [2]
|
|
||||||
"@eslint-community/eslint-comments/no-unused-disable": [2]
|
|
||||||
"@eslint-community/eslint-comments/no-unused-enable": [2]
|
|
||||||
"@eslint-community/eslint-comments/no-use": [0]
|
|
||||||
"@eslint-community/eslint-comments/require-description": [0]
|
|
||||||
"@stylistic/js/array-bracket-newline": [0]
|
|
||||||
"@stylistic/js/array-bracket-spacing": [2, never]
|
|
||||||
"@stylistic/js/array-element-newline": [0]
|
|
||||||
"@stylistic/js/arrow-parens": [2, always]
|
|
||||||
"@stylistic/js/arrow-spacing": [2, {before: true, after: true}]
|
|
||||||
"@stylistic/js/block-spacing": [0]
|
|
||||||
"@stylistic/js/brace-style": [2, 1tbs, {allowSingleLine: true}]
|
|
||||||
"@stylistic/js/comma-dangle": [2, always-multiline]
|
|
||||||
"@stylistic/js/comma-spacing": [2, {before: false, after: true}]
|
|
||||||
"@stylistic/js/comma-style": [2, last]
|
|
||||||
"@stylistic/js/computed-property-spacing": [2, never]
|
|
||||||
"@stylistic/js/dot-location": [2, property]
|
|
||||||
"@stylistic/js/eol-last": [2]
|
|
||||||
"@stylistic/js/function-call-argument-newline": [0]
|
|
||||||
"@stylistic/js/function-call-spacing": [2, never]
|
|
||||||
"@stylistic/js/function-paren-newline": [0]
|
|
||||||
"@stylistic/js/generator-star-spacing": [0]
|
|
||||||
"@stylistic/js/implicit-arrow-linebreak": [0]
|
|
||||||
"@stylistic/js/indent": [2, 2, {ignoreComments: true, SwitchCase: 1}]
|
|
||||||
"@stylistic/js/key-spacing": [2]
|
|
||||||
"@stylistic/js/keyword-spacing": [2]
|
|
||||||
"@stylistic/js/line-comment-position": [0]
|
|
||||||
"@stylistic/js/linebreak-style": [2, unix]
|
|
||||||
"@stylistic/js/lines-around-comment": [0]
|
|
||||||
"@stylistic/js/lines-between-class-members": [0]
|
|
||||||
"@stylistic/js/max-len": [0]
|
|
||||||
"@stylistic/js/max-statements-per-line": [0]
|
|
||||||
"@stylistic/js/multiline-comment-style": [0]
|
|
||||||
"@stylistic/js/multiline-ternary": [0]
|
|
||||||
"@stylistic/js/new-parens": [2]
|
|
||||||
"@stylistic/js/newline-per-chained-call": [0]
|
|
||||||
"@stylistic/js/no-confusing-arrow": [0]
|
|
||||||
"@stylistic/js/no-extra-parens": [0]
|
|
||||||
"@stylistic/js/no-extra-semi": [2]
|
|
||||||
"@stylistic/js/no-floating-decimal": [0]
|
|
||||||
"@stylistic/js/no-mixed-operators": [0]
|
|
||||||
"@stylistic/js/no-mixed-spaces-and-tabs": [2]
|
|
||||||
"@stylistic/js/no-multi-spaces": [2, {ignoreEOLComments: true, exceptions: {Property: true}}]
|
|
||||||
"@stylistic/js/no-multiple-empty-lines": [2, {max: 1, maxEOF: 0, maxBOF: 0}]
|
|
||||||
"@stylistic/js/no-tabs": [2]
|
|
||||||
"@stylistic/js/no-trailing-spaces": [2]
|
|
||||||
"@stylistic/js/no-whitespace-before-property": [2]
|
|
||||||
"@stylistic/js/nonblock-statement-body-position": [2]
|
|
||||||
"@stylistic/js/object-curly-newline": [0]
|
|
||||||
"@stylistic/js/object-curly-spacing": [2, never]
|
|
||||||
"@stylistic/js/object-property-newline": [0]
|
|
||||||
"@stylistic/js/one-var-declaration-per-line": [0]
|
|
||||||
"@stylistic/js/operator-linebreak": [2, after]
|
|
||||||
"@stylistic/js/padded-blocks": [2, never]
|
|
||||||
"@stylistic/js/padding-line-between-statements": [0]
|
|
||||||
"@stylistic/js/quote-props": [0]
|
|
||||||
"@stylistic/js/quotes": [2, single, {avoidEscape: true, allowTemplateLiterals: true}]
|
|
||||||
"@stylistic/js/rest-spread-spacing": [2, never]
|
|
||||||
"@stylistic/js/semi": [2, always, {omitLastInOneLineBlock: true}]
|
|
||||||
"@stylistic/js/semi-spacing": [2, {before: false, after: true}]
|
|
||||||
"@stylistic/js/semi-style": [2, last]
|
|
||||||
"@stylistic/js/space-before-blocks": [2, always]
|
|
||||||
"@stylistic/js/space-before-function-paren": [2, {anonymous: ignore, named: never, asyncArrow: always}]
|
|
||||||
"@stylistic/js/space-in-parens": [2, never]
|
|
||||||
"@stylistic/js/space-infix-ops": [2]
|
|
||||||
"@stylistic/js/space-unary-ops": [2]
|
|
||||||
"@stylistic/js/spaced-comment": [2, always]
|
|
||||||
"@stylistic/js/switch-colon-spacing": [2]
|
|
||||||
"@stylistic/js/template-curly-spacing": [2, never]
|
|
||||||
"@stylistic/js/template-tag-spacing": [2, never]
|
|
||||||
"@stylistic/js/wrap-iife": [2, inside]
|
|
||||||
"@stylistic/js/wrap-regex": [0]
|
|
||||||
"@stylistic/js/yield-star-spacing": [2, after]
|
|
||||||
"@typescript-eslint/adjacent-overload-signatures": [0]
|
|
||||||
"@typescript-eslint/array-type": [0]
|
|
||||||
"@typescript-eslint/await-thenable": [2]
|
|
||||||
"@typescript-eslint/ban-ts-comment": [2, {'ts-expect-error': false, 'ts-ignore': true, 'ts-nocheck': false, 'ts-check': false}]
|
|
||||||
"@typescript-eslint/ban-tslint-comment": [0]
|
|
||||||
"@typescript-eslint/class-literal-property-style": [0]
|
|
||||||
"@typescript-eslint/class-methods-use-this": [0]
|
|
||||||
"@typescript-eslint/consistent-generic-constructors": [0]
|
|
||||||
"@typescript-eslint/consistent-indexed-object-style": [0]
|
|
||||||
"@typescript-eslint/consistent-return": [0]
|
|
||||||
"@typescript-eslint/consistent-type-assertions": [2, {assertionStyle: as, objectLiteralTypeAssertions: allow}]
|
|
||||||
"@typescript-eslint/consistent-type-definitions": [2, type]
|
|
||||||
"@typescript-eslint/consistent-type-exports": [2, {fixMixedExportsWithInlineTypeSpecifier: false}]
|
|
||||||
"@typescript-eslint/consistent-type-imports": [2, {prefer: type-imports, fixStyle: separate-type-imports, disallowTypeAnnotations: true}]
|
|
||||||
"@typescript-eslint/default-param-last": [0]
|
|
||||||
"@typescript-eslint/dot-notation": [0]
|
|
||||||
"@typescript-eslint/explicit-function-return-type": [0]
|
|
||||||
"@typescript-eslint/explicit-member-accessibility": [0]
|
|
||||||
"@typescript-eslint/explicit-module-boundary-types": [0]
|
|
||||||
"@typescript-eslint/init-declarations": [0]
|
|
||||||
"@typescript-eslint/max-params": [0]
|
|
||||||
"@typescript-eslint/member-ordering": [0]
|
|
||||||
"@typescript-eslint/method-signature-style": [0]
|
|
||||||
"@typescript-eslint/naming-convention": [0]
|
|
||||||
"@typescript-eslint/no-array-constructor": [2]
|
|
||||||
"@typescript-eslint/no-array-delete": [2]
|
|
||||||
"@typescript-eslint/no-base-to-string": [0]
|
|
||||||
"@typescript-eslint/no-confusing-non-null-assertion": [2]
|
|
||||||
"@typescript-eslint/no-confusing-void-expression": [0]
|
|
||||||
"@typescript-eslint/no-deprecated": [2]
|
|
||||||
"@typescript-eslint/no-dupe-class-members": [0]
|
|
||||||
"@typescript-eslint/no-duplicate-enum-values": [2]
|
|
||||||
"@typescript-eslint/no-duplicate-type-constituents": [2, {ignoreUnions: true}]
|
|
||||||
"@typescript-eslint/no-dynamic-delete": [0]
|
|
||||||
"@typescript-eslint/no-empty-function": [0]
|
|
||||||
"@typescript-eslint/no-empty-interface": [0]
|
|
||||||
"@typescript-eslint/no-empty-object-type": [2]
|
|
||||||
"@typescript-eslint/no-explicit-any": [0]
|
|
||||||
"@typescript-eslint/no-extra-non-null-assertion": [2]
|
|
||||||
"@typescript-eslint/no-extraneous-class": [0]
|
|
||||||
"@typescript-eslint/no-floating-promises": [0]
|
|
||||||
"@typescript-eslint/no-for-in-array": [2]
|
|
||||||
"@typescript-eslint/no-implied-eval": [2]
|
|
||||||
"@typescript-eslint/no-import-type-side-effects": [0] # dupe with consistent-type-imports
|
|
||||||
"@typescript-eslint/no-inferrable-types": [0]
|
|
||||||
"@typescript-eslint/no-invalid-this": [0]
|
|
||||||
"@typescript-eslint/no-invalid-void-type": [0]
|
|
||||||
"@typescript-eslint/no-loop-func": [0]
|
|
||||||
"@typescript-eslint/no-loss-of-precision": [0]
|
|
||||||
"@typescript-eslint/no-magic-numbers": [0]
|
|
||||||
"@typescript-eslint/no-meaningless-void-operator": [0]
|
|
||||||
"@typescript-eslint/no-misused-new": [2]
|
|
||||||
"@typescript-eslint/no-misused-promises": [2, {checksVoidReturn: {attributes: false, arguments: false}}]
|
|
||||||
"@typescript-eslint/no-mixed-enums": [0]
|
|
||||||
"@typescript-eslint/no-namespace": [2]
|
|
||||||
"@typescript-eslint/no-non-null-asserted-nullish-coalescing": [0]
|
|
||||||
"@typescript-eslint/no-non-null-asserted-optional-chain": [2]
|
|
||||||
"@typescript-eslint/no-non-null-assertion": [0]
|
|
||||||
"@typescript-eslint/no-redeclare": [0]
|
|
||||||
"@typescript-eslint/no-redundant-type-constituents": [2]
|
|
||||||
"@typescript-eslint/no-require-imports": [2]
|
|
||||||
"@typescript-eslint/no-restricted-imports": [0]
|
|
||||||
"@typescript-eslint/no-restricted-types": [0]
|
|
||||||
"@typescript-eslint/no-shadow": [0]
|
|
||||||
"@typescript-eslint/no-this-alias": [0] # handled by unicorn/no-this-assignment
|
|
||||||
"@typescript-eslint/no-unnecessary-boolean-literal-compare": [0]
|
|
||||||
"@typescript-eslint/no-unnecessary-condition": [0]
|
|
||||||
"@typescript-eslint/no-unnecessary-qualifier": [0]
|
|
||||||
"@typescript-eslint/no-unnecessary-template-expression": [0]
|
|
||||||
"@typescript-eslint/no-unnecessary-type-arguments": [0]
|
|
||||||
"@typescript-eslint/no-unnecessary-type-assertion": [2]
|
|
||||||
"@typescript-eslint/no-unnecessary-type-constraint": [2]
|
|
||||||
"@typescript-eslint/no-unsafe-argument": [0]
|
|
||||||
"@typescript-eslint/no-unsafe-assignment": [0]
|
|
||||||
"@typescript-eslint/no-unsafe-call": [0]
|
|
||||||
"@typescript-eslint/no-unsafe-declaration-merging": [2]
|
|
||||||
"@typescript-eslint/no-unsafe-enum-comparison": [2]
|
|
||||||
"@typescript-eslint/no-unsafe-function-type": [2]
|
|
||||||
"@typescript-eslint/no-unsafe-member-access": [0]
|
|
||||||
"@typescript-eslint/no-unsafe-return": [0]
|
|
||||||
"@typescript-eslint/no-unsafe-unary-minus": [2]
|
|
||||||
"@typescript-eslint/no-unused-expressions": [0]
|
|
||||||
"@typescript-eslint/no-unused-vars": [2, {vars: all, args: all, caughtErrors: all, ignoreRestSiblings: false, argsIgnorePattern: ^_, varsIgnorePattern: ^_, caughtErrorsIgnorePattern: ^_, destructuredArrayIgnorePattern: ^_}]
|
|
||||||
"@typescript-eslint/no-use-before-define": [0]
|
|
||||||
"@typescript-eslint/no-useless-constructor": [0]
|
|
||||||
"@typescript-eslint/no-useless-empty-export": [0]
|
|
||||||
"@typescript-eslint/no-wrapper-object-types": [2]
|
|
||||||
"@typescript-eslint/non-nullable-type-assertion-style": [0]
|
|
||||||
"@typescript-eslint/only-throw-error": [2]
|
|
||||||
"@typescript-eslint/parameter-properties": [0]
|
|
||||||
"@typescript-eslint/prefer-as-const": [2]
|
|
||||||
"@typescript-eslint/prefer-destructuring": [0]
|
|
||||||
"@typescript-eslint/prefer-enum-initializers": [0]
|
|
||||||
"@typescript-eslint/prefer-find": [2]
|
|
||||||
"@typescript-eslint/prefer-for-of": [2]
|
|
||||||
"@typescript-eslint/prefer-function-type": [2]
|
|
||||||
"@typescript-eslint/prefer-includes": [2]
|
|
||||||
"@typescript-eslint/prefer-literal-enum-member": [0]
|
|
||||||
"@typescript-eslint/prefer-namespace-keyword": [0]
|
|
||||||
"@typescript-eslint/prefer-nullish-coalescing": [0]
|
|
||||||
"@typescript-eslint/prefer-optional-chain": [2, {requireNullish: true}]
|
|
||||||
"@typescript-eslint/prefer-promise-reject-errors": [0]
|
|
||||||
"@typescript-eslint/prefer-readonly": [0]
|
|
||||||
"@typescript-eslint/prefer-readonly-parameter-types": [0]
|
|
||||||
"@typescript-eslint/prefer-reduce-type-parameter": [0]
|
|
||||||
"@typescript-eslint/prefer-regexp-exec": [0]
|
|
||||||
"@typescript-eslint/prefer-return-this-type": [0]
|
|
||||||
"@typescript-eslint/prefer-string-starts-ends-with": [2, {allowSingleElementEquality: always}]
|
|
||||||
"@typescript-eslint/promise-function-async": [0]
|
|
||||||
"@typescript-eslint/require-array-sort-compare": [0]
|
|
||||||
"@typescript-eslint/require-await": [0]
|
|
||||||
"@typescript-eslint/restrict-plus-operands": [2]
|
|
||||||
"@typescript-eslint/restrict-template-expressions": [0]
|
|
||||||
"@typescript-eslint/return-await": [0]
|
|
||||||
"@typescript-eslint/strict-boolean-expressions": [0]
|
|
||||||
"@typescript-eslint/switch-exhaustiveness-check": [0]
|
|
||||||
"@typescript-eslint/triple-slash-reference": [2]
|
|
||||||
"@typescript-eslint/typedef": [0]
|
|
||||||
"@typescript-eslint/unbound-method": [0] # too many false-positives
|
|
||||||
"@typescript-eslint/unified-signatures": [2]
|
|
||||||
accessor-pairs: [2]
|
|
||||||
array-callback-return: [2, {checkForEach: true}]
|
|
||||||
array-func/avoid-reverse: [2]
|
|
||||||
array-func/from-map: [2]
|
|
||||||
array-func/no-unnecessary-this-arg: [2]
|
|
||||||
array-func/prefer-array-from: [2]
|
|
||||||
array-func/prefer-flat-map: [0] # handled by unicorn/prefer-array-flat-map
|
|
||||||
array-func/prefer-flat: [0] # handled by unicorn/prefer-array-flat
|
|
||||||
arrow-body-style: [0]
|
|
||||||
block-scoped-var: [2]
|
|
||||||
camelcase: [0]
|
|
||||||
capitalized-comments: [0]
|
|
||||||
class-methods-use-this: [0]
|
|
||||||
complexity: [0]
|
|
||||||
consistent-return: [0]
|
|
||||||
consistent-this: [0]
|
|
||||||
constructor-super: [2]
|
|
||||||
curly: [0]
|
|
||||||
default-case-last: [2]
|
|
||||||
default-case: [0]
|
|
||||||
default-param-last: [0]
|
|
||||||
dot-notation: [0]
|
|
||||||
eqeqeq: [2]
|
|
||||||
for-direction: [2]
|
|
||||||
func-name-matching: [2]
|
|
||||||
func-names: [0]
|
|
||||||
func-style: [0]
|
|
||||||
getter-return: [2]
|
|
||||||
github/a11y-aria-label-is-well-formatted: [0]
|
|
||||||
github/a11y-no-title-attribute: [0]
|
|
||||||
github/a11y-no-visually-hidden-interactive-element: [0]
|
|
||||||
github/a11y-role-supports-aria-props: [0]
|
|
||||||
github/a11y-svg-has-accessible-name: [0]
|
|
||||||
github/array-foreach: [0]
|
|
||||||
github/async-currenttarget: [2]
|
|
||||||
github/async-preventdefault: [2]
|
|
||||||
github/authenticity-token: [0]
|
|
||||||
github/get-attribute: [0]
|
|
||||||
github/js-class-name: [0]
|
|
||||||
github/no-blur: [0]
|
|
||||||
github/no-d-none: [0]
|
|
||||||
github/no-dataset: [2]
|
|
||||||
github/no-dynamic-script-tag: [2]
|
|
||||||
github/no-implicit-buggy-globals: [2]
|
|
||||||
github/no-inner-html: [0]
|
|
||||||
github/no-innerText: [2]
|
|
||||||
github/no-then: [2]
|
|
||||||
github/no-useless-passive: [2]
|
|
||||||
github/prefer-observers: [2]
|
|
||||||
github/require-passive-events: [2]
|
|
||||||
github/unescaped-html-literal: [0]
|
|
||||||
grouped-accessor-pairs: [2]
|
|
||||||
guard-for-in: [0]
|
|
||||||
id-blacklist: [0]
|
|
||||||
id-length: [0]
|
|
||||||
id-match: [0]
|
|
||||||
import-x/consistent-type-specifier-style: [0]
|
|
||||||
import-x/default: [0]
|
|
||||||
import-x/dynamic-import-chunkname: [0]
|
|
||||||
import-x/export: [2]
|
|
||||||
import-x/exports-last: [0]
|
|
||||||
import-x/extensions: [2, always, {ignorePackages: true}]
|
|
||||||
import-x/first: [2]
|
|
||||||
import-x/group-exports: [0]
|
|
||||||
import-x/max-dependencies: [0]
|
|
||||||
import-x/named: [2]
|
|
||||||
import-x/namespace: [0]
|
|
||||||
import-x/newline-after-import: [0]
|
|
||||||
import-x/no-absolute-path: [0]
|
|
||||||
import-x/no-amd: [2]
|
|
||||||
import-x/no-anonymous-default-export: [0]
|
|
||||||
import-x/no-commonjs: [2]
|
|
||||||
import-x/no-cycle: [2, {ignoreExternal: true, maxDepth: 1}]
|
|
||||||
import-x/no-default-export: [0]
|
|
||||||
import-x/no-deprecated: [0]
|
|
||||||
import-x/no-dynamic-require: [0]
|
|
||||||
import-x/no-empty-named-blocks: [2]
|
|
||||||
import-x/no-extraneous-dependencies: [2]
|
|
||||||
import-x/no-import-module-exports: [0]
|
|
||||||
import-x/no-internal-modules: [0]
|
|
||||||
import-x/no-mutable-exports: [0]
|
|
||||||
import-x/no-named-as-default-member: [0]
|
|
||||||
import-x/no-named-as-default: [0]
|
|
||||||
import-x/no-named-default: [0]
|
|
||||||
import-x/no-named-export: [0]
|
|
||||||
import-x/no-namespace: [0]
|
|
||||||
import-x/no-nodejs-modules: [0]
|
|
||||||
import-x/no-relative-packages: [0]
|
|
||||||
import-x/no-relative-parent-imports: [0]
|
|
||||||
import-x/no-restricted-paths: [0]
|
|
||||||
import-x/no-self-import: [2]
|
|
||||||
import-x/no-unassigned-import: [0]
|
|
||||||
import-x/no-unresolved: [2, {commonjs: true, ignore: ["\\?.+$"]}]
|
|
||||||
import-x/no-unused-modules: [2, {unusedExports: true}]
|
|
||||||
import-x/no-useless-path-segments: [2, {commonjs: true}]
|
|
||||||
import-x/no-webpack-loader-syntax: [2]
|
|
||||||
import-x/order: [0]
|
|
||||||
import-x/prefer-default-export: [0]
|
|
||||||
import-x/unambiguous: [0]
|
|
||||||
init-declarations: [0]
|
|
||||||
line-comment-position: [0]
|
|
||||||
logical-assignment-operators: [0]
|
|
||||||
max-classes-per-file: [0]
|
|
||||||
max-depth: [0]
|
|
||||||
max-lines-per-function: [0]
|
|
||||||
max-lines: [0]
|
|
||||||
max-nested-callbacks: [0]
|
|
||||||
max-params: [0]
|
|
||||||
max-statements: [0]
|
|
||||||
multiline-comment-style: [2, separate-lines]
|
|
||||||
new-cap: [0]
|
|
||||||
no-alert: [0]
|
|
||||||
no-array-constructor: [0] # handled by @typescript-eslint/no-array-constructor
|
|
||||||
no-async-promise-executor: [0]
|
|
||||||
no-await-in-loop: [0]
|
|
||||||
no-bitwise: [0]
|
|
||||||
no-buffer-constructor: [0]
|
|
||||||
no-caller: [2]
|
|
||||||
no-case-declarations: [2]
|
|
||||||
no-class-assign: [2]
|
|
||||||
no-compare-neg-zero: [2]
|
|
||||||
no-cond-assign: [2, except-parens]
|
|
||||||
no-console: [1, {allow: [debug, info, warn, error]}]
|
|
||||||
no-const-assign: [2]
|
|
||||||
no-constant-binary-expression: [2]
|
|
||||||
no-constant-condition: [0]
|
|
||||||
no-constructor-return: [2]
|
|
||||||
no-continue: [0]
|
|
||||||
no-control-regex: [0]
|
|
||||||
no-debugger: [1]
|
|
||||||
no-delete-var: [2]
|
|
||||||
no-div-regex: [0]
|
|
||||||
no-dupe-args: [2]
|
|
||||||
no-dupe-class-members: [2]
|
|
||||||
no-dupe-else-if: [2]
|
|
||||||
no-dupe-keys: [2]
|
|
||||||
no-duplicate-case: [2]
|
|
||||||
no-duplicate-imports: [0]
|
|
||||||
no-else-return: [2]
|
|
||||||
no-empty-character-class: [2]
|
|
||||||
no-empty-function: [0]
|
|
||||||
no-empty-pattern: [2]
|
|
||||||
no-empty-static-block: [2]
|
|
||||||
no-empty: [2, {allowEmptyCatch: true}]
|
|
||||||
no-eq-null: [2]
|
|
||||||
no-eval: [2]
|
|
||||||
no-ex-assign: [2]
|
|
||||||
no-extend-native: [2]
|
|
||||||
no-extra-bind: [2]
|
|
||||||
no-extra-boolean-cast: [2]
|
|
||||||
no-extra-label: [0]
|
|
||||||
no-fallthrough: [2]
|
|
||||||
no-func-assign: [2]
|
|
||||||
no-global-assign: [2]
|
|
||||||
no-implicit-coercion: [2]
|
|
||||||
no-implicit-globals: [0]
|
|
||||||
no-implied-eval: [0] # handled by @typescript-eslint/no-implied-eval
|
|
||||||
no-import-assign: [2]
|
|
||||||
no-inline-comments: [0]
|
|
||||||
no-inner-declarations: [2]
|
|
||||||
no-invalid-regexp: [2]
|
|
||||||
no-invalid-this: [0]
|
|
||||||
no-irregular-whitespace: [2]
|
|
||||||
no-iterator: [2]
|
|
||||||
no-jquery/no-ajax-events: [2]
|
|
||||||
no-jquery/no-ajax: [2]
|
|
||||||
no-jquery/no-and-self: [2]
|
|
||||||
no-jquery/no-animate-toggle: [2]
|
|
||||||
no-jquery/no-animate: [2]
|
|
||||||
no-jquery/no-append-html: [2]
|
|
||||||
no-jquery/no-attr: [2]
|
|
||||||
no-jquery/no-bind: [2]
|
|
||||||
no-jquery/no-box-model: [2]
|
|
||||||
no-jquery/no-browser: [2]
|
|
||||||
no-jquery/no-camel-case: [2]
|
|
||||||
no-jquery/no-class-state: [2]
|
|
||||||
no-jquery/no-class: [0]
|
|
||||||
no-jquery/no-clone: [2]
|
|
||||||
no-jquery/no-closest: [0]
|
|
||||||
no-jquery/no-constructor-attributes: [2]
|
|
||||||
no-jquery/no-contains: [2]
|
|
||||||
no-jquery/no-context-prop: [2]
|
|
||||||
no-jquery/no-css: [2]
|
|
||||||
no-jquery/no-data: [0]
|
|
||||||
no-jquery/no-deferred: [2]
|
|
||||||
no-jquery/no-delegate: [2]
|
|
||||||
no-jquery/no-done-fail: [2]
|
|
||||||
no-jquery/no-each-collection: [0]
|
|
||||||
no-jquery/no-each-util: [0]
|
|
||||||
no-jquery/no-each: [0]
|
|
||||||
no-jquery/no-error-shorthand: [2]
|
|
||||||
no-jquery/no-error: [2]
|
|
||||||
no-jquery/no-escape-selector: [2]
|
|
||||||
no-jquery/no-event-shorthand: [2]
|
|
||||||
no-jquery/no-extend: [2]
|
|
||||||
no-jquery/no-fade: [2]
|
|
||||||
no-jquery/no-filter: [0]
|
|
||||||
no-jquery/no-find-collection: [0]
|
|
||||||
no-jquery/no-find-util: [2]
|
|
||||||
no-jquery/no-find: [0]
|
|
||||||
no-jquery/no-fx-interval: [2]
|
|
||||||
no-jquery/no-fx: [2]
|
|
||||||
no-jquery/no-global-eval: [2]
|
|
||||||
no-jquery/no-global-selector: [0]
|
|
||||||
no-jquery/no-grep: [2]
|
|
||||||
no-jquery/no-has: [2]
|
|
||||||
no-jquery/no-hold-ready: [2]
|
|
||||||
no-jquery/no-html: [0]
|
|
||||||
no-jquery/no-in-array: [2]
|
|
||||||
no-jquery/no-is-array: [2]
|
|
||||||
no-jquery/no-is-empty-object: [2]
|
|
||||||
no-jquery/no-is-function: [2]
|
|
||||||
no-jquery/no-is-numeric: [2]
|
|
||||||
no-jquery/no-is-plain-object: [2]
|
|
||||||
no-jquery/no-is-window: [2]
|
|
||||||
no-jquery/no-is: [2]
|
|
||||||
no-jquery/no-jquery-constructor: [0]
|
|
||||||
no-jquery/no-live: [2]
|
|
||||||
no-jquery/no-load-shorthand: [2]
|
|
||||||
no-jquery/no-load: [2]
|
|
||||||
no-jquery/no-map-collection: [0]
|
|
||||||
no-jquery/no-map-util: [2]
|
|
||||||
no-jquery/no-map: [2]
|
|
||||||
no-jquery/no-merge: [2]
|
|
||||||
no-jquery/no-node-name: [2]
|
|
||||||
no-jquery/no-noop: [2]
|
|
||||||
no-jquery/no-now: [2]
|
|
||||||
no-jquery/no-on-ready: [2]
|
|
||||||
no-jquery/no-other-methods: [0]
|
|
||||||
no-jquery/no-other-utils: [2]
|
|
||||||
no-jquery/no-param: [2]
|
|
||||||
no-jquery/no-parent: [0]
|
|
||||||
no-jquery/no-parents: [2]
|
|
||||||
no-jquery/no-parse-html-literal: [2]
|
|
||||||
no-jquery/no-parse-html: [2]
|
|
||||||
no-jquery/no-parse-json: [2]
|
|
||||||
no-jquery/no-parse-xml: [2]
|
|
||||||
no-jquery/no-prop: [2]
|
|
||||||
no-jquery/no-proxy: [2]
|
|
||||||
no-jquery/no-ready-shorthand: [2]
|
|
||||||
no-jquery/no-ready: [2]
|
|
||||||
no-jquery/no-selector-prop: [2]
|
|
||||||
no-jquery/no-serialize: [2]
|
|
||||||
no-jquery/no-size: [2]
|
|
||||||
no-jquery/no-sizzle: [2]
|
|
||||||
no-jquery/no-slide: [2]
|
|
||||||
no-jquery/no-sub: [2]
|
|
||||||
no-jquery/no-support: [2]
|
|
||||||
no-jquery/no-text: [2]
|
|
||||||
no-jquery/no-trigger: [0]
|
|
||||||
no-jquery/no-trim: [2]
|
|
||||||
no-jquery/no-type: [2]
|
|
||||||
no-jquery/no-unique: [2]
|
|
||||||
no-jquery/no-unload-shorthand: [2]
|
|
||||||
no-jquery/no-val: [0]
|
|
||||||
no-jquery/no-visibility: [2]
|
|
||||||
no-jquery/no-when: [2]
|
|
||||||
no-jquery/no-wrap: [2]
|
|
||||||
no-jquery/variable-pattern: [2]
|
|
||||||
no-label-var: [2]
|
|
||||||
no-labels: [0] # handled by no-restricted-syntax
|
|
||||||
no-lone-blocks: [2]
|
|
||||||
no-lonely-if: [0]
|
|
||||||
no-loop-func: [0]
|
|
||||||
no-loss-of-precision: [2]
|
|
||||||
no-magic-numbers: [0]
|
|
||||||
no-misleading-character-class: [2]
|
|
||||||
no-multi-assign: [0]
|
|
||||||
no-multi-str: [2]
|
|
||||||
no-negated-condition: [0]
|
|
||||||
no-nested-ternary: [0]
|
|
||||||
no-new-func: [2]
|
|
||||||
no-new-native-nonconstructor: [2]
|
|
||||||
no-new-object: [2]
|
|
||||||
no-new-symbol: [2]
|
|
||||||
no-new-wrappers: [2]
|
|
||||||
no-new: [0]
|
|
||||||
no-nonoctal-decimal-escape: [2]
|
|
||||||
no-obj-calls: [2]
|
|
||||||
no-octal-escape: [2]
|
|
||||||
no-octal: [2]
|
|
||||||
no-param-reassign: [0]
|
|
||||||
no-plusplus: [0]
|
|
||||||
no-promise-executor-return: [0]
|
|
||||||
no-proto: [2]
|
|
||||||
no-prototype-builtins: [2]
|
|
||||||
no-redeclare: [0] # must be disabled for typescript overloads
|
|
||||||
no-regex-spaces: [2]
|
|
||||||
no-restricted-exports: [0]
|
|
||||||
no-restricted-globals: [2, addEventListener, blur, close, closed, confirm, defaultStatus, defaultstatus, error, event, external, find, focus, frameElement, frames, history, innerHeight, innerWidth, isFinite, isNaN, length, location, locationbar, menubar, moveBy, moveTo, name, onblur, onerror, onfocus, onload, onresize, onunload, open, opener, opera, outerHeight, outerWidth, pageXOffset, pageYOffset, parent, print, removeEventListener, resizeBy, resizeTo, screen, screenLeft, screenTop, screenX, screenY, scroll, scrollbars, scrollBy, scrollTo, scrollX, scrollY, self, status, statusbar, stop, toolbar, top, __dirname, __filename]
|
|
||||||
no-restricted-imports: [0]
|
|
||||||
no-restricted-syntax: [2, WithStatement, ForInStatement, LabeledStatement, SequenceExpression, {selector: "CallExpression[callee.name='fetch']", message: "use modules/fetch.ts instead"}]
|
|
||||||
no-return-assign: [0]
|
|
||||||
no-script-url: [2]
|
|
||||||
no-self-assign: [2, {props: true}]
|
|
||||||
no-self-compare: [2]
|
|
||||||
no-sequences: [2]
|
|
||||||
no-setter-return: [2]
|
|
||||||
no-shadow-restricted-names: [2]
|
|
||||||
no-shadow: [0]
|
|
||||||
no-sparse-arrays: [2]
|
|
||||||
no-template-curly-in-string: [2]
|
|
||||||
no-ternary: [0]
|
|
||||||
no-this-before-super: [2]
|
|
||||||
no-throw-literal: [2]
|
|
||||||
no-undef-init: [2]
|
|
||||||
no-undef: [2, {typeof: true}] # TODO: disable this rule after tsc passes
|
|
||||||
no-undefined: [0]
|
|
||||||
no-underscore-dangle: [0]
|
|
||||||
no-unexpected-multiline: [2]
|
|
||||||
no-unmodified-loop-condition: [2]
|
|
||||||
no-unneeded-ternary: [2]
|
|
||||||
no-unreachable-loop: [2]
|
|
||||||
no-unreachable: [2]
|
|
||||||
no-unsafe-finally: [2]
|
|
||||||
no-unsafe-negation: [2]
|
|
||||||
no-unused-expressions: [2]
|
|
||||||
no-unused-labels: [2]
|
|
||||||
no-unused-private-class-members: [2]
|
|
||||||
no-unused-vars: [0] # handled by @typescript-eslint/no-unused-vars
|
|
||||||
no-use-before-define: [2, {functions: false, classes: true, variables: true, allowNamedExports: true}]
|
|
||||||
no-use-extend-native/no-use-extend-native: [2]
|
|
||||||
no-useless-backreference: [2]
|
|
||||||
no-useless-call: [2]
|
|
||||||
no-useless-catch: [2]
|
|
||||||
no-useless-computed-key: [2]
|
|
||||||
no-useless-concat: [2]
|
|
||||||
no-useless-constructor: [2]
|
|
||||||
no-useless-escape: [2]
|
|
||||||
no-useless-rename: [2]
|
|
||||||
no-useless-return: [2]
|
|
||||||
no-var: [2]
|
|
||||||
no-void: [2]
|
|
||||||
no-warning-comments: [0]
|
|
||||||
no-with: [0] # handled by no-restricted-syntax
|
|
||||||
object-shorthand: [2, always]
|
|
||||||
one-var-declaration-per-line: [0]
|
|
||||||
one-var: [0]
|
|
||||||
operator-assignment: [2, always]
|
|
||||||
operator-linebreak: [2, after]
|
|
||||||
prefer-arrow-callback: [2, {allowNamedFunctions: true, allowUnboundThis: true}]
|
|
||||||
prefer-const: [2, {destructuring: all, ignoreReadBeforeAssign: true}]
|
|
||||||
prefer-destructuring: [0]
|
|
||||||
prefer-exponentiation-operator: [2]
|
|
||||||
prefer-named-capture-group: [0]
|
|
||||||
prefer-numeric-literals: [2]
|
|
||||||
prefer-object-has-own: [2]
|
|
||||||
prefer-object-spread: [2]
|
|
||||||
prefer-promise-reject-errors: [2, {allowEmptyReject: false}]
|
|
||||||
prefer-regex-literals: [2]
|
|
||||||
prefer-rest-params: [2]
|
|
||||||
prefer-spread: [2]
|
|
||||||
prefer-template: [2]
|
|
||||||
radix: [2, as-needed]
|
|
||||||
regexp/confusing-quantifier: [2]
|
|
||||||
regexp/control-character-escape: [2]
|
|
||||||
regexp/hexadecimal-escape: [0]
|
|
||||||
regexp/letter-case: [0]
|
|
||||||
regexp/match-any: [2]
|
|
||||||
regexp/negation: [2]
|
|
||||||
regexp/no-contradiction-with-assertion: [0]
|
|
||||||
regexp/no-control-character: [0]
|
|
||||||
regexp/no-dupe-characters-character-class: [2]
|
|
||||||
regexp/no-dupe-disjunctions: [2]
|
|
||||||
regexp/no-empty-alternative: [2]
|
|
||||||
regexp/no-empty-capturing-group: [2]
|
|
||||||
regexp/no-empty-character-class: [0]
|
|
||||||
regexp/no-empty-group: [2]
|
|
||||||
regexp/no-empty-lookarounds-assertion: [2]
|
|
||||||
regexp/no-empty-string-literal: [2]
|
|
||||||
regexp/no-escape-backspace: [2]
|
|
||||||
regexp/no-extra-lookaround-assertions: [0]
|
|
||||||
regexp/no-invalid-regexp: [2]
|
|
||||||
regexp/no-invisible-character: [2]
|
|
||||||
regexp/no-lazy-ends: [2]
|
|
||||||
regexp/no-legacy-features: [2]
|
|
||||||
regexp/no-misleading-capturing-group: [0]
|
|
||||||
regexp/no-misleading-unicode-character: [0]
|
|
||||||
regexp/no-missing-g-flag: [2]
|
|
||||||
regexp/no-non-standard-flag: [2]
|
|
||||||
regexp/no-obscure-range: [2]
|
|
||||||
regexp/no-octal: [2]
|
|
||||||
regexp/no-optional-assertion: [2]
|
|
||||||
regexp/no-potentially-useless-backreference: [2]
|
|
||||||
regexp/no-standalone-backslash: [2]
|
|
||||||
regexp/no-super-linear-backtracking: [0]
|
|
||||||
regexp/no-super-linear-move: [0]
|
|
||||||
regexp/no-trivially-nested-assertion: [2]
|
|
||||||
regexp/no-trivially-nested-quantifier: [2]
|
|
||||||
regexp/no-unused-capturing-group: [0]
|
|
||||||
regexp/no-useless-assertions: [2]
|
|
||||||
regexp/no-useless-backreference: [2]
|
|
||||||
regexp/no-useless-character-class: [2]
|
|
||||||
regexp/no-useless-dollar-replacements: [2]
|
|
||||||
regexp/no-useless-escape: [2]
|
|
||||||
regexp/no-useless-flag: [2]
|
|
||||||
regexp/no-useless-lazy: [2]
|
|
||||||
regexp/no-useless-non-capturing-group: [2]
|
|
||||||
regexp/no-useless-quantifier: [2]
|
|
||||||
regexp/no-useless-range: [2]
|
|
||||||
regexp/no-useless-set-operand: [2]
|
|
||||||
regexp/no-useless-string-literal: [2]
|
|
||||||
regexp/no-useless-two-nums-quantifier: [2]
|
|
||||||
regexp/no-zero-quantifier: [2]
|
|
||||||
regexp/optimal-lookaround-quantifier: [2]
|
|
||||||
regexp/optimal-quantifier-concatenation: [0]
|
|
||||||
regexp/prefer-character-class: [0]
|
|
||||||
regexp/prefer-d: [0]
|
|
||||||
regexp/prefer-escape-replacement-dollar-char: [0]
|
|
||||||
regexp/prefer-lookaround: [0]
|
|
||||||
regexp/prefer-named-backreference: [0]
|
|
||||||
regexp/prefer-named-capture-group: [0]
|
|
||||||
regexp/prefer-named-replacement: [0]
|
|
||||||
regexp/prefer-plus-quantifier: [2]
|
|
||||||
regexp/prefer-predefined-assertion: [2]
|
|
||||||
regexp/prefer-quantifier: [0]
|
|
||||||
regexp/prefer-question-quantifier: [2]
|
|
||||||
regexp/prefer-range: [2]
|
|
||||||
regexp/prefer-regexp-exec: [2]
|
|
||||||
regexp/prefer-regexp-test: [2]
|
|
||||||
regexp/prefer-result-array-groups: [0]
|
|
||||||
regexp/prefer-set-operation: [2]
|
|
||||||
regexp/prefer-star-quantifier: [2]
|
|
||||||
regexp/prefer-unicode-codepoint-escapes: [2]
|
|
||||||
regexp/prefer-w: [0]
|
|
||||||
regexp/require-unicode-regexp: [0]
|
|
||||||
regexp/simplify-set-operations: [2]
|
|
||||||
regexp/sort-alternatives: [0]
|
|
||||||
regexp/sort-character-class-elements: [0]
|
|
||||||
regexp/sort-flags: [0]
|
|
||||||
regexp/strict: [2]
|
|
||||||
regexp/unicode-escape: [0]
|
|
||||||
regexp/use-ignore-case: [0]
|
|
||||||
require-atomic-updates: [0]
|
|
||||||
require-await: [0] # handled by @typescript-eslint/require-await
|
|
||||||
require-unicode-regexp: [0]
|
|
||||||
require-yield: [2]
|
|
||||||
sonarjs/cognitive-complexity: [0]
|
|
||||||
sonarjs/elseif-without-else: [0]
|
|
||||||
sonarjs/max-switch-cases: [0]
|
|
||||||
sonarjs/no-all-duplicated-branches: [2]
|
|
||||||
sonarjs/no-collapsible-if: [0]
|
|
||||||
sonarjs/no-collection-size-mischeck: [2]
|
|
||||||
sonarjs/no-duplicate-string: [0]
|
|
||||||
sonarjs/no-duplicated-branches: [0]
|
|
||||||
sonarjs/no-element-overwrite: [2]
|
|
||||||
sonarjs/no-empty-collection: [2]
|
|
||||||
sonarjs/no-extra-arguments: [2]
|
|
||||||
sonarjs/no-gratuitous-expressions: [2]
|
|
||||||
sonarjs/no-identical-conditions: [2]
|
|
||||||
sonarjs/no-identical-expressions: [2]
|
|
||||||
sonarjs/no-identical-functions: [2, 5]
|
|
||||||
sonarjs/no-ignored-return: [2]
|
|
||||||
sonarjs/no-inverted-boolean-check: [2]
|
|
||||||
sonarjs/no-nested-switch: [0]
|
|
||||||
sonarjs/no-nested-template-literals: [0]
|
|
||||||
sonarjs/no-one-iteration-loop: [2]
|
|
||||||
sonarjs/no-redundant-boolean: [2]
|
|
||||||
sonarjs/no-redundant-jump: [2]
|
|
||||||
sonarjs/no-same-line-conditional: [2]
|
|
||||||
sonarjs/no-small-switch: [0]
|
|
||||||
sonarjs/no-unused-collection: [2]
|
|
||||||
sonarjs/no-use-of-empty-return-value: [2]
|
|
||||||
sonarjs/no-useless-catch: [2]
|
|
||||||
sonarjs/non-existent-operator: [2]
|
|
||||||
sonarjs/prefer-immediate-return: [0]
|
|
||||||
sonarjs/prefer-object-literal: [0]
|
|
||||||
sonarjs/prefer-single-boolean-return: [0]
|
|
||||||
sonarjs/prefer-while: [2]
|
|
||||||
sort-imports: [0]
|
|
||||||
sort-keys: [0]
|
|
||||||
sort-vars: [0]
|
|
||||||
strict: [0]
|
|
||||||
symbol-description: [2]
|
|
||||||
unicode-bom: [2, never]
|
|
||||||
unicorn/better-regex: [0]
|
|
||||||
unicorn/catch-error-name: [0]
|
|
||||||
unicorn/consistent-destructuring: [2]
|
|
||||||
unicorn/consistent-empty-array-spread: [2]
|
|
||||||
unicorn/consistent-existence-index-check: [0]
|
|
||||||
unicorn/consistent-function-scoping: [0]
|
|
||||||
unicorn/custom-error-definition: [0]
|
|
||||||
unicorn/empty-brace-spaces: [2]
|
|
||||||
unicorn/error-message: [0]
|
|
||||||
unicorn/escape-case: [0]
|
|
||||||
unicorn/expiring-todo-comments: [0]
|
|
||||||
unicorn/explicit-length-check: [0]
|
|
||||||
unicorn/filename-case: [0]
|
|
||||||
unicorn/import-index: [0]
|
|
||||||
unicorn/import-style: [0]
|
|
||||||
unicorn/new-for-builtins: [2]
|
|
||||||
unicorn/no-abusive-eslint-disable: [0]
|
|
||||||
unicorn/no-anonymous-default-export: [0]
|
|
||||||
unicorn/no-array-callback-reference: [0]
|
|
||||||
unicorn/no-array-for-each: [2]
|
|
||||||
unicorn/no-array-method-this-argument: [2]
|
|
||||||
unicorn/no-array-push-push: [2]
|
|
||||||
unicorn/no-array-reduce: [2]
|
|
||||||
unicorn/no-await-expression-member: [0]
|
|
||||||
unicorn/no-await-in-promise-methods: [2]
|
|
||||||
unicorn/no-console-spaces: [0]
|
|
||||||
unicorn/no-document-cookie: [2]
|
|
||||||
unicorn/no-empty-file: [2]
|
|
||||||
unicorn/no-for-loop: [0]
|
|
||||||
unicorn/no-hex-escape: [0]
|
|
||||||
unicorn/no-instanceof-array: [0]
|
|
||||||
unicorn/no-invalid-fetch-options: [2]
|
|
||||||
unicorn/no-invalid-remove-event-listener: [2]
|
|
||||||
unicorn/no-keyword-prefix: [0]
|
|
||||||
unicorn/no-length-as-slice-end: [2]
|
|
||||||
unicorn/no-lonely-if: [2]
|
|
||||||
unicorn/no-magic-array-flat-depth: [0]
|
|
||||||
unicorn/no-negated-condition: [0]
|
|
||||||
unicorn/no-negation-in-equality-check: [2]
|
|
||||||
unicorn/no-nested-ternary: [0]
|
|
||||||
unicorn/no-new-array: [0]
|
|
||||||
unicorn/no-new-buffer: [0]
|
|
||||||
unicorn/no-null: [0]
|
|
||||||
unicorn/no-object-as-default-parameter: [0]
|
|
||||||
unicorn/no-process-exit: [0]
|
|
||||||
unicorn/no-single-promise-in-promise-methods: [2]
|
|
||||||
unicorn/no-static-only-class: [2]
|
|
||||||
unicorn/no-thenable: [2]
|
|
||||||
unicorn/no-this-assignment: [2]
|
|
||||||
unicorn/no-typeof-undefined: [2]
|
|
||||||
unicorn/no-unnecessary-await: [2]
|
|
||||||
unicorn/no-unnecessary-polyfills: [2]
|
|
||||||
unicorn/no-unreadable-array-destructuring: [0]
|
|
||||||
unicorn/no-unreadable-iife: [2]
|
|
||||||
unicorn/no-unused-properties: [2]
|
|
||||||
unicorn/no-useless-fallback-in-spread: [2]
|
|
||||||
unicorn/no-useless-length-check: [2]
|
|
||||||
unicorn/no-useless-promise-resolve-reject: [2]
|
|
||||||
unicorn/no-useless-spread: [2]
|
|
||||||
unicorn/no-useless-switch-case: [2]
|
|
||||||
unicorn/no-useless-undefined: [0]
|
|
||||||
unicorn/no-zero-fractions: [2]
|
|
||||||
unicorn/number-literal-case: [0]
|
|
||||||
unicorn/numeric-separators-style: [0]
|
|
||||||
unicorn/prefer-add-event-listener: [2]
|
|
||||||
unicorn/prefer-array-find: [2]
|
|
||||||
unicorn/prefer-array-flat-map: [2]
|
|
||||||
unicorn/prefer-array-flat: [2]
|
|
||||||
unicorn/prefer-array-index-of: [2]
|
|
||||||
unicorn/prefer-array-some: [2]
|
|
||||||
unicorn/prefer-at: [0]
|
|
||||||
unicorn/prefer-blob-reading-methods: [2]
|
|
||||||
unicorn/prefer-code-point: [0]
|
|
||||||
unicorn/prefer-date-now: [2]
|
|
||||||
unicorn/prefer-default-parameters: [0]
|
|
||||||
unicorn/prefer-dom-node-append: [2]
|
|
||||||
unicorn/prefer-dom-node-dataset: [0]
|
|
||||||
unicorn/prefer-dom-node-remove: [2]
|
|
||||||
unicorn/prefer-dom-node-text-content: [2]
|
|
||||||
unicorn/prefer-event-target: [2]
|
|
||||||
unicorn/prefer-export-from: [0]
|
|
||||||
unicorn/prefer-global-this: [0]
|
|
||||||
unicorn/prefer-includes: [2]
|
|
||||||
unicorn/prefer-json-parse-buffer: [0]
|
|
||||||
unicorn/prefer-keyboard-event-key: [2]
|
|
||||||
unicorn/prefer-logical-operator-over-ternary: [2]
|
|
||||||
unicorn/prefer-math-min-max: [2]
|
|
||||||
unicorn/prefer-math-trunc: [2]
|
|
||||||
unicorn/prefer-modern-dom-apis: [0]
|
|
||||||
unicorn/prefer-modern-math-apis: [2]
|
|
||||||
unicorn/prefer-module: [2]
|
|
||||||
unicorn/prefer-native-coercion-functions: [2]
|
|
||||||
unicorn/prefer-negative-index: [2]
|
|
||||||
unicorn/prefer-node-protocol: [2]
|
|
||||||
unicorn/prefer-number-properties: [0]
|
|
||||||
unicorn/prefer-object-from-entries: [2]
|
|
||||||
unicorn/prefer-object-has-own: [0]
|
|
||||||
unicorn/prefer-optional-catch-binding: [2]
|
|
||||||
unicorn/prefer-prototype-methods: [0]
|
|
||||||
unicorn/prefer-query-selector: [2]
|
|
||||||
unicorn/prefer-reflect-apply: [0]
|
|
||||||
unicorn/prefer-regexp-test: [2]
|
|
||||||
unicorn/prefer-set-has: [0]
|
|
||||||
unicorn/prefer-set-size: [2]
|
|
||||||
unicorn/prefer-spread: [0]
|
|
||||||
unicorn/prefer-string-raw: [0]
|
|
||||||
unicorn/prefer-string-replace-all: [0]
|
|
||||||
unicorn/prefer-string-slice: [0]
|
|
||||||
unicorn/prefer-string-starts-ends-with: [2]
|
|
||||||
unicorn/prefer-string-trim-start-end: [2]
|
|
||||||
unicorn/prefer-structured-clone: [2]
|
|
||||||
unicorn/prefer-switch: [0]
|
|
||||||
unicorn/prefer-ternary: [0]
|
|
||||||
unicorn/prefer-text-content: [2]
|
|
||||||
unicorn/prefer-top-level-await: [0]
|
|
||||||
unicorn/prefer-type-error: [0]
|
|
||||||
unicorn/prevent-abbreviations: [0]
|
|
||||||
unicorn/relative-url-style: [2]
|
|
||||||
unicorn/require-array-join-separator: [2]
|
|
||||||
unicorn/require-number-to-fixed-digits-argument: [2]
|
|
||||||
unicorn/require-post-message-target-origin: [0]
|
|
||||||
unicorn/string-content: [0]
|
|
||||||
unicorn/switch-case-braces: [0]
|
|
||||||
unicorn/template-indent: [2]
|
|
||||||
unicorn/text-encoding-identifier-case: [0]
|
|
||||||
unicorn/throw-new-error: [2]
|
|
||||||
use-isnan: [2]
|
|
||||||
valid-typeof: [2, {requireStringLiterals: true}]
|
|
||||||
vars-on-top: [0]
|
|
||||||
wc/attach-shadow-constructor: [2]
|
|
||||||
wc/define-tag-after-class-definition: [0]
|
|
||||||
wc/expose-class-on-global: [0]
|
|
||||||
wc/file-name-matches-element: [2]
|
|
||||||
wc/guard-define-call: [0]
|
|
||||||
wc/guard-super-call: [2]
|
|
||||||
wc/max-elements-per-file: [0]
|
|
||||||
wc/no-child-traversal-in-attributechangedcallback: [2]
|
|
||||||
wc/no-child-traversal-in-connectedcallback: [2]
|
|
||||||
wc/no-closed-shadow-root: [2]
|
|
||||||
wc/no-constructor-attributes: [2]
|
|
||||||
wc/no-constructor-params: [2]
|
|
||||||
wc/no-constructor: [2]
|
|
||||||
wc/no-customized-built-in-elements: [2]
|
|
||||||
wc/no-exports-with-element: [0]
|
|
||||||
wc/no-invalid-element-name: [2]
|
|
||||||
wc/no-invalid-extends: [2]
|
|
||||||
wc/no-method-prefixed-with-on: [2]
|
|
||||||
wc/no-self-class: [2]
|
|
||||||
wc/no-typos: [2]
|
|
||||||
wc/require-listener-teardown: [2]
|
|
||||||
wc/tag-name-matches-class: [2]
|
|
||||||
yoda: [2, never]
|
|
||||||
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -4,8 +4,7 @@
|
|||||||
/assets/*.json linguist-generated
|
/assets/*.json linguist-generated
|
||||||
/public/assets/img/svg/*.svg linguist-generated
|
/public/assets/img/svg/*.svg linguist-generated
|
||||||
/templates/swagger/v1_json.tmpl linguist-generated
|
/templates/swagger/v1_json.tmpl linguist-generated
|
||||||
|
/options/fileicon/** linguist-generated
|
||||||
/vendor/** -text -eol linguist-vendored
|
/vendor/** -text -eol linguist-vendored
|
||||||
/web_src/fomantic/build/** linguist-generated
|
|
||||||
/web_src/fomantic/_site/globals/site.variables linguist-language=Less
|
|
||||||
/web_src/js/vendor/** -text -eol linguist-vendored
|
/web_src/js/vendor/** -text -eol linguist-vendored
|
||||||
Dockerfile.* linguist-language=Dockerfile
|
Dockerfile.* linguist-language=Dockerfile
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -13,5 +13,5 @@ contact_links:
|
|||||||
url: https://docs.gitea.com/help/faq
|
url: https://docs.gitea.com/help/faq
|
||||||
about: Please check if your question isn't mentioned here.
|
about: Please check if your question isn't mentioned here.
|
||||||
- name: Crowdin Translations
|
- name: Crowdin Translations
|
||||||
url: https://crowdin.com/project/gitea
|
url: https://translate.gitea.com
|
||||||
about: Translations are managed here.
|
about: Translations are managed here.
|
||||||
|
|||||||
24
.github/labeler.yml
vendored
24
.github/labeler.yml
vendored
@@ -41,7 +41,7 @@ modifies/internal:
|
|||||||
- ".dockerignore"
|
- ".dockerignore"
|
||||||
- "docker/**"
|
- "docker/**"
|
||||||
- ".editorconfig"
|
- ".editorconfig"
|
||||||
- ".eslintrc.yaml"
|
- ".eslintrc.cjs"
|
||||||
- ".golangci.yml"
|
- ".golangci.yml"
|
||||||
- ".gitpod.yml"
|
- ".gitpod.yml"
|
||||||
- ".markdownlint.yaml"
|
- ".markdownlint.yaml"
|
||||||
@@ -49,7 +49,7 @@ modifies/internal:
|
|||||||
- "stylelint.config.js"
|
- "stylelint.config.js"
|
||||||
- ".yamllint.yaml"
|
- ".yamllint.yaml"
|
||||||
- ".github/**"
|
- ".github/**"
|
||||||
- ".gitea/"
|
- ".gitea/**"
|
||||||
- ".devcontainer/**"
|
- ".devcontainer/**"
|
||||||
- "build.go"
|
- "build.go"
|
||||||
- "build/**"
|
- "build/**"
|
||||||
@@ -59,9 +59,9 @@ modifies/dependencies:
|
|||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- "package.json"
|
- "package.json"
|
||||||
- "package-lock.json"
|
- "pnpm-lock.yaml"
|
||||||
- "pyproject.toml"
|
- "pyproject.toml"
|
||||||
- "poetry.lock"
|
- "uv.lock"
|
||||||
- "go.mod"
|
- "go.mod"
|
||||||
- "go.sum"
|
- "go.sum"
|
||||||
|
|
||||||
@@ -73,11 +73,21 @@ modifies/go:
|
|||||||
modifies/frontend:
|
modifies/frontend:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- "**/*.js"
|
- "*.js"
|
||||||
- "**/*.ts"
|
- "*.ts"
|
||||||
- "**/*.vue"
|
- "web_src/**"
|
||||||
|
|
||||||
docs-update-needed:
|
docs-update-needed:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- "custom/conf/app.example.ini"
|
- "custom/conf/app.example.ini"
|
||||||
|
|
||||||
|
topic/code-linting:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- ".eslintrc.cjs"
|
||||||
|
- ".golangci.yml"
|
||||||
|
- ".markdownlint.yaml"
|
||||||
|
- ".spectral.yaml"
|
||||||
|
- ".yamllint.yaml"
|
||||||
|
- "stylelint.config.js"
|
||||||
|
|||||||
6
.github/workflows/cron-licenses.yml
vendored
6
.github/workflows/cron-licenses.yml
vendored
@@ -1,8 +1,8 @@
|
|||||||
name: cron-licenses
|
name: cron-licenses
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
# schedule:
|
||||||
- cron: "7 0 * * 1" # every Monday at 00:07 UTC
|
# - cron: "7 0 * * 1" # every Monday at 00:07 UTC
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- run: make generate-license generate-gitignore
|
- run: make generate-gitignore
|
||||||
timeout-minutes: 40
|
timeout-minutes: 40
|
||||||
- name: push translations to repo
|
- name: push translations to repo
|
||||||
uses: appleboy/git-push-action@v0.0.3
|
uses: appleboy/git-push-action@v0.0.3
|
||||||
|
|||||||
18
.github/workflows/files-changed.yml
vendored
18
.github/workflows/files-changed.yml
vendored
@@ -51,21 +51,23 @@ jobs:
|
|||||||
- "options/locale/locale_en-US.ini"
|
- "options/locale/locale_en-US.ini"
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
- "**/*.js"
|
- "*.js"
|
||||||
|
- "*.ts"
|
||||||
- "web_src/**"
|
- "web_src/**"
|
||||||
|
- "tools/*.js"
|
||||||
|
- "tools/*.ts"
|
||||||
- "assets/emoji.json"
|
- "assets/emoji.json"
|
||||||
- "package.json"
|
- "package.json"
|
||||||
- "package-lock.json"
|
- "pnpm-lock.yaml"
|
||||||
- "Makefile"
|
- "Makefile"
|
||||||
- ".eslintrc.yaml"
|
- ".eslintrc.cjs"
|
||||||
- "stylelint.config.js"
|
|
||||||
- ".npmrc"
|
- ".npmrc"
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
- "**/*.md"
|
- "**/*.md"
|
||||||
- ".markdownlint.yaml"
|
- ".markdownlint.yaml"
|
||||||
- "package.json"
|
- "package.json"
|
||||||
- "package-lock.json"
|
- "pnpm-lock.yaml"
|
||||||
|
|
||||||
actions:
|
actions:
|
||||||
- ".github/workflows/*"
|
- ".github/workflows/*"
|
||||||
@@ -75,7 +77,7 @@ jobs:
|
|||||||
- "tools/lint-templates-*.js"
|
- "tools/lint-templates-*.js"
|
||||||
- "templates/**/*.tmpl"
|
- "templates/**/*.tmpl"
|
||||||
- "pyproject.toml"
|
- "pyproject.toml"
|
||||||
- "poetry.lock"
|
- "uv.lock"
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
- "Dockerfile"
|
- "Dockerfile"
|
||||||
@@ -85,9 +87,10 @@ jobs:
|
|||||||
|
|
||||||
swagger:
|
swagger:
|
||||||
- "templates/swagger/v1_json.tmpl"
|
- "templates/swagger/v1_json.tmpl"
|
||||||
|
- "templates/swagger/v1_input.json"
|
||||||
- "Makefile"
|
- "Makefile"
|
||||||
- "package.json"
|
- "package.json"
|
||||||
- "package-lock.json"
|
- "pnpm-lock.yaml"
|
||||||
- ".spectral.yaml"
|
- ".spectral.yaml"
|
||||||
|
|
||||||
yaml:
|
yaml:
|
||||||
@@ -95,4 +98,3 @@ jobs:
|
|||||||
- "**/*.yaml"
|
- "**/*.yaml"
|
||||||
- ".yamllint.yaml"
|
- ".yamllint.yaml"
|
||||||
- "pyproject.toml"
|
- "pyproject.toml"
|
||||||
- "poetry.lock"
|
|
||||||
|
|||||||
42
.github/workflows/pull-compliance.yml
vendored
42
.github/workflows/pull-compliance.yml
vendored
@@ -32,15 +32,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-python@v5
|
- uses: astral-sh/setup-uv@v6
|
||||||
|
- run: uv python install 3.12
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
- uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.12"
|
node-version: 24
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 22
|
|
||||||
cache: npm
|
|
||||||
cache-dependency-path: package-lock.json
|
|
||||||
- run: pip install poetry
|
|
||||||
- run: make deps-py
|
- run: make deps-py
|
||||||
- run: make deps-frontend
|
- run: make deps-frontend
|
||||||
- run: make lint-templates
|
- run: make lint-templates
|
||||||
@@ -51,10 +48,8 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-python@v5
|
- uses: astral-sh/setup-uv@v6
|
||||||
with:
|
- run: uv python install 3.12
|
||||||
python-version: "3.12"
|
|
||||||
- run: pip install poetry
|
|
||||||
- run: make deps-py
|
- run: make deps-py
|
||||||
- run: make lint-yaml
|
- run: make lint-yaml
|
||||||
|
|
||||||
@@ -64,11 +59,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: pnpm/action-setup@v4
|
||||||
|
- uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 24
|
||||||
cache: npm
|
|
||||||
cache-dependency-path: package-lock.json
|
|
||||||
- run: make deps-frontend
|
- run: make deps-frontend
|
||||||
- run: make lint-swagger
|
- run: make lint-swagger
|
||||||
|
|
||||||
@@ -95,7 +89,7 @@ jobs:
|
|||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- run: make deps-backend deps-tools
|
- run: make deps-backend deps-tools
|
||||||
- run: make lint-go-windows lint-go-vet
|
- run: make lint-go-windows lint-go-gitea-vet
|
||||||
env:
|
env:
|
||||||
TAGS: bindata sqlite sqlite_unlock_notify
|
TAGS: bindata sqlite sqlite_unlock_notify
|
||||||
GOOS: windows
|
GOOS: windows
|
||||||
@@ -135,11 +129,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: pnpm/action-setup@v4
|
||||||
|
- uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 24
|
||||||
cache: npm
|
|
||||||
cache-dependency-path: package-lock.json
|
|
||||||
- run: make deps-frontend
|
- run: make deps-frontend
|
||||||
- run: make lint-frontend
|
- run: make lint-frontend
|
||||||
- run: make checks-frontend
|
- run: make checks-frontend
|
||||||
@@ -184,11 +177,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: pnpm/action-setup@v4
|
||||||
|
- uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 24
|
||||||
cache: npm
|
|
||||||
cache-dependency-path: package-lock.json
|
|
||||||
- run: make deps-frontend
|
- run: make deps-frontend
|
||||||
- run: make lint-md
|
- run: make lint-md
|
||||||
|
|
||||||
|
|||||||
16
.github/workflows/pull-db-tests.yml
vendored
16
.github/workflows/pull-db-tests.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
services:
|
services:
|
||||||
pgsql:
|
pgsql:
|
||||||
image: postgres:12
|
image: postgres:14
|
||||||
env:
|
env:
|
||||||
POSTGRES_DB: test
|
POSTGRES_DB: test
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
minio:
|
minio:
|
||||||
# as github actions doesn't support "entrypoint", we need to use a non-official image
|
# as github actions doesn't support "entrypoint", we need to use a non-official image
|
||||||
# that has a custom entrypoint set to "minio server /data"
|
# that has a custom entrypoint set to "minio server /data"
|
||||||
image: bitnami/minio:2023.8.31
|
image: bitnamilegacy/minio:2023.8.31
|
||||||
env:
|
env:
|
||||||
MINIO_ROOT_USER: 123456
|
MINIO_ROOT_USER: 123456
|
||||||
MINIO_ROOT_PASSWORD: 12345678
|
MINIO_ROOT_PASSWORD: 12345678
|
||||||
@@ -98,7 +98,7 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- "9200:9200"
|
- "9200:9200"
|
||||||
meilisearch:
|
meilisearch:
|
||||||
image: getmeili/meilisearch:v1.2.0
|
image: getmeili/meilisearch:v1
|
||||||
env:
|
env:
|
||||||
MEILI_ENV: development # disable auth
|
MEILI_ENV: development # disable auth
|
||||||
ports:
|
ports:
|
||||||
@@ -113,7 +113,7 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- 6379:6379
|
- 6379:6379
|
||||||
minio:
|
minio:
|
||||||
image: bitnami/minio:2021.3.17
|
image: bitnamilegacy/minio:2021.3.17
|
||||||
env:
|
env:
|
||||||
MINIO_ACCESS_KEY: 123456
|
MINIO_ACCESS_KEY: 123456
|
||||||
MINIO_SECRET_KEY: 12345678
|
MINIO_SECRET_KEY: 12345678
|
||||||
@@ -155,7 +155,7 @@ jobs:
|
|||||||
services:
|
services:
|
||||||
mysql:
|
mysql:
|
||||||
# the bitnami mysql image has more options than the official one, it's easier to customize
|
# the bitnami mysql image has more options than the official one, it's easier to customize
|
||||||
image: bitnami/mysql:8.0
|
image: bitnamilegacy/mysql:8.0
|
||||||
env:
|
env:
|
||||||
ALLOW_EMPTY_PASSWORD: true
|
ALLOW_EMPTY_PASSWORD: true
|
||||||
MYSQL_DATABASE: testgitea
|
MYSQL_DATABASE: testgitea
|
||||||
@@ -202,12 +202,10 @@ jobs:
|
|||||||
test-mssql:
|
test-mssql:
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
# specifying the version of ubuntu in use as mssql fails on newer kernels
|
runs-on: ubuntu-latest
|
||||||
# pending resolution from vendor
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
services:
|
services:
|
||||||
mssql:
|
mssql:
|
||||||
image: mcr.microsoft.com/mssql/server:2017-latest
|
image: mcr.microsoft.com/mssql/server:2019-latest
|
||||||
env:
|
env:
|
||||||
ACCEPT_EULA: Y
|
ACCEPT_EULA: Y
|
||||||
MSSQL_PID: Standard
|
MSSQL_PID: Standard
|
||||||
|
|||||||
13
.github/workflows/pull-e2e-tests.yml
vendored
13
.github/workflows/pull-e2e-tests.yml
vendored
@@ -12,7 +12,9 @@ jobs:
|
|||||||
uses: ./.github/workflows/files-changed.yml
|
uses: ./.github/workflows/files-changed.yml
|
||||||
|
|
||||||
test-e2e:
|
test-e2e:
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true'
|
# the "test-e2e" won't pass, and it seems that there is no useful test, so skip
|
||||||
|
# if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
|
if: false
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -21,13 +23,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/setup-node@v4
|
- uses: pnpm/action-setup@v4
|
||||||
|
- uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 24
|
||||||
cache: npm
|
|
||||||
cache-dependency-path: package-lock.json
|
|
||||||
- run: make deps-frontend frontend deps-backend
|
- run: make deps-frontend frontend deps-backend
|
||||||
- run: npx playwright install --with-deps
|
- run: pnpm exec playwright install --with-deps
|
||||||
- run: make test-e2e-sqlite
|
- run: make test-e2e-sqlite
|
||||||
timeout-minutes: 40
|
timeout-minutes: 40
|
||||||
env:
|
env:
|
||||||
|
|||||||
43
.github/workflows/release-nightly.yml
vendored
43
.github/workflows/release-nightly.yml
vendored
@@ -20,11 +20,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/setup-node@v4
|
- uses: pnpm/action-setup@v4
|
||||||
|
- uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 24
|
||||||
cache: npm
|
|
||||||
cache-dependency-path: package-lock.json
|
|
||||||
- run: make deps-frontend deps-backend
|
- run: make deps-frontend deps-backend
|
||||||
# xgo build
|
# xgo build
|
||||||
- run: make release
|
- run: make release
|
||||||
@@ -59,6 +58,8 @@ jobs:
|
|||||||
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
||||||
nightly-docker-rootful:
|
nightly-docker-rootful:
|
||||||
runs-on: namespace-profile-gitea-release-docker
|
runs-on: namespace-profile-gitea-release-docker
|
||||||
|
permissions:
|
||||||
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
@@ -73,11 +74,6 @@ jobs:
|
|||||||
- name: Get cleaned branch name
|
- name: Get cleaned branch name
|
||||||
id: clean_name
|
id: clean_name
|
||||||
run: |
|
run: |
|
||||||
# if main then say nightly otherwise cleanup name
|
|
||||||
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
|
|
||||||
echo "branch=nightly" >> "$GITHUB_OUTPUT"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
|
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
|
||||||
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
|
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
@@ -85,17 +81,27 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to GHCR using PAT
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: fetch go modules
|
- name: fetch go modules
|
||||||
run: make vendor
|
run: make vendor
|
||||||
- name: build rootful docker image
|
- name: build rootful docker image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
push: true
|
push: true
|
||||||
tags: gitea/gitea:${{ steps.clean_name.outputs.branch }}
|
tags: |-
|
||||||
|
gitea/gitea:${{ steps.clean_name.outputs.branch }}
|
||||||
|
ghcr.io/go-gitea/gitea:${{ steps.clean_name.outputs.branch }}
|
||||||
nightly-docker-rootless:
|
nightly-docker-rootless:
|
||||||
runs-on: namespace-profile-gitea-release-docker
|
runs-on: namespace-profile-gitea-release-docker
|
||||||
|
permissions:
|
||||||
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
@@ -110,11 +116,6 @@ jobs:
|
|||||||
- name: Get cleaned branch name
|
- name: Get cleaned branch name
|
||||||
id: clean_name
|
id: clean_name
|
||||||
run: |
|
run: |
|
||||||
# if main then say nightly otherwise cleanup name
|
|
||||||
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
|
|
||||||
echo "branch=nightly" >> "$GITHUB_OUTPUT"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
|
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
|
||||||
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
|
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
@@ -122,6 +123,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to GHCR using PAT
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: fetch go modules
|
- name: fetch go modules
|
||||||
run: make vendor
|
run: make vendor
|
||||||
- name: build rootless docker image
|
- name: build rootless docker image
|
||||||
@@ -131,4 +138,6 @@ jobs:
|
|||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
file: Dockerfile.rootless
|
file: Dockerfile.rootless
|
||||||
tags: gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
|
tags: |-
|
||||||
|
gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
|
||||||
|
ghcr.io/go-gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
|
||||||
|
|||||||
35
.github/workflows/release-tag-rc.yml
vendored
35
.github/workflows/release-tag-rc.yml
vendored
@@ -21,11 +21,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/setup-node@v4
|
- uses: pnpm/action-setup@v4
|
||||||
|
- uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 24
|
||||||
cache: npm
|
|
||||||
cache-dependency-path: package-lock.json
|
|
||||||
- run: make deps-frontend deps-backend
|
- run: make deps-frontend deps-backend
|
||||||
# xgo build
|
# xgo build
|
||||||
- run: make release
|
- run: make release
|
||||||
@@ -69,6 +68,8 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
docker-rootful:
|
docker-rootful:
|
||||||
runs-on: namespace-profile-gitea-release-docker
|
runs-on: namespace-profile-gitea-release-docker
|
||||||
|
permissions:
|
||||||
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
@@ -79,7 +80,9 @@ jobs:
|
|||||||
- uses: docker/metadata-action@v5
|
- uses: docker/metadata-action@v5
|
||||||
id: meta
|
id: meta
|
||||||
with:
|
with:
|
||||||
images: gitea/gitea
|
images: |-
|
||||||
|
gitea/gitea
|
||||||
|
ghcr.io/go-gitea/gitea
|
||||||
flavor: |
|
flavor: |
|
||||||
latest=false
|
latest=false
|
||||||
# 1.2.3-rc0
|
# 1.2.3-rc0
|
||||||
@@ -90,16 +93,24 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to GHCR using PAT
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: build rootful docker image
|
- name: build rootful docker image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
docker-rootless:
|
docker-rootless:
|
||||||
runs-on: namespace-profile-gitea-release-docker
|
runs-on: namespace-profile-gitea-release-docker
|
||||||
|
permissions:
|
||||||
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
@@ -110,7 +121,9 @@ jobs:
|
|||||||
- uses: docker/metadata-action@v5
|
- uses: docker/metadata-action@v5
|
||||||
id: meta
|
id: meta
|
||||||
with:
|
with:
|
||||||
images: gitea/gitea
|
images: |-
|
||||||
|
gitea/gitea
|
||||||
|
ghcr.io/go-gitea/gitea
|
||||||
# each tag below will have the suffix of -rootless
|
# each tag below will have the suffix of -rootless
|
||||||
flavor: |
|
flavor: |
|
||||||
latest=false
|
latest=false
|
||||||
@@ -123,11 +136,17 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to GHCR using PAT
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: build rootless docker image
|
- name: build rootless docker image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
push: true
|
push: true
|
||||||
file: Dockerfile.rootless
|
file: Dockerfile.rootless
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
|||||||
39
.github/workflows/release-tag-version.yml
vendored
39
.github/workflows/release-tag-version.yml
vendored
@@ -14,6 +14,8 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
binary:
|
binary:
|
||||||
runs-on: namespace-profile-gitea-release-binary
|
runs-on: namespace-profile-gitea-release-binary
|
||||||
|
permissions:
|
||||||
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
@@ -23,11 +25,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/setup-node@v4
|
- uses: pnpm/action-setup@v4
|
||||||
|
- uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 24
|
||||||
cache: npm
|
|
||||||
cache-dependency-path: package-lock.json
|
|
||||||
- run: make deps-frontend deps-backend
|
- run: make deps-frontend deps-backend
|
||||||
# xgo build
|
# xgo build
|
||||||
- run: make release
|
- run: make release
|
||||||
@@ -71,6 +72,8 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
docker-rootful:
|
docker-rootful:
|
||||||
runs-on: namespace-profile-gitea-release-docker
|
runs-on: namespace-profile-gitea-release-docker
|
||||||
|
permissions:
|
||||||
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
@@ -81,26 +84,34 @@ jobs:
|
|||||||
- uses: docker/metadata-action@v5
|
- uses: docker/metadata-action@v5
|
||||||
id: meta
|
id: meta
|
||||||
with:
|
with:
|
||||||
images: gitea/gitea
|
images: |-
|
||||||
|
gitea/gitea
|
||||||
|
ghcr.io/go-gitea/gitea
|
||||||
# this will generate tags in the following format:
|
# this will generate tags in the following format:
|
||||||
# latest
|
# latest
|
||||||
# 1
|
# 1
|
||||||
# 1.2
|
# 1.2
|
||||||
# 1.2.3
|
# 1.2.3
|
||||||
tags: |
|
tags: |
|
||||||
|
type=semver,pattern={{version}}
|
||||||
type=semver,pattern={{major}}
|
type=semver,pattern={{major}}
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
type=semver,pattern={{version}}
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to GHCR using PAT
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: build rootful docker image
|
- name: build rootful docker image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
@@ -116,7 +127,9 @@ jobs:
|
|||||||
- uses: docker/metadata-action@v5
|
- uses: docker/metadata-action@v5
|
||||||
id: meta
|
id: meta
|
||||||
with:
|
with:
|
||||||
images: gitea/gitea
|
images: |-
|
||||||
|
gitea/gitea
|
||||||
|
ghcr.io/go-gitea/gitea
|
||||||
# each tag below will have the suffix of -rootless
|
# each tag below will have the suffix of -rootless
|
||||||
flavor: |
|
flavor: |
|
||||||
suffix=-rootless,onlatest=true
|
suffix=-rootless,onlatest=true
|
||||||
@@ -126,19 +139,25 @@ jobs:
|
|||||||
# 1.2
|
# 1.2
|
||||||
# 1.2.3
|
# 1.2.3
|
||||||
tags: |
|
tags: |
|
||||||
|
type=semver,pattern={{version}}
|
||||||
type=semver,pattern={{major}}
|
type=semver,pattern={{major}}
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
type=semver,pattern={{version}}
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to GHCR using PAT
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: build rootless docker image
|
- name: build rootless docker image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
push: true
|
push: true
|
||||||
file: Dockerfile.rootless
|
file: Dockerfile.rootless
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
|||||||
45
.gitignore
vendored
45
.gitignore
vendored
@@ -9,6 +9,11 @@ _test
|
|||||||
|
|
||||||
# IntelliJ
|
# IntelliJ
|
||||||
.idea
|
.idea
|
||||||
|
.run
|
||||||
|
|
||||||
|
# IntelliJ Gateway
|
||||||
|
.uuid
|
||||||
|
|
||||||
# Goland's output filename can not be set manually
|
# Goland's output filename can not be set manually
|
||||||
/go_build_*
|
/go_build_*
|
||||||
/gitea_*
|
/gitea_*
|
||||||
@@ -17,6 +22,9 @@ _test
|
|||||||
.vscode
|
.vscode
|
||||||
__debug_bin*
|
__debug_bin*
|
||||||
|
|
||||||
|
# Visual Studio
|
||||||
|
/.vs/
|
||||||
|
|
||||||
*.cgo1.go
|
*.cgo1.go
|
||||||
*.cgo2.c
|
*.cgo2.c
|
||||||
_cgo_defun.c
|
_cgo_defun.c
|
||||||
@@ -34,14 +42,10 @@ _testmain.go
|
|||||||
coverage.all
|
coverage.all
|
||||||
cpu.out
|
cpu.out
|
||||||
|
|
||||||
/modules/migration/bindata.go
|
/modules/migration/bindata.*
|
||||||
/modules/migration/bindata.go.hash
|
/modules/options/bindata.*
|
||||||
/modules/options/bindata.go
|
/modules/public/bindata.*
|
||||||
/modules/options/bindata.go.hash
|
/modules/templates/bindata.*
|
||||||
/modules/public/bindata.go
|
|
||||||
/modules/public/bindata.go.hash
|
|
||||||
/modules/templates/bindata.go
|
|
||||||
/modules/templates/bindata.go.hash
|
|
||||||
|
|
||||||
*.db
|
*.db
|
||||||
*.log
|
*.log
|
||||||
@@ -74,23 +78,12 @@ cpu.out
|
|||||||
/yarn.lock
|
/yarn.lock
|
||||||
/yarn-error.log
|
/yarn-error.log
|
||||||
/npm-debug.log*
|
/npm-debug.log*
|
||||||
|
/.pnpm-store
|
||||||
/public/assets/js
|
/public/assets/js
|
||||||
/public/assets/css
|
/public/assets/css
|
||||||
/public/assets/fonts
|
/public/assets/fonts
|
||||||
/public/assets/licenses.txt
|
/public/assets/licenses.txt
|
||||||
/vendor
|
/vendor
|
||||||
/web_src/fomantic/node_modules
|
|
||||||
/web_src/fomantic/build/*
|
|
||||||
!/web_src/fomantic/build/semantic.js
|
|
||||||
!/web_src/fomantic/build/semantic.css
|
|
||||||
!/web_src/fomantic/build/themes
|
|
||||||
/web_src/fomantic/build/themes/*
|
|
||||||
!/web_src/fomantic/build/themes/default
|
|
||||||
/web_src/fomantic/build/themes/default/assets/*
|
|
||||||
!/web_src/fomantic/build/themes/default/assets/fonts
|
|
||||||
/web_src/fomantic/build/themes/default/assets/fonts/*
|
|
||||||
!/web_src/fomantic/build/themes/default/assets/fonts/icons.woff2
|
|
||||||
!/web_src/fomantic/build/themes/default/assets/fonts/outline-icons.woff2
|
|
||||||
/VERSION
|
/VERSION
|
||||||
/.air
|
/.air
|
||||||
/.go-licenses
|
/.go-licenses
|
||||||
@@ -117,3 +110,15 @@ prime/
|
|||||||
|
|
||||||
# Manpage
|
# Manpage
|
||||||
/man
|
/man
|
||||||
|
|
||||||
|
# Ignore AI/LLM instruction files
|
||||||
|
/.claude/
|
||||||
|
/.cursorrules
|
||||||
|
/.cursor/
|
||||||
|
/.goosehints
|
||||||
|
/.windsurfrules
|
||||||
|
/.github/copilot-instructions.md
|
||||||
|
/AGENT.md
|
||||||
|
/CLAUDE.md
|
||||||
|
/llms.txt
|
||||||
|
|
||||||
|
|||||||
285
.golangci.yml
285
.golangci.yml
@@ -1,7 +1,9 @@
|
|||||||
|
version: "2"
|
||||||
|
output:
|
||||||
|
sort-order:
|
||||||
|
- file
|
||||||
linters:
|
linters:
|
||||||
enable-all: false
|
default: none
|
||||||
disable-all: true
|
|
||||||
fast: false
|
|
||||||
enable:
|
enable:
|
||||||
- bidichk
|
- bidichk
|
||||||
- depguard
|
- depguard
|
||||||
@@ -9,139 +11,172 @@ linters:
|
|||||||
- errcheck
|
- errcheck
|
||||||
- forbidigo
|
- forbidigo
|
||||||
- gocritic
|
- gocritic
|
||||||
- gofmt
|
|
||||||
- gofumpt
|
|
||||||
- gosimple
|
|
||||||
- govet
|
- govet
|
||||||
- ineffassign
|
- ineffassign
|
||||||
|
- mirror
|
||||||
- nakedret
|
- nakedret
|
||||||
- nolintlint
|
- nolintlint
|
||||||
|
- perfsprint
|
||||||
- revive
|
- revive
|
||||||
- staticcheck
|
- staticcheck
|
||||||
- stylecheck
|
|
||||||
- tenv
|
|
||||||
- testifylint
|
- testifylint
|
||||||
- typecheck
|
|
||||||
- unconvert
|
- unconvert
|
||||||
- unused
|
|
||||||
- unparam
|
- unparam
|
||||||
|
- unused
|
||||||
|
- usestdlibvars
|
||||||
|
- usetesting
|
||||||
- wastedassign
|
- wastedassign
|
||||||
|
settings:
|
||||||
run:
|
depguard:
|
||||||
timeout: 10m
|
rules:
|
||||||
|
main:
|
||||||
output:
|
deny:
|
||||||
sort-results: true
|
- pkg: encoding/json
|
||||||
sort-order: [file]
|
desc: use gitea's modules/json instead of encoding/json
|
||||||
show-stats: true
|
- pkg: github.com/unknwon/com
|
||||||
|
desc: use gitea's util and replacements
|
||||||
linters-settings:
|
- pkg: io/ioutil
|
||||||
testifylint:
|
desc: use os or io instead
|
||||||
disable:
|
- pkg: golang.org/x/exp
|
||||||
- go-require
|
desc: it's experimental and unreliable
|
||||||
- require-error
|
- pkg: code.gitea.io/gitea/modules/git/internal
|
||||||
stylecheck:
|
desc: do not use the internal package, use AddXxx function instead
|
||||||
checks: ["all", "-ST1005", "-ST1003"]
|
- pkg: gopkg.in/ini.v1
|
||||||
nakedret:
|
desc: do not use the ini package, use gitea's config system instead
|
||||||
max-func-lines: 0
|
- pkg: gitea.com/go-chi/cache
|
||||||
gocritic:
|
desc: do not use the go-chi cache package, use gitea's cache system
|
||||||
disabled-checks:
|
nolintlint:
|
||||||
- ifElseChain
|
allow-unused: false
|
||||||
- singleCaseSwitch # Every time this occurred in the code, there was no other way.
|
require-explanation: true
|
||||||
revive:
|
require-specific: true
|
||||||
severity: error
|
gocritic:
|
||||||
|
enabled-checks:
|
||||||
|
- equalFold
|
||||||
|
disabled-checks:
|
||||||
|
- ifElseChain
|
||||||
|
- singleCaseSwitch # Every time this occurred in the code, there was no other way.
|
||||||
|
revive:
|
||||||
|
severity: error
|
||||||
|
rules:
|
||||||
|
- name: atomic
|
||||||
|
- name: bare-return
|
||||||
|
- name: blank-imports
|
||||||
|
- name: constant-logical-expr
|
||||||
|
- name: context-as-argument
|
||||||
|
- name: context-keys-type
|
||||||
|
- name: dot-imports
|
||||||
|
- name: duplicated-imports
|
||||||
|
- name: empty-lines
|
||||||
|
- name: error-naming
|
||||||
|
- name: error-return
|
||||||
|
- name: error-strings
|
||||||
|
- name: errorf
|
||||||
|
- name: exported
|
||||||
|
- name: identical-branches
|
||||||
|
- name: if-return
|
||||||
|
- name: increment-decrement
|
||||||
|
- name: indent-error-flow
|
||||||
|
- name: modifies-value-receiver
|
||||||
|
- name: package-comments
|
||||||
|
- name: range
|
||||||
|
- name: receiver-naming
|
||||||
|
- name: redefines-builtin-id
|
||||||
|
- name: string-of-int
|
||||||
|
- name: superfluous-else
|
||||||
|
- name: time-naming
|
||||||
|
- name: unconditional-recursion
|
||||||
|
- name: unexported-return
|
||||||
|
- name: unreachable-code
|
||||||
|
- name: var-declaration
|
||||||
|
- name: var-naming
|
||||||
|
arguments:
|
||||||
|
- [] # AllowList - do not remove as args for the rule are positional and won't work without lists first
|
||||||
|
- [] # DenyList
|
||||||
|
- - skip-package-name-checks: true # supress errors from underscore in migration packages
|
||||||
|
staticcheck:
|
||||||
|
checks:
|
||||||
|
- all
|
||||||
|
- -ST1003
|
||||||
|
- -ST1005
|
||||||
|
- -QF1001
|
||||||
|
- -QF1006
|
||||||
|
- -QF1008
|
||||||
|
testifylint:
|
||||||
|
disable:
|
||||||
|
- go-require
|
||||||
|
- require-error
|
||||||
|
usetesting:
|
||||||
|
os-temp-dir: true
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
presets:
|
||||||
|
- comments
|
||||||
|
- common-false-positives
|
||||||
|
- legacy
|
||||||
|
- std-error-handling
|
||||||
rules:
|
rules:
|
||||||
- name: atomic
|
- linters:
|
||||||
- name: bare-return
|
- dupl
|
||||||
- name: blank-imports
|
- errcheck
|
||||||
- name: constant-logical-expr
|
- gocyclo
|
||||||
- name: context-as-argument
|
- gosec
|
||||||
- name: context-keys-type
|
- staticcheck
|
||||||
- name: dot-imports
|
- unparam
|
||||||
- name: duplicated-imports
|
path: _test\.go
|
||||||
- name: empty-lines
|
- linters:
|
||||||
- name: error-naming
|
- dupl
|
||||||
- name: error-return
|
- errcheck
|
||||||
- name: error-strings
|
- gocyclo
|
||||||
- name: errorf
|
- gosec
|
||||||
- name: exported
|
path: models/migrations/v
|
||||||
- name: identical-branches
|
- linters:
|
||||||
- name: if-return
|
- forbidigo
|
||||||
- name: increment-decrement
|
path: cmd
|
||||||
- name: indent-error-flow
|
- linters:
|
||||||
- name: modifies-value-receiver
|
- dupl
|
||||||
- name: package-comments
|
text: (?i)webhook
|
||||||
- name: range
|
- linters:
|
||||||
- name: receiver-naming
|
- gocritic
|
||||||
- name: redefines-builtin-id
|
text: (?i)`ID' should not be capitalized
|
||||||
- name: string-of-int
|
- linters:
|
||||||
- name: superfluous-else
|
- deadcode
|
||||||
- name: time-naming
|
- unused
|
||||||
- name: unconditional-recursion
|
text: (?i)swagger
|
||||||
- name: unexported-return
|
- linters:
|
||||||
- name: unreachable-code
|
- staticcheck
|
||||||
- name: var-declaration
|
text: (?i)argument x is overwritten before first use
|
||||||
- name: var-naming
|
- linters:
|
||||||
gofumpt:
|
- gocritic
|
||||||
extra-rules: true
|
text: '(?i)commentFormatting: put a space between `//` and comment text'
|
||||||
depguard:
|
- linters:
|
||||||
rules:
|
- gocritic
|
||||||
main:
|
text: '(?i)exitAfterDefer:'
|
||||||
deny:
|
paths:
|
||||||
- pkg: encoding/json
|
- node_modules
|
||||||
desc: use gitea's modules/json instead of encoding/json
|
- public
|
||||||
- pkg: github.com/unknwon/com
|
- web_src
|
||||||
desc: use gitea's util and replacements
|
- third_party$
|
||||||
- pkg: io/ioutil
|
- builtin$
|
||||||
desc: use os or io instead
|
- examples$
|
||||||
- pkg: golang.org/x/exp
|
|
||||||
desc: it's experimental and unreliable
|
|
||||||
- pkg: code.gitea.io/gitea/modules/git/internal
|
|
||||||
desc: do not use the internal package, use AddXxx function instead
|
|
||||||
- pkg: gopkg.in/ini.v1
|
|
||||||
desc: do not use the ini package, use gitea's config system instead
|
|
||||||
- pkg: gitea.com/go-chi/cache
|
|
||||||
desc: do not use the go-chi cache package, use gitea's cache system
|
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
max-issues-per-linter: 0
|
max-issues-per-linter: 0
|
||||||
max-same-issues: 0
|
max-same-issues: 0
|
||||||
exclude-dirs: [node_modules, public, web_src]
|
formatters:
|
||||||
exclude-case-sensitive: true
|
enable:
|
||||||
exclude-rules:
|
- gofmt
|
||||||
- path: _test\.go
|
- gofumpt
|
||||||
linters:
|
settings:
|
||||||
- gocyclo
|
gofumpt:
|
||||||
- errcheck
|
extra-rules: true
|
||||||
- dupl
|
exclusions:
|
||||||
- gosec
|
generated: lax
|
||||||
- unparam
|
paths:
|
||||||
- staticcheck
|
- node_modules
|
||||||
- path: models/migrations/v
|
- public
|
||||||
linters:
|
- web_src
|
||||||
- gocyclo
|
- third_party$
|
||||||
- errcheck
|
- builtin$
|
||||||
- dupl
|
- examples$
|
||||||
- gosec
|
|
||||||
- path: cmd
|
run:
|
||||||
linters:
|
timeout: 10m
|
||||||
- forbidigo
|
|
||||||
- text: "webhook"
|
|
||||||
linters:
|
|
||||||
- dupl
|
|
||||||
- text: "`ID' should not be capitalized"
|
|
||||||
linters:
|
|
||||||
- gocritic
|
|
||||||
- text: "swagger"
|
|
||||||
linters:
|
|
||||||
- unused
|
|
||||||
- deadcode
|
|
||||||
- text: "argument x is overwritten before first use"
|
|
||||||
linters:
|
|
||||||
- staticcheck
|
|
||||||
- text: "commentFormatting: put a space between `//` and comment text"
|
|
||||||
linters:
|
|
||||||
- gocritic
|
|
||||||
- text: "exitAfterDefer:"
|
|
||||||
linters:
|
|
||||||
- gocritic
|
|
||||||
|
|||||||
3
.ignore
3
.ignore
@@ -1,9 +1,6 @@
|
|||||||
*.min.css
|
*.min.css
|
||||||
*.min.js
|
*.min.js
|
||||||
/assets/*.json
|
/assets/*.json
|
||||||
/modules/options/bindata.go
|
|
||||||
/modules/public/bindata.go
|
|
||||||
/modules/templates/bindata.go
|
|
||||||
/options/gitignore
|
/options/gitignore
|
||||||
/options/license
|
/options/license
|
||||||
/public/assets
|
/public/assets
|
||||||
|
|||||||
2
.mailmap
Normal file
2
.mailmap
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Unknwon <u@gogs.io> <joe2010xtmf@163.com>
|
||||||
|
Unknwon <u@gogs.io> 无闻 <u@gogs.io>
|
||||||
5
.npmrc
5
.npmrc
@@ -1,6 +1,7 @@
|
|||||||
audit=false
|
audit=false
|
||||||
fund=false
|
fund=false
|
||||||
update-notifier=false
|
update-notifier=false
|
||||||
package-lock=true
|
|
||||||
save-exact=true
|
save-exact=true
|
||||||
lockfile-version=3
|
auto-install-peers=true
|
||||||
|
dedupe-peer-dependents=false
|
||||||
|
enable-pre-post-scripts=true
|
||||||
|
|||||||
1373
CHANGELOG.md
1373
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -30,7 +30,7 @@ These are the values to which people in the Gitea community should aspire.
|
|||||||
- **Be constructive.**
|
- **Be constructive.**
|
||||||
- Avoid derailing: stay on topic; if you want to talk about something else, start a new conversation.
|
- Avoid derailing: stay on topic; if you want to talk about something else, start a new conversation.
|
||||||
- Avoid unconstructive criticism: don't merely decry the current state of affairs; offer—or at least solicit—suggestions as to how things may be improved.
|
- Avoid unconstructive criticism: don't merely decry the current state of affairs; offer—or at least solicit—suggestions as to how things may be improved.
|
||||||
- Avoid snarking (pithy, unproductive, sniping comments)
|
- Avoid snarking (pithy, unproductive, sniping comments).
|
||||||
- Avoid discussing potentially offensive or sensitive issues; this all too often leads to unnecessary conflict.
|
- Avoid discussing potentially offensive or sensitive issues; this all too often leads to unnecessary conflict.
|
||||||
- Avoid microaggressions (brief and commonplace verbal, behavioral and environmental indignities that communicate hostile, derogatory or negative slights and insults to a person or group).
|
- Avoid microaggressions (brief and commonplace verbal, behavioral and environmental indignities that communicate hostile, derogatory or negative slights and insults to a person or group).
|
||||||
- **Be responsible.**
|
- **Be responsible.**
|
||||||
@@ -42,7 +42,7 @@ People are complicated. You should expect to be misunderstood and to misundersta
|
|||||||
|
|
||||||
### Our Pledge
|
### Our Pledge
|
||||||
|
|
||||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
### Our Standards
|
### Our Standards
|
||||||
|
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ Here's how to run the test suite:
|
|||||||
|
|
||||||
## Translation
|
## Translation
|
||||||
|
|
||||||
All translation work happens on [Crowdin](https://crowdin.com/project/gitea).
|
All translation work happens on [Crowdin](https://translate.gitea.com).
|
||||||
The only translation that is maintained in this repository is [the English translation](https://github.com/go-gitea/gitea/blob/main/options/locale/locale_en-US.ini).
|
The only translation that is maintained in this repository is [the English translation](https://github.com/go-gitea/gitea/blob/main/options/locale/locale_en-US.ini).
|
||||||
It is synced regularly with Crowdin. \
|
It is synced regularly with Crowdin. \
|
||||||
Other locales on main branch **should not** be updated manually as they will be overwritten with each sync. \
|
Other locales on main branch **should not** be updated manually as they will be overwritten with each sync. \
|
||||||
@@ -591,7 +591,7 @@ be reviewed by two maintainers and must pass the automatic tests.
|
|||||||
## Releasing Gitea
|
## Releasing Gitea
|
||||||
|
|
||||||
- Let $vmaj, $vmin and $vpat be Major, Minor and Patch version numbers, $vpat should be rc1, rc2, 0, 1, ...... $vmaj.$vmin will be kept the same as milestones on github or gitea in future.
|
- Let $vmaj, $vmin and $vpat be Major, Minor and Patch version numbers, $vpat should be rc1, rc2, 0, 1, ...... $vmaj.$vmin will be kept the same as milestones on github or gitea in future.
|
||||||
- Before releasing, confirm all the version's milestone issues or PRs has been resolved. Then discuss the release on Discord channel #maintainers and get agreed with almost all the owners and mergers. Or you can declare the version and if nobody against in about serval hours.
|
- Before releasing, confirm all the version's milestone issues or PRs has been resolved. Then discuss the release on Discord channel #maintainers and get agreed with almost all the owners and mergers. Or you can declare the version and if nobody is against it in about several hours.
|
||||||
- If this is a big version first you have to create PR for changelog on branch `main` with PRs with label `changelog` and after it has been merged do following steps:
|
- If this is a big version first you have to create PR for changelog on branch `main` with PRs with label `changelog` and after it has been merged do following steps:
|
||||||
- Create `-dev` tag as `git tag -s -F release.notes v$vmaj.$vmin.0-dev` and push the tag as `git push origin v$vmaj.$vmin.0-dev`.
|
- Create `-dev` tag as `git tag -s -F release.notes v$vmaj.$vmin.0-dev` and push the tag as `git push origin v$vmaj.$vmin.0-dev`.
|
||||||
- When CI has finished building tag then you have to create a new branch named `release/v$vmaj.$vmin`
|
- When CI has finished building tag then you have to create a new branch named `release/v$vmaj.$vmin`
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Build stage
|
# Build stage
|
||||||
FROM docker.io/library/golang:1.23-alpine3.20 AS build-env
|
FROM docker.io/library/golang:1.25-alpine3.22 AS build-env
|
||||||
|
|
||||||
ARG GOPROXY
|
ARG GOPROXY
|
||||||
ENV GOPROXY=${GOPROXY:-direct}
|
ENV GOPROXY=${GOPROXY:-direct}
|
||||||
@@ -15,6 +15,7 @@ RUN apk --no-cache add \
|
|||||||
git \
|
git \
|
||||||
nodejs \
|
nodejs \
|
||||||
npm \
|
npm \
|
||||||
|
&& npm install -g pnpm@10 \
|
||||||
&& rm -rf /var/cache/apk/*
|
&& rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
# Setup repo
|
# Setup repo
|
||||||
@@ -39,9 +40,8 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
|
|||||||
/tmp/local/etc/s6/.s6-svscan/* \
|
/tmp/local/etc/s6/.s6-svscan/* \
|
||||||
/go/src/code.gitea.io/gitea/gitea \
|
/go/src/code.gitea.io/gitea/gitea \
|
||||||
/go/src/code.gitea.io/gitea/environment-to-ini
|
/go/src/code.gitea.io/gitea/environment-to-ini
|
||||||
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
|
|
||||||
|
|
||||||
FROM docker.io/library/alpine:3.20
|
FROM docker.io/library/alpine:3.22
|
||||||
LABEL maintainer="maintainers@gitea.io"
|
LABEL maintainer="maintainers@gitea.io"
|
||||||
|
|
||||||
EXPOSE 22 3000
|
EXPOSE 22 3000
|
||||||
@@ -78,9 +78,8 @@ ENV GITEA_CUSTOM=/data/gitea
|
|||||||
VOLUME ["/data"]
|
VOLUME ["/data"]
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/entrypoint"]
|
ENTRYPOINT ["/usr/bin/entrypoint"]
|
||||||
CMD ["/bin/s6-svscan", "/etc/s6"]
|
CMD ["/usr/bin/s6-svscan", "/etc/s6"]
|
||||||
|
|
||||||
COPY --from=build-env /tmp/local /
|
COPY --from=build-env /tmp/local /
|
||||||
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
|
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
|
||||||
COPY --from=build-env /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
|
COPY --from=build-env /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
|
||||||
COPY --from=build-env /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Build stage
|
# Build stage
|
||||||
FROM docker.io/library/golang:1.23-alpine3.20 AS build-env
|
FROM docker.io/library/golang:1.25-alpine3.22 AS build-env
|
||||||
|
|
||||||
ARG GOPROXY
|
ARG GOPROXY
|
||||||
ENV GOPROXY=${GOPROXY:-direct}
|
ENV GOPROXY=${GOPROXY:-direct}
|
||||||
@@ -15,6 +15,7 @@ RUN apk --no-cache add \
|
|||||||
git \
|
git \
|
||||||
nodejs \
|
nodejs \
|
||||||
npm \
|
npm \
|
||||||
|
&& npm install -g pnpm@10 \
|
||||||
&& rm -rf /var/cache/apk/*
|
&& rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
# Setup repo
|
# Setup repo
|
||||||
@@ -37,9 +38,8 @@ RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
|
|||||||
/tmp/local/usr/local/bin/gitea \
|
/tmp/local/usr/local/bin/gitea \
|
||||||
/go/src/code.gitea.io/gitea/gitea \
|
/go/src/code.gitea.io/gitea/gitea \
|
||||||
/go/src/code.gitea.io/gitea/environment-to-ini
|
/go/src/code.gitea.io/gitea/environment-to-ini
|
||||||
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
|
|
||||||
|
|
||||||
FROM docker.io/library/alpine:3.20
|
FROM docker.io/library/alpine:3.22
|
||||||
LABEL maintainer="maintainers@gitea.io"
|
LABEL maintainer="maintainers@gitea.io"
|
||||||
|
|
||||||
EXPOSE 2222 3000
|
EXPOSE 2222 3000
|
||||||
@@ -52,6 +52,7 @@ RUN apk --no-cache add \
|
|||||||
git \
|
git \
|
||||||
curl \
|
curl \
|
||||||
gnupg \
|
gnupg \
|
||||||
|
openssh-keygen \
|
||||||
&& rm -rf /var/cache/apk/*
|
&& rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
RUN addgroup \
|
RUN addgroup \
|
||||||
@@ -71,7 +72,6 @@ RUN chown git:git /var/lib/gitea /etc/gitea
|
|||||||
COPY --from=build-env /tmp/local /
|
COPY --from=build-env /tmp/local /
|
||||||
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
|
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
|
||||||
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
|
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
|
||||||
COPY --from=build-env /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh
|
|
||||||
|
|
||||||
# git:git
|
# git:git
|
||||||
USER 1000:1000
|
USER 1000:1000
|
||||||
|
|||||||
@@ -31,15 +31,12 @@ Gary Kim <gary@garykim.dev> (@gary-kim)
|
|||||||
Guillermo Prandi <gitea.maint@mailfilter.com.ar> (@guillep2k)
|
Guillermo Prandi <gitea.maint@mailfilter.com.ar> (@guillep2k)
|
||||||
Mura Li <typeless@ctli.io> (@typeless)
|
Mura Li <typeless@ctli.io> (@typeless)
|
||||||
6543 <6543@obermui.de> (@6543)
|
6543 <6543@obermui.de> (@6543)
|
||||||
jaqra <jaqra@hotmail.com> (@jaqra)
|
|
||||||
David Svantesson <davidsvantesson@gmail.com> (@davidsvantesson)
|
David Svantesson <davidsvantesson@gmail.com> (@davidsvantesson)
|
||||||
a1012112796 <1012112796@qq.com> (@a1012112796)
|
a1012112796 <1012112796@qq.com> (@a1012112796)
|
||||||
Karl Heinz Marbaise <kama@soebes.de> (@khmarbaise)
|
Karl Heinz Marbaise <kama@soebes.de> (@khmarbaise)
|
||||||
Norwin Roosen <git@nroo.de> (@noerw)
|
Norwin Roosen <git@nroo.de> (@noerw)
|
||||||
Kyle Dumont <kdumontnu@gmail.com> (@kdumontnu)
|
Kyle Dumont <kdumontnu@gmail.com> (@kdumontnu)
|
||||||
Patrick Schratz <patrick.schratz@gmail.com> (@pat-s)
|
|
||||||
Janis Estelmann <admin@oldschoolhack.me> (@KN4CK3R)
|
Janis Estelmann <admin@oldschoolhack.me> (@KN4CK3R)
|
||||||
Steven Kriegler <sk.bunsenbrenner@gmail.com> (@justusbunsi)
|
|
||||||
Jimmy Praet <jimmy.praet@telenet.be> (@jpraet)
|
Jimmy Praet <jimmy.praet@telenet.be> (@jpraet)
|
||||||
Leon Hofmeister <dev.lh@web.de> (@delvh)
|
Leon Hofmeister <dev.lh@web.de> (@delvh)
|
||||||
Wim <wim@42.be> (@42wim)
|
Wim <wim@42.be> (@42wim)
|
||||||
@@ -63,3 +60,7 @@ Yu Liu <1240335630@qq.com> (@HEREYUA)
|
|||||||
Kemal Zebari <kemalzebra@gmail.com> (@kemzeb)
|
Kemal Zebari <kemalzebra@gmail.com> (@kemzeb)
|
||||||
Rowan Bohde <rowan.bohde@gmail.com> (@bohde)
|
Rowan Bohde <rowan.bohde@gmail.com> (@bohde)
|
||||||
hiifong <i@hiif.ong> (@hiifong)
|
hiifong <i@hiif.ong> (@hiifong)
|
||||||
|
metiftikci <metiftikci@hotmail.com> (@metiftikci)
|
||||||
|
Christopher Homberger <christopher.homberger@web.de> (@ChristopherHX)
|
||||||
|
Tobias Balle-Petersen <tobiasbp@gmail.com> (@tobiasbp)
|
||||||
|
TheFox <thefox0x7@gmail.com> (@TheFox0x7)
|
||||||
|
|||||||
405
Makefile
405
Makefile
@@ -23,20 +23,21 @@ SHASUM ?= shasum -a 256
|
|||||||
HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes)
|
HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes)
|
||||||
COMMA := ,
|
COMMA := ,
|
||||||
|
|
||||||
XGO_VERSION := go-1.23.x
|
XGO_VERSION := go-1.25.x
|
||||||
|
|
||||||
AIR_PACKAGE ?= github.com/air-verse/air@v1
|
AIR_PACKAGE ?= github.com/air-verse/air@v1
|
||||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.7.0
|
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3
|
||||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0
|
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.9.1
|
||||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2
|
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.4.0
|
||||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11
|
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15
|
||||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.5.1
|
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.7.0
|
||||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0
|
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@717e3cb29becaaf00e56953556c6d80f8a01b286
|
||||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
||||||
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
|
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
|
||||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
|
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
|
||||||
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1
|
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1
|
||||||
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.15.3
|
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.20.0
|
||||||
|
GOPLS_MODERNIZE_PACKAGE ?= golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@v0.20.0
|
||||||
|
|
||||||
DOCKER_IMAGE ?= gitea/gitea
|
DOCKER_IMAGE ?= gitea/gitea
|
||||||
DOCKER_TAG ?= latest
|
DOCKER_TAG ?= latest
|
||||||
@@ -47,6 +48,17 @@ ifeq ($(HAS_GO), yes)
|
|||||||
CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS)
|
CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
CGO_ENABLED ?= 0
|
||||||
|
ifneq (,$(findstring sqlite,$(TAGS))$(findstring pam,$(TAGS)))
|
||||||
|
CGO_ENABLED = 1
|
||||||
|
endif
|
||||||
|
|
||||||
|
STATIC ?=
|
||||||
|
EXTLDFLAGS ?=
|
||||||
|
ifneq ($(STATIC),)
|
||||||
|
EXTLDFLAGS = -extldflags "-static"
|
||||||
|
endif
|
||||||
|
|
||||||
ifeq ($(GOOS),windows)
|
ifeq ($(GOOS),windows)
|
||||||
IS_WINDOWS := yes
|
IS_WINDOWS := yes
|
||||||
else ifeq ($(patsubst Windows%,Windows,$(OS)),Windows)
|
else ifeq ($(patsubst Windows%,Windows,$(OS)),Windows)
|
||||||
@@ -73,17 +85,26 @@ EXTRA_GOFLAGS ?=
|
|||||||
MAKE_VERSION := $(shell "$(MAKE)" -v | cat | head -n 1)
|
MAKE_VERSION := $(shell "$(MAKE)" -v | cat | head -n 1)
|
||||||
MAKE_EVIDENCE_DIR := .make_evidence
|
MAKE_EVIDENCE_DIR := .make_evidence
|
||||||
|
|
||||||
|
GOTESTFLAGS ?=
|
||||||
ifeq ($(RACE_ENABLED),true)
|
ifeq ($(RACE_ENABLED),true)
|
||||||
GOFLAGS += -race
|
GOFLAGS += -race
|
||||||
GOTESTFLAGS += -race
|
GOTESTFLAGS += -race
|
||||||
endif
|
endif
|
||||||
|
|
||||||
STORED_VERSION_FILE := VERSION
|
STORED_VERSION_FILE := VERSION
|
||||||
HUGO_VERSION ?= 0.111.3
|
|
||||||
|
|
||||||
GITHUB_REF_TYPE ?= branch
|
GITHUB_REF_TYPE ?= branch
|
||||||
GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
|
GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
|
||||||
|
|
||||||
|
# Enable typescript support in Node.js before 22.18
|
||||||
|
# TODO: Remove this once we can raise the minimum Node.js version to 22.18 (alpine >= 3.23)
|
||||||
|
NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell node -v 2>/dev/null | cut -c2- | tr '.' ' '))
|
||||||
|
ifeq ($(shell test "$(NODE_VERSION)" -lt "022018000"; echo $$?),0)
|
||||||
|
NODE_VARS := NODE_OPTIONS="--experimental-strip-types"
|
||||||
|
else
|
||||||
|
NODE_VARS :=
|
||||||
|
endif
|
||||||
|
|
||||||
ifneq ($(GITHUB_REF_TYPE),branch)
|
ifneq ($(GITHUB_REF_TYPE),branch)
|
||||||
VERSION ?= $(subst v,,$(GITHUB_REF_NAME))
|
VERSION ?= $(subst v,,$(GITHUB_REF_NAME))
|
||||||
GITEA_VERSION ?= $(VERSION)
|
GITEA_VERSION ?= $(VERSION)
|
||||||
@@ -109,20 +130,17 @@ endif
|
|||||||
|
|
||||||
LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)"
|
LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)"
|
||||||
|
|
||||||
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
|
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64,linux/riscv64
|
||||||
|
|
||||||
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
|
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
|
||||||
MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...)
|
MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...)
|
||||||
|
|
||||||
FOMANTIC_WORK_DIR := web_src/fomantic
|
|
||||||
|
|
||||||
WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f)
|
WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f)
|
||||||
WEBPACK_CONFIGS := webpack.config.js tailwind.config.js
|
WEBPACK_CONFIGS := webpack.config.ts tailwind.config.ts
|
||||||
WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css
|
WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css
|
||||||
WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts
|
WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts
|
||||||
|
|
||||||
BINDATA_DEST := modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go
|
BINDATA_DEST_WILDCARD := modules/migration/bindata.* modules/public/bindata.* modules/options/bindata.* modules/templates/bindata.*
|
||||||
BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST))
|
|
||||||
|
|
||||||
GENERATED_GO_DEST := modules/charset/invisible_gen.go modules/charset/ambiguous_gen.go
|
GENERATED_GO_DEST := modules/charset/invisible_gen.go modules/charset/ambiguous_gen.go
|
||||||
|
|
||||||
@@ -139,25 +157,19 @@ TAGS_EVIDENCE := $(MAKE_EVIDENCE_DIR)/tags
|
|||||||
|
|
||||||
TEST_TAGS ?= $(TAGS_SPLIT) sqlite sqlite_unlock_notify
|
TEST_TAGS ?= $(TAGS_SPLIT) sqlite sqlite_unlock_notify
|
||||||
|
|
||||||
TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(FOMANTIC_WORK_DIR)/node_modules $(DIST) $(MAKE_EVIDENCE_DIR) $(AIR_TMP_DIR) $(GO_LICENSE_TMP_DIR)
|
TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(DIST) $(MAKE_EVIDENCE_DIR) $(AIR_TMP_DIR) $(GO_LICENSE_TMP_DIR)
|
||||||
|
|
||||||
GO_DIRS := build cmd models modules routers services tests
|
GO_DIRS := build cmd models modules routers services tests
|
||||||
WEB_DIRS := web_src/js web_src/css
|
WEB_DIRS := web_src/js web_src/css
|
||||||
|
|
||||||
ESLINT_FILES := web_src/js tools *.js *.ts tests/e2e
|
ESLINT_FILES := web_src/js tools *.ts tests/e2e
|
||||||
STYLELINT_FILES := web_src/css web_src/js/components/*.vue
|
STYLELINT_FILES := web_src/css web_src/js/components/*.vue
|
||||||
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) templates options/locale/locale_en-US.ini .github $(filter-out CHANGELOG.md, $(wildcard *.go *.js *.md *.yml *.yaml *.toml))
|
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) templates options/locale/locale_en-US.ini .github $(filter-out CHANGELOG.md, $(wildcard *.go *.md *.yml *.yaml *.toml)) $(filter-out tools/misspellings.csv, $(wildcard tools/*))
|
||||||
EDITORCONFIG_FILES := templates .github/workflows options/locale/locale_en-US.ini
|
EDITORCONFIG_FILES := templates .github/workflows options/locale/locale_en-US.ini
|
||||||
|
|
||||||
GO_SOURCES := $(wildcard *.go)
|
GO_SOURCES := $(wildcard *.go)
|
||||||
GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" ! -path modules/options/bindata.go ! -path modules/public/bindata.go ! -path modules/templates/bindata.go)
|
GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go")
|
||||||
GO_SOURCES += $(GENERATED_GO_DEST)
|
GO_SOURCES += $(GENERATED_GO_DEST)
|
||||||
GO_SOURCES_NO_BINDATA := $(GO_SOURCES)
|
|
||||||
|
|
||||||
ifeq ($(filter $(TAGS_SPLIT),bindata),bindata)
|
|
||||||
GO_SOURCES += $(BINDATA_DEST)
|
|
||||||
GENERATED_GO_DEST += $(BINDATA_DEST)
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Force installation of playwright dependencies by setting this flag
|
# Force installation of playwright dependencies by setting this flag
|
||||||
ifdef DEPS_PLAYWRIGHT
|
ifdef DEPS_PLAYWRIGHT
|
||||||
@@ -165,10 +177,8 @@ ifdef DEPS_PLAYWRIGHT
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
SWAGGER_SPEC := templates/swagger/v1_json.tmpl
|
SWAGGER_SPEC := templates/swagger/v1_json.tmpl
|
||||||
SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape}}/api/v1"|g
|
SWAGGER_SPEC_INPUT := templates/swagger/v1_input.json
|
||||||
SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape}}/api/v1"|"basePath": "/api/v1"|g
|
|
||||||
SWAGGER_EXCLUDE := code.gitea.io/sdk
|
SWAGGER_EXCLUDE := code.gitea.io/sdk
|
||||||
SWAGGER_NEWLINE_COMMAND := -e '$$a\'
|
|
||||||
|
|
||||||
TEST_MYSQL_HOST ?= mysql:3306
|
TEST_MYSQL_HOST ?= mysql:3306
|
||||||
TEST_MYSQL_DBNAME ?= testgitea
|
TEST_MYSQL_DBNAME ?= testgitea
|
||||||
@@ -189,67 +199,11 @@ TEST_MSSQL_PASSWORD ?= MwantsaSecurePassword1
|
|||||||
all: build
|
all: build
|
||||||
|
|
||||||
.PHONY: help
|
.PHONY: help
|
||||||
help:
|
help: Makefile ## print Makefile help information.
|
||||||
@echo "Make Routines:"
|
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m[TARGETS] default target: build\033[0m\n\n\033[35mTargets:\033[0m\n"} /^[0-9A-Za-z._-]+:.*?##/ { printf " \033[36m%-45s\033[0m %s\n", $$1, $$2 }' Makefile #$(MAKEFILE_LIST)
|
||||||
@echo " - \"\" equivalent to \"build\""
|
@printf " \033[36m%-46s\033[0m %s\n" "test-e2e[#TestSpecificName]" "test end to end using playwright"
|
||||||
@echo " - build build everything"
|
@printf " \033[36m%-46s\033[0m %s\n" "test[#TestSpecificName]" "run unit test"
|
||||||
@echo " - frontend build frontend files"
|
@printf " \033[36m%-46s\033[0m %s\n" "test-sqlite[#TestSpecificName]" "run integration test for sqlite"
|
||||||
@echo " - backend build backend files"
|
|
||||||
@echo " - watch watch everything and continuously rebuild"
|
|
||||||
@echo " - watch-frontend watch frontend files and continuously rebuild"
|
|
||||||
@echo " - watch-backend watch backend files and continuously rebuild"
|
|
||||||
@echo " - clean delete backend and integration files"
|
|
||||||
@echo " - clean-all delete backend, frontend and integration files"
|
|
||||||
@echo " - deps install dependencies"
|
|
||||||
@echo " - deps-frontend install frontend dependencies"
|
|
||||||
@echo " - deps-backend install backend dependencies"
|
|
||||||
@echo " - deps-tools install tool dependencies"
|
|
||||||
@echo " - deps-py install python dependencies"
|
|
||||||
@echo " - lint lint everything"
|
|
||||||
@echo " - lint-fix lint everything and fix issues"
|
|
||||||
@echo " - lint-actions lint action workflow files"
|
|
||||||
@echo " - lint-frontend lint frontend files"
|
|
||||||
@echo " - lint-frontend-fix lint frontend files and fix issues"
|
|
||||||
@echo " - lint-backend lint backend files"
|
|
||||||
@echo " - lint-backend-fix lint backend files and fix issues"
|
|
||||||
@echo " - lint-go lint go files"
|
|
||||||
@echo " - lint-go-fix lint go files and fix issues"
|
|
||||||
@echo " - lint-go-vet lint go files with vet"
|
|
||||||
@echo " - lint-go-gopls lint go files with gopls"
|
|
||||||
@echo " - lint-js lint js files"
|
|
||||||
@echo " - lint-js-fix lint js files and fix issues"
|
|
||||||
@echo " - lint-css lint css files"
|
|
||||||
@echo " - lint-css-fix lint css files and fix issues"
|
|
||||||
@echo " - lint-md lint markdown files"
|
|
||||||
@echo " - lint-swagger lint swagger files"
|
|
||||||
@echo " - lint-templates lint template files"
|
|
||||||
@echo " - lint-yaml lint yaml files"
|
|
||||||
@echo " - lint-spell lint spelling"
|
|
||||||
@echo " - lint-spell-fix lint spelling and fix issues"
|
|
||||||
@echo " - checks run various consistency checks"
|
|
||||||
@echo " - checks-frontend check frontend files"
|
|
||||||
@echo " - checks-backend check backend files"
|
|
||||||
@echo " - test test everything"
|
|
||||||
@echo " - test-frontend test frontend files"
|
|
||||||
@echo " - test-backend test backend files"
|
|
||||||
@echo " - test-e2e[\#TestSpecificName] test end to end using playwright"
|
|
||||||
@echo " - update update js and py dependencies"
|
|
||||||
@echo " - update-js update js dependencies"
|
|
||||||
@echo " - update-py update py dependencies"
|
|
||||||
@echo " - webpack build webpack files"
|
|
||||||
@echo " - svg build svg files"
|
|
||||||
@echo " - fomantic build fomantic files"
|
|
||||||
@echo " - generate run \"go generate\""
|
|
||||||
@echo " - fmt format the Go code"
|
|
||||||
@echo " - generate-license update license files"
|
|
||||||
@echo " - generate-gitignore update gitignore files"
|
|
||||||
@echo " - generate-manpage generate manpage"
|
|
||||||
@echo " - generate-swagger generate the swagger spec from code comments"
|
|
||||||
@echo " - swagger-validate check if the swagger spec is valid"
|
|
||||||
@echo " - go-licenses regenerate go licenses"
|
|
||||||
@echo " - tidy run go mod tidy"
|
|
||||||
@echo " - test[\#TestSpecificName] run unit test"
|
|
||||||
@echo " - test-sqlite[\#TestSpecificName] run integration test for sqlite"
|
|
||||||
|
|
||||||
.PHONY: go-check
|
.PHONY: go-check
|
||||||
go-check:
|
go-check:
|
||||||
@@ -272,20 +226,23 @@ git-check:
|
|||||||
node-check:
|
node-check:
|
||||||
$(eval MIN_NODE_VERSION_STR := $(shell grep -Eo '"node":.*[0-9.]+"' package.json | sed -n 's/.*[^0-9.]\([0-9.]*\)"/\1/p'))
|
$(eval MIN_NODE_VERSION_STR := $(shell grep -Eo '"node":.*[0-9.]+"' package.json | sed -n 's/.*[^0-9.]\([0-9.]*\)"/\1/p'))
|
||||||
$(eval MIN_NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell echo '$(MIN_NODE_VERSION_STR)' | tr '.' ' ')))
|
$(eval MIN_NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell echo '$(MIN_NODE_VERSION_STR)' | tr '.' ' ')))
|
||||||
$(eval NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell node -v | cut -c2- | tr '.' ' ');))
|
$(eval PNPM_MISSING := $(shell hash pnpm > /dev/null 2>&1 || echo 1))
|
||||||
$(eval NPM_MISSING := $(shell hash npm > /dev/null 2>&1 || echo 1))
|
@if [ "$(NODE_VERSION)" -lt "$(MIN_NODE_VERSION)" ]; then \
|
||||||
@if [ "$(NODE_VERSION)" -lt "$(MIN_NODE_VERSION)" -o "$(NPM_MISSING)" = "1" ]; then \
|
echo "Gitea requires Node.js $(MIN_NODE_VERSION_STR) or greater to build. You can get it at https://nodejs.org/en/download/"; \
|
||||||
echo "Gitea requires Node.js $(MIN_NODE_VERSION_STR) or greater and npm to build. You can get it at https://nodejs.org/en/download/"; \
|
exit 1; \
|
||||||
|
fi
|
||||||
|
@if [ "$(PNPM_MISSING)" = "1" ]; then \
|
||||||
|
echo "Gitea requires pnpm to build. You can install it at https://pnpm.io/installation"; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
.PHONY: clean-all
|
.PHONY: clean-all
|
||||||
clean-all: clean
|
clean-all: clean ## delete backend, frontend and integration files
|
||||||
rm -rf $(WEBPACK_DEST_ENTRIES) node_modules
|
rm -rf $(WEBPACK_DEST_ENTRIES) node_modules
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean: ## delete backend and integration files
|
||||||
rm -rf $(EXECUTABLE) $(DIST) $(BINDATA_DEST) $(BINDATA_HASH) \
|
rm -rf $(EXECUTABLE) $(DIST) $(BINDATA_DEST_WILDCARD) \
|
||||||
integrations*.test \
|
integrations*.test \
|
||||||
e2e*.test \
|
e2e*.test \
|
||||||
tests/integration/gitea-integration-* \
|
tests/integration/gitea-integration-* \
|
||||||
@@ -296,7 +253,7 @@ clean:
|
|||||||
tests/e2e/reports/ tests/e2e/test-artifacts/ tests/e2e/test-snapshots/
|
tests/e2e/reports/ tests/e2e/test-artifacts/ tests/e2e/test-snapshots/
|
||||||
|
|
||||||
.PHONY: fmt
|
.PHONY: fmt
|
||||||
fmt:
|
fmt: ## format the Go and template code
|
||||||
@GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}'
|
@GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}'
|
||||||
$(eval TEMPLATES := $(shell find templates -type f -name '*.tmpl'))
|
$(eval TEMPLATES := $(shell find templates -type f -name '*.tmpl'))
|
||||||
@# strip whitespace after '{{' or '(' and before '}}' or ')' unless there is only
|
@# strip whitespace after '{{' or '(' and before '}}' or ')' unless there is only
|
||||||
@@ -311,7 +268,20 @@ fmt-check: fmt
|
|||||||
@diff=$$(git diff --color=always $(GO_SOURCES) templates $(WEB_DIRS)); \
|
@diff=$$(git diff --color=always $(GO_SOURCES) templates $(WEB_DIRS)); \
|
||||||
if [ -n "$$diff" ]; then \
|
if [ -n "$$diff" ]; then \
|
||||||
echo "Please run 'make fmt' and commit the result:"; \
|
echo "Please run 'make fmt' and commit the result:"; \
|
||||||
echo "$${diff}"; \
|
printf "%s" "$${diff}"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
.PHONY: fix
|
||||||
|
fix: ## apply automated fixes to Go code
|
||||||
|
$(GO) run $(GOPLS_MODERNIZE_PACKAGE) -fix ./...
|
||||||
|
|
||||||
|
.PHONY: fix-check
|
||||||
|
fix-check: fix
|
||||||
|
@diff=$$(git diff --color=always $(GO_SOURCES)); \
|
||||||
|
if [ -n "$$diff" ]; then \
|
||||||
|
echo "Please run 'make fix' and commit the result:"; \
|
||||||
|
printf "%s" "$${diff}"; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -325,95 +295,95 @@ TAGS_PREREQ := $(TAGS_EVIDENCE)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: generate-swagger
|
.PHONY: generate-swagger
|
||||||
generate-swagger: $(SWAGGER_SPEC)
|
generate-swagger: $(SWAGGER_SPEC) ## generate the swagger spec from code comments
|
||||||
|
|
||||||
$(SWAGGER_SPEC): $(GO_SOURCES_NO_BINDATA)
|
$(SWAGGER_SPEC): $(GO_SOURCES) $(SWAGGER_SPEC_INPUT)
|
||||||
$(GO) run $(SWAGGER_PACKAGE) generate spec -x "$(SWAGGER_EXCLUDE)" -o './$(SWAGGER_SPEC)'
|
$(GO) run $(SWAGGER_PACKAGE) generate spec --exclude "$(SWAGGER_EXCLUDE)" --input "$(SWAGGER_SPEC_INPUT)" --output './$(SWAGGER_SPEC)'
|
||||||
$(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)'
|
|
||||||
$(SED_INPLACE) $(SWAGGER_NEWLINE_COMMAND) './$(SWAGGER_SPEC)'
|
|
||||||
|
|
||||||
.PHONY: swagger-check
|
.PHONY: swagger-check
|
||||||
swagger-check: generate-swagger
|
swagger-check: generate-swagger
|
||||||
@diff=$$(git diff --color=always '$(SWAGGER_SPEC)'); \
|
@diff=$$(git diff --color=always '$(SWAGGER_SPEC)'); \
|
||||||
if [ -n "$$diff" ]; then \
|
if [ -n "$$diff" ]; then \
|
||||||
echo "Please run 'make generate-swagger' and commit the result:"; \
|
echo "Please run 'make generate-swagger' and commit the result:"; \
|
||||||
echo "$${diff}"; \
|
printf "%s" "$${diff}"; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
.PHONY: swagger-validate
|
.PHONY: swagger-validate
|
||||||
swagger-validate:
|
swagger-validate: ## check if the swagger spec is valid
|
||||||
$(SED_INPLACE) '$(SWAGGER_SPEC_S_JSON)' './$(SWAGGER_SPEC)'
|
@# swagger "validate" requires that the "basePath" must start with a slash, but we are using Golang template "{{...}}"
|
||||||
|
@$(SED_INPLACE) -E -e 's|"basePath":( *)"(.*)"|"basePath":\1"/\2"|g' './$(SWAGGER_SPEC)' # add a prefix slash to basePath
|
||||||
|
@# FIXME: there are some warnings
|
||||||
$(GO) run $(SWAGGER_PACKAGE) validate './$(SWAGGER_SPEC)'
|
$(GO) run $(SWAGGER_PACKAGE) validate './$(SWAGGER_SPEC)'
|
||||||
$(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)'
|
@$(SED_INPLACE) -E -e 's|"basePath":( *)"/(.*)"|"basePath":\1"\2"|g' './$(SWAGGER_SPEC)' # remove the prefix slash from basePath
|
||||||
|
|
||||||
.PHONY: checks
|
.PHONY: checks
|
||||||
checks: checks-frontend checks-backend
|
checks: checks-frontend checks-backend ## run various consistency checks
|
||||||
|
|
||||||
.PHONY: checks-frontend
|
.PHONY: checks-frontend
|
||||||
checks-frontend: lockfile-check svg-check
|
checks-frontend: lockfile-check svg-check ## check frontend files
|
||||||
|
|
||||||
.PHONY: checks-backend
|
.PHONY: checks-backend
|
||||||
checks-backend: tidy-check swagger-check fmt-check swagger-validate security-check
|
checks-backend: tidy-check swagger-check fmt-check fix-check swagger-validate security-check ## check backend files
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint: lint-frontend lint-backend lint-spell
|
lint: lint-frontend lint-backend lint-spell ## lint everything
|
||||||
|
|
||||||
.PHONY: lint-fix
|
.PHONY: lint-fix
|
||||||
lint-fix: lint-frontend-fix lint-backend-fix lint-spell-fix
|
lint-fix: lint-frontend-fix lint-backend-fix lint-spell-fix ## lint everything and fix issues
|
||||||
|
|
||||||
.PHONY: lint-frontend
|
.PHONY: lint-frontend
|
||||||
lint-frontend: lint-js lint-css
|
lint-frontend: lint-js lint-css ## lint frontend files
|
||||||
|
|
||||||
.PHONY: lint-frontend-fix
|
.PHONY: lint-frontend-fix
|
||||||
lint-frontend-fix: lint-js-fix lint-css-fix
|
lint-frontend-fix: lint-js-fix lint-css-fix ## lint frontend files and fix issues
|
||||||
|
|
||||||
.PHONY: lint-backend
|
.PHONY: lint-backend
|
||||||
lint-backend: lint-go lint-go-vet lint-go-gopls lint-editorconfig
|
lint-backend: lint-go lint-go-gitea-vet lint-go-gopls lint-editorconfig ## lint backend files
|
||||||
|
|
||||||
.PHONY: lint-backend-fix
|
.PHONY: lint-backend-fix
|
||||||
lint-backend-fix: lint-go-fix lint-go-vet lint-editorconfig
|
lint-backend-fix: lint-go-fix lint-go-gitea-vet lint-editorconfig ## lint backend files and fix issues
|
||||||
|
|
||||||
.PHONY: lint-js
|
.PHONY: lint-js
|
||||||
lint-js: node_modules
|
lint-js: node_modules ## lint js files
|
||||||
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES)
|
$(NODE_VARS) pnpm exec eslint --color --max-warnings=0 --flag unstable_native_nodejs_ts_config $(ESLINT_FILES)
|
||||||
npx vue-tsc
|
$(NODE_VARS) pnpm exec vue-tsc
|
||||||
|
|
||||||
.PHONY: lint-js-fix
|
.PHONY: lint-js-fix
|
||||||
lint-js-fix: node_modules
|
lint-js-fix: node_modules ## lint js files and fix issues
|
||||||
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) --fix
|
$(NODE_VARS) pnpm exec eslint --color --max-warnings=0 --flag unstable_native_nodejs_ts_config $(ESLINT_FILES) --fix
|
||||||
npx vue-tsc
|
$(NODE_VARS) pnpm exec vue-tsc
|
||||||
|
|
||||||
.PHONY: lint-css
|
.PHONY: lint-css
|
||||||
lint-css: node_modules
|
lint-css: node_modules ## lint css files
|
||||||
npx stylelint --color --max-warnings=0 $(STYLELINT_FILES)
|
$(NODE_VARS) pnpm exec stylelint --color --max-warnings=0 $(STYLELINT_FILES)
|
||||||
|
|
||||||
.PHONY: lint-css-fix
|
.PHONY: lint-css-fix
|
||||||
lint-css-fix: node_modules
|
lint-css-fix: node_modules ## lint css files and fix issues
|
||||||
npx stylelint --color --max-warnings=0 $(STYLELINT_FILES) --fix
|
$(NODE_VARS) pnpm exec stylelint --color --max-warnings=0 $(STYLELINT_FILES) --fix
|
||||||
|
|
||||||
.PHONY: lint-swagger
|
.PHONY: lint-swagger
|
||||||
lint-swagger: node_modules
|
lint-swagger: node_modules ## lint swagger files
|
||||||
npx spectral lint -q -F hint $(SWAGGER_SPEC)
|
$(NODE_VARS) pnpm exec spectral lint -q -F hint $(SWAGGER_SPEC)
|
||||||
|
|
||||||
.PHONY: lint-md
|
.PHONY: lint-md
|
||||||
lint-md: node_modules
|
lint-md: node_modules ## lint markdown files
|
||||||
npx markdownlint *.md
|
$(NODE_VARS) pnpm exec markdownlint *.md
|
||||||
|
|
||||||
.PHONY: lint-spell
|
.PHONY: lint-spell
|
||||||
lint-spell:
|
lint-spell: ## lint spelling
|
||||||
@go run $(MISSPELL_PACKAGE) -dict tools/misspellings.csv -error $(SPELLCHECK_FILES)
|
@go run $(MISSPELL_PACKAGE) -dict tools/misspellings.csv -error $(SPELLCHECK_FILES)
|
||||||
|
|
||||||
.PHONY: lint-spell-fix
|
.PHONY: lint-spell-fix
|
||||||
lint-spell-fix:
|
lint-spell-fix: ## lint spelling and fix issues
|
||||||
@go run $(MISSPELL_PACKAGE) -dict tools/misspellings.csv -w $(SPELLCHECK_FILES)
|
@go run $(MISSPELL_PACKAGE) -dict tools/misspellings.csv -w $(SPELLCHECK_FILES)
|
||||||
|
|
||||||
.PHONY: lint-go
|
.PHONY: lint-go
|
||||||
lint-go:
|
lint-go: ## lint go files
|
||||||
$(GO) run $(GOLANGCI_LINT_PACKAGE) run
|
$(GO) run $(GOLANGCI_LINT_PACKAGE) run
|
||||||
|
|
||||||
.PHONY: lint-go-fix
|
.PHONY: lint-go-fix
|
||||||
lint-go-fix:
|
lint-go-fix: ## lint go files and fix issues
|
||||||
$(GO) run $(GOLANGCI_LINT_PACKAGE) run --fix
|
$(GO) run $(GOLANGCI_LINT_PACKAGE) run --fix
|
||||||
|
|
||||||
# workaround step for the lint-go-windows CI task because 'go run' can not
|
# workaround step for the lint-go-windows CI task because 'go run' can not
|
||||||
@@ -423,58 +393,59 @@ lint-go-windows:
|
|||||||
@GOOS= GOARCH= $(GO) install $(GOLANGCI_LINT_PACKAGE)
|
@GOOS= GOARCH= $(GO) install $(GOLANGCI_LINT_PACKAGE)
|
||||||
golangci-lint run
|
golangci-lint run
|
||||||
|
|
||||||
.PHONY: lint-go-vet
|
.PHONY: lint-go-gitea-vet
|
||||||
lint-go-vet:
|
lint-go-gitea-vet: ## lint go files with gitea-vet
|
||||||
@echo "Running go vet..."
|
@echo "Running gitea-vet..."
|
||||||
@GOOS= GOARCH= $(GO) build code.gitea.io/gitea-vet
|
@GOOS= GOARCH= $(GO) build code.gitea.io/gitea-vet
|
||||||
@$(GO) vet -vettool=gitea-vet ./...
|
@$(GO) vet -vettool=gitea-vet ./...
|
||||||
|
|
||||||
.PHONY: lint-go-gopls
|
.PHONY: lint-go-gopls
|
||||||
lint-go-gopls:
|
lint-go-gopls: ## lint go files with gopls
|
||||||
@echo "Running gopls check..."
|
@echo "Running gopls check..."
|
||||||
@GO=$(GO) GOPLS_PACKAGE=$(GOPLS_PACKAGE) tools/lint-go-gopls.sh $(GO_SOURCES_NO_BINDATA)
|
@GO=$(GO) GOPLS_PACKAGE=$(GOPLS_PACKAGE) tools/lint-go-gopls.sh $(GO_SOURCES)
|
||||||
|
|
||||||
.PHONY: lint-editorconfig
|
.PHONY: lint-editorconfig
|
||||||
lint-editorconfig:
|
lint-editorconfig:
|
||||||
|
@echo "Running editorconfig check..."
|
||||||
@$(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) $(EDITORCONFIG_FILES)
|
@$(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) $(EDITORCONFIG_FILES)
|
||||||
|
|
||||||
.PHONY: lint-actions
|
.PHONY: lint-actions
|
||||||
lint-actions:
|
lint-actions: ## lint action workflow files
|
||||||
$(GO) run $(ACTIONLINT_PACKAGE)
|
$(GO) run $(ACTIONLINT_PACKAGE)
|
||||||
|
|
||||||
.PHONY: lint-templates
|
.PHONY: lint-templates
|
||||||
lint-templates: .venv node_modules
|
lint-templates: .venv node_modules ## lint template files
|
||||||
@node tools/lint-templates-svg.js
|
@node tools/lint-templates-svg.ts
|
||||||
@poetry run djlint $(shell find templates -type f -iname '*.tmpl')
|
@uv run --frozen djlint $(shell find templates -type f -iname '*.tmpl')
|
||||||
|
|
||||||
.PHONY: lint-yaml
|
.PHONY: lint-yaml
|
||||||
lint-yaml: .venv
|
lint-yaml: .venv ## lint yaml files
|
||||||
@poetry run yamllint .
|
@uv run --frozen yamllint -s .
|
||||||
|
|
||||||
.PHONY: watch
|
.PHONY: watch
|
||||||
watch:
|
watch: ## watch everything and continuously rebuild
|
||||||
@bash tools/watch.sh
|
@bash tools/watch.sh
|
||||||
|
|
||||||
.PHONY: watch-frontend
|
.PHONY: watch-frontend
|
||||||
watch-frontend: node-check node_modules
|
watch-frontend: node-check node_modules ## watch frontend files and continuously rebuild
|
||||||
@rm -rf $(WEBPACK_DEST_ENTRIES)
|
@rm -rf $(WEBPACK_DEST_ENTRIES)
|
||||||
NODE_ENV=development npx webpack --watch --progress
|
NODE_ENV=development $(NODE_VARS) pnpm exec webpack --watch --progress --disable-interpret
|
||||||
|
|
||||||
.PHONY: watch-backend
|
.PHONY: watch-backend
|
||||||
watch-backend: go-check
|
watch-backend: go-check ## watch backend files and continuously rebuild
|
||||||
GITEA_RUN_MODE=dev $(GO) run $(AIR_PACKAGE) -c .air.toml
|
GITEA_RUN_MODE=dev $(GO) run $(AIR_PACKAGE) -c .air.toml
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: test-frontend test-backend
|
test: test-frontend test-backend ## test everything
|
||||||
|
|
||||||
.PHONY: test-backend
|
.PHONY: test-backend
|
||||||
test-backend:
|
test-backend: ## test backend files
|
||||||
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
|
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
|
||||||
@$(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_PACKAGES)
|
@$(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_PACKAGES)
|
||||||
|
|
||||||
.PHONY: test-frontend
|
.PHONY: test-frontend
|
||||||
test-frontend: node_modules
|
test-frontend: node_modules ## test frontend files
|
||||||
npx vitest
|
$(NODE_VARS) pnpm exec vitest
|
||||||
|
|
||||||
.PHONY: test-check
|
.PHONY: test-check
|
||||||
test-check:
|
test-check:
|
||||||
@@ -482,7 +453,7 @@ test-check:
|
|||||||
@diff=$$(git status -s); \
|
@diff=$$(git status -s); \
|
||||||
if [ -n "$$diff" ]; then \
|
if [ -n "$$diff" ]; then \
|
||||||
echo "make test-backend has changed files in the source tree:"; \
|
echo "make test-backend has changed files in the source tree:"; \
|
||||||
echo "$${diff}"; \
|
printf "%s" "$${diff}"; \
|
||||||
echo "You should change the tests to create these files in a temporary directory."; \
|
echo "You should change the tests to create these files in a temporary directory."; \
|
||||||
echo "Do not simply add these files to .gitignore"; \
|
echo "Do not simply add these files to .gitignore"; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
@@ -505,7 +476,7 @@ unit-test-coverage:
|
|||||||
@$(GO) test $(GOTESTFLAGS) -timeout=20m -tags='$(TEST_TAGS)' -cover -coverprofile coverage.out $(GO_TEST_PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1
|
@$(GO) test $(GOTESTFLAGS) -timeout=20m -tags='$(TEST_TAGS)' -cover -coverprofile coverage.out $(GO_TEST_PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1
|
||||||
|
|
||||||
.PHONY: tidy
|
.PHONY: tidy
|
||||||
tidy:
|
tidy: ## run go mod tidy
|
||||||
$(eval MIN_GO_VERSION := $(shell grep -Eo '^go\s+[0-9]+\.[0-9.]+' go.mod | cut -d' ' -f2))
|
$(eval MIN_GO_VERSION := $(shell grep -Eo '^go\s+[0-9]+\.[0-9.]+' go.mod | cut -d' ' -f2))
|
||||||
$(GO) mod tidy -compat=$(MIN_GO_VERSION)
|
$(GO) mod tidy -compat=$(MIN_GO_VERSION)
|
||||||
@$(MAKE) --no-print-directory $(GO_LICENSE_FILE)
|
@$(MAKE) --no-print-directory $(GO_LICENSE_FILE)
|
||||||
@@ -519,15 +490,17 @@ tidy-check: tidy
|
|||||||
@diff=$$(git diff --color=always go.mod go.sum $(GO_LICENSE_FILE)); \
|
@diff=$$(git diff --color=always go.mod go.sum $(GO_LICENSE_FILE)); \
|
||||||
if [ -n "$$diff" ]; then \
|
if [ -n "$$diff" ]; then \
|
||||||
echo "Please run 'make tidy' and commit the result:"; \
|
echo "Please run 'make tidy' and commit the result:"; \
|
||||||
echo "$${diff}"; \
|
printf "%s" "$${diff}"; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
.PHONY: go-licenses
|
.PHONY: go-licenses
|
||||||
go-licenses: $(GO_LICENSE_FILE)
|
go-licenses: $(GO_LICENSE_FILE) ## regenerate go licenses
|
||||||
|
|
||||||
$(GO_LICENSE_FILE): go.mod go.sum
|
$(GO_LICENSE_FILE): go.mod go.sum
|
||||||
-$(GO) run $(GO_LICENSES_PACKAGE) save . --force --save_path=$(GO_LICENSE_TMP_DIR) 2>/dev/null
|
@rm -rf $(GO_LICENSE_FILE)
|
||||||
|
$(GO) install $(GO_LICENSES_PACKAGE)
|
||||||
|
-GOOS=linux CGO_ENABLED=1 go-licenses save . --force --save_path=$(GO_LICENSE_TMP_DIR) 2>/dev/null
|
||||||
$(GO) run build/generate-go-licenses.go $(GO_LICENSE_TMP_DIR) $(GO_LICENSE_FILE)
|
$(GO) run build/generate-go-licenses.go $(GO_LICENSE_TMP_DIR) $(GO_LICENSE_FILE)
|
||||||
@rm -rf $(GO_LICENSE_TMP_DIR)
|
@rm -rf $(GO_LICENSE_TMP_DIR)
|
||||||
|
|
||||||
@@ -615,7 +588,7 @@ test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test
|
|||||||
|
|
||||||
.PHONY: playwright
|
.PHONY: playwright
|
||||||
playwright: deps-frontend
|
playwright: deps-frontend
|
||||||
npx playwright install $(PLAYWRIGHT_FLAGS)
|
$(NODE_VARS) pnpm exec playwright install $(PLAYWRIGHT_FLAGS)
|
||||||
|
|
||||||
.PHONY: test-e2e%
|
.PHONY: test-e2e%
|
||||||
test-e2e%: TEST_TYPE ?= e2e
|
test-e2e%: TEST_TYPE ?= e2e
|
||||||
@@ -771,17 +744,17 @@ install: $(wildcard *.go)
|
|||||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) install -v -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)'
|
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) install -v -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)'
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build: frontend backend
|
build: frontend backend ## build everything
|
||||||
|
|
||||||
.PHONY: frontend
|
.PHONY: frontend
|
||||||
frontend: $(WEBPACK_DEST)
|
frontend: $(WEBPACK_DEST) ## build frontend files
|
||||||
|
|
||||||
.PHONY: backend
|
.PHONY: backend
|
||||||
backend: go-check generate-backend $(EXECUTABLE)
|
backend: go-check generate-backend $(EXECUTABLE) ## build backend files
|
||||||
|
|
||||||
# We generate the backend before the frontend in case we in future we want to generate things in the frontend from generated files in backend
|
# We generate the backend before the frontend in case we in future we want to generate things in the frontend from generated files in backend
|
||||||
.PHONY: generate
|
.PHONY: generate
|
||||||
generate: generate-backend
|
generate: generate-backend ## run "go generate"
|
||||||
|
|
||||||
.PHONY: generate-backend
|
.PHONY: generate-backend
|
||||||
generate-backend: $(TAGS_PREREQ) generate-go
|
generate-backend: $(TAGS_PREREQ) generate-go
|
||||||
@@ -793,10 +766,13 @@ generate-go: $(TAGS_PREREQ)
|
|||||||
|
|
||||||
.PHONY: security-check
|
.PHONY: security-check
|
||||||
security-check:
|
security-check:
|
||||||
go run $(GOVULNCHECK_PACKAGE) ./...
|
go run $(GOVULNCHECK_PACKAGE) -show color ./...
|
||||||
|
|
||||||
$(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ)
|
$(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ)
|
||||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
|
ifneq ($(and $(STATIC),$(findstring pam,$(TAGS))),)
|
||||||
|
$(error pam support set via TAGS doesn't support static builds)
|
||||||
|
endif
|
||||||
|
CGO_ENABLED="$(CGO_ENABLED)" CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(EXTLDFLAGS) $(LDFLAGS)' -o $@
|
||||||
|
|
||||||
.PHONY: release
|
.PHONY: release
|
||||||
release: frontend generate release-windows release-linux release-darwin release-freebsd release-copy release-compress vendor release-sources release-check
|
release: frontend generate release-windows release-linux release-darwin release-freebsd release-copy release-compress vendor release-sources release-check
|
||||||
@@ -806,22 +782,22 @@ $(DIST_DIRS):
|
|||||||
|
|
||||||
.PHONY: release-windows
|
.PHONY: release-windows
|
||||||
release-windows: | $(DIST_DIRS)
|
release-windows: | $(DIST_DIRS)
|
||||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
|
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo $(TAGS)' -ldflags '-s -w -linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
|
||||||
ifeq (,$(findstring gogit,$(TAGS)))
|
ifeq (,$(findstring gogit,$(TAGS)))
|
||||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo gogit $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit .
|
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo gogit $(TAGS)' -ldflags '-s -w -linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit .
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: release-linux
|
.PHONY: release-linux
|
||||||
release-linux: | $(DIST_DIRS)
|
release-linux: | $(DIST_DIRS)
|
||||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out gitea-$(VERSION) .
|
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-s -w -linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out gitea-$(VERSION) .
|
||||||
|
|
||||||
.PHONY: release-darwin
|
.PHONY: release-darwin
|
||||||
release-darwin: | $(DIST_DIRS)
|
release-darwin: | $(DIST_DIRS)
|
||||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin-10.12/amd64,darwin-10.12/arm64' -out gitea-$(VERSION) .
|
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-s -w $(LDFLAGS)' -targets 'darwin-10.12/amd64,darwin-10.12/arm64' -out gitea-$(VERSION) .
|
||||||
|
|
||||||
.PHONY: release-freebsd
|
.PHONY: release-freebsd
|
||||||
release-freebsd: | $(DIST_DIRS)
|
release-freebsd: | $(DIST_DIRS)
|
||||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'freebsd/amd64' -out gitea-$(VERSION) .
|
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-s -w $(LDFLAGS)' -targets 'freebsd/amd64' -out gitea-$(VERSION) .
|
||||||
|
|
||||||
.PHONY: release-copy
|
.PHONY: release-copy
|
||||||
release-copy: | $(DIST_DIRS)
|
release-copy: | $(DIST_DIRS)
|
||||||
@@ -846,20 +822,20 @@ release-sources: | $(DIST_DIRS)
|
|||||||
rm -f $(STORED_VERSION_FILE)
|
rm -f $(STORED_VERSION_FILE)
|
||||||
|
|
||||||
.PHONY: deps
|
.PHONY: deps
|
||||||
deps: deps-frontend deps-backend deps-tools deps-py
|
deps: deps-frontend deps-backend deps-tools deps-py ## install dependencies
|
||||||
|
|
||||||
.PHONY: deps-py
|
.PHONY: deps-py
|
||||||
deps-py: .venv
|
deps-py: .venv ## install python dependencies
|
||||||
|
|
||||||
.PHONY: deps-frontend
|
.PHONY: deps-frontend
|
||||||
deps-frontend: node_modules
|
deps-frontend: node_modules ## install frontend dependencies
|
||||||
|
|
||||||
.PHONY: deps-backend
|
.PHONY: deps-backend
|
||||||
deps-backend:
|
deps-backend: ## install backend dependencies
|
||||||
$(GO) mod download
|
$(GO) mod download
|
||||||
|
|
||||||
.PHONY: deps-tools
|
.PHONY: deps-tools
|
||||||
deps-tools:
|
deps-tools: ## install tool dependencies
|
||||||
$(GO) install $(AIR_PACKAGE) & \
|
$(GO) install $(AIR_PACKAGE) & \
|
||||||
$(GO) install $(EDITORCONFIG_CHECKER_PACKAGE) & \
|
$(GO) install $(EDITORCONFIG_CHECKER_PACKAGE) & \
|
||||||
$(GO) install $(GOFUMPT_PACKAGE) & \
|
$(GO) install $(GOFUMPT_PACKAGE) & \
|
||||||
@@ -872,62 +848,50 @@ deps-tools:
|
|||||||
$(GO) install $(GOVULNCHECK_PACKAGE) & \
|
$(GO) install $(GOVULNCHECK_PACKAGE) & \
|
||||||
$(GO) install $(ACTIONLINT_PACKAGE) & \
|
$(GO) install $(ACTIONLINT_PACKAGE) & \
|
||||||
$(GO) install $(GOPLS_PACKAGE) & \
|
$(GO) install $(GOPLS_PACKAGE) & \
|
||||||
|
$(GO) install $(GOPLS_MODERNIZE_PACKAGE) & \
|
||||||
wait
|
wait
|
||||||
|
|
||||||
node_modules: package-lock.json
|
node_modules: pnpm-lock.yaml
|
||||||
npm install --no-save
|
$(NODE_VARS) pnpm install --frozen-lockfile
|
||||||
@touch node_modules
|
@touch node_modules
|
||||||
|
|
||||||
.venv: poetry.lock
|
.venv: uv.lock
|
||||||
poetry install
|
uv sync
|
||||||
@touch .venv
|
@touch .venv
|
||||||
|
|
||||||
.PHONY: update
|
.PHONY: update
|
||||||
update: update-js update-py
|
update: update-js update-py ## update js and py dependencies
|
||||||
|
|
||||||
.PHONY: update-js
|
.PHONY: update-js
|
||||||
update-js: node-check | node_modules
|
update-js: node-check | node_modules ## update js dependencies
|
||||||
npx updates -u -f package.json
|
$(NODE_VARS) pnpm exec updates -u -f package.json
|
||||||
rm -rf node_modules package-lock.json
|
rm -rf node_modules pnpm-lock.yaml
|
||||||
npm install --package-lock
|
$(NODE_VARS) pnpm install
|
||||||
npx nolyfill install
|
$(NODE_VARS) pnpm exec nolyfill install
|
||||||
npm install --package-lock
|
$(NODE_VARS) pnpm install
|
||||||
@touch node_modules
|
@touch node_modules
|
||||||
|
|
||||||
.PHONY: update-py
|
.PHONY: update-py
|
||||||
update-py: node-check | node_modules
|
update-py: node-check | node_modules ## update py dependencies
|
||||||
npx updates -u -f pyproject.toml
|
$(NODE_VARS) pnpm exec updates -u -f pyproject.toml
|
||||||
rm -rf .venv poetry.lock
|
rm -rf .venv uv.lock
|
||||||
poetry install
|
uv sync
|
||||||
@touch .venv
|
@touch .venv
|
||||||
|
|
||||||
.PHONY: fomantic
|
|
||||||
fomantic:
|
|
||||||
rm -rf $(FOMANTIC_WORK_DIR)/build
|
|
||||||
cd $(FOMANTIC_WORK_DIR) && npm install --no-save
|
|
||||||
cp -f $(FOMANTIC_WORK_DIR)/theme.config.less $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/theme.config
|
|
||||||
cp -rf $(FOMANTIC_WORK_DIR)/_site $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/
|
|
||||||
$(SED_INPLACE) -e 's/ overrideBrowserslist\r/ overrideBrowserslist: ["defaults"]\r/g' $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/tasks/config/tasks.js
|
|
||||||
cd $(FOMANTIC_WORK_DIR) && npx gulp -f node_modules/fomantic-ui/gulpfile.js build
|
|
||||||
# fomantic uses "touchstart" as click event for some browsers, it's not ideal, so we force fomantic to always use "click" as click event
|
|
||||||
$(SED_INPLACE) -e 's/clickEvent[ \t]*=/clickEvent = "click", unstableClickEvent =/g' $(FOMANTIC_WORK_DIR)/build/semantic.js
|
|
||||||
$(SED_INPLACE) -e 's/\r//g' $(FOMANTIC_WORK_DIR)/build/semantic.css $(FOMANTIC_WORK_DIR)/build/semantic.js
|
|
||||||
rm -f $(FOMANTIC_WORK_DIR)/build/*.min.*
|
|
||||||
|
|
||||||
.PHONY: webpack
|
.PHONY: webpack
|
||||||
webpack: $(WEBPACK_DEST)
|
webpack: $(WEBPACK_DEST) ## build webpack files
|
||||||
|
|
||||||
$(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) package-lock.json
|
$(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) pnpm-lock.yaml
|
||||||
@$(MAKE) -s node-check node_modules
|
@$(MAKE) -s node-check node_modules
|
||||||
@rm -rf $(WEBPACK_DEST_ENTRIES)
|
@rm -rf $(WEBPACK_DEST_ENTRIES)
|
||||||
@echo "Running webpack..."
|
@echo "Running webpack..."
|
||||||
@BROWSERSLIST_IGNORE_OLD_DATA=true npx webpack
|
@BROWSERSLIST_IGNORE_OLD_DATA=true $(NODE_VARS) pnpm exec webpack --disable-interpret
|
||||||
@touch $(WEBPACK_DEST)
|
@touch $(WEBPACK_DEST)
|
||||||
|
|
||||||
.PHONY: svg
|
.PHONY: svg
|
||||||
svg: node-check | node_modules
|
svg: node-check | node_modules ## build svg files
|
||||||
rm -rf $(SVG_DEST_DIR)
|
rm -rf $(SVG_DEST_DIR)
|
||||||
node tools/generate-svg.js
|
node tools/generate-svg.ts
|
||||||
|
|
||||||
.PHONY: svg-check
|
.PHONY: svg-check
|
||||||
svg-check: svg
|
svg-check: svg
|
||||||
@@ -935,18 +899,18 @@ svg-check: svg
|
|||||||
@diff=$$(git diff --color=always --cached $(SVG_DEST_DIR)); \
|
@diff=$$(git diff --color=always --cached $(SVG_DEST_DIR)); \
|
||||||
if [ -n "$$diff" ]; then \
|
if [ -n "$$diff" ]; then \
|
||||||
echo "Please run 'make svg' and 'git add $(SVG_DEST_DIR)' and commit the result:"; \
|
echo "Please run 'make svg' and 'git add $(SVG_DEST_DIR)' and commit the result:"; \
|
||||||
echo "$${diff}"; \
|
printf "%s" "$${diff}"; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
.PHONY: lockfile-check
|
.PHONY: lockfile-check
|
||||||
lockfile-check:
|
lockfile-check:
|
||||||
npm install --package-lock-only
|
$(NODE_VARS) pnpm install --frozen-lockfile
|
||||||
@diff=$$(git diff --color=always package-lock.json); \
|
@diff=$$(git diff --color=always pnpm-lock.yaml); \
|
||||||
if [ -n "$$diff" ]; then \
|
if [ -n "$$diff" ]; then \
|
||||||
echo "package-lock.json is inconsistent with package.json"; \
|
echo "pnpm-lock.yaml is inconsistent with package.json"; \
|
||||||
echo "Please run 'npm install --package-lock-only' and commit the result:"; \
|
echo "Please run 'pnpm install --frozen-lockfile' and commit the result:"; \
|
||||||
echo "$${diff}"; \
|
printf "%s" "$${diff}"; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -960,21 +924,16 @@ update-translations:
|
|||||||
mv ./translations/*.ini ./options/locale/
|
mv ./translations/*.ini ./options/locale/
|
||||||
rmdir ./translations
|
rmdir ./translations
|
||||||
|
|
||||||
.PHONY: generate-license
|
|
||||||
generate-license:
|
|
||||||
$(GO) run build/generate-licenses.go
|
|
||||||
|
|
||||||
.PHONY: generate-gitignore
|
.PHONY: generate-gitignore
|
||||||
generate-gitignore:
|
generate-gitignore: ## update gitignore files
|
||||||
$(GO) run build/generate-gitignores.go
|
$(GO) run build/generate-gitignores.go
|
||||||
|
|
||||||
.PHONY: generate-images
|
.PHONY: generate-images
|
||||||
generate-images: | node_modules
|
generate-images: | node_modules ## generate images
|
||||||
npm install --no-save fabric@6 imagemin-zopfli@7
|
cd tools && node generate-images.ts $(TAGS)
|
||||||
node tools/generate-images.js $(TAGS)
|
|
||||||
|
|
||||||
.PHONY: generate-manpage
|
.PHONY: generate-manpage
|
||||||
generate-manpage:
|
generate-manpage: ## generate manpage
|
||||||
@[ -f gitea ] || make backend
|
@[ -f gitea ] || make backend
|
||||||
@mkdir -p man/man1/ man/man5
|
@mkdir -p man/man1/ man/man5
|
||||||
@./gitea docs --man > man/man1/gitea.1
|
@./gitea docs --man > man/man1/gitea.1
|
||||||
|
|||||||
112
README.md
112
README.md
@@ -9,9 +9,9 @@
|
|||||||
[](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
|
[](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
|
||||||
[](https://opensource.org/licenses/MIT "License: MIT")
|
[](https://opensource.org/licenses/MIT "License: MIT")
|
||||||
[](https://gitpod.io/#https://github.com/go-gitea/gitea)
|
[](https://gitpod.io/#https://github.com/go-gitea/gitea)
|
||||||
[](https://crowdin.com/project/gitea "Crowdin")
|
[](https://translate.gitea.com "Crowdin")
|
||||||
|
|
||||||
[View this document in Chinese](./README_ZH.md)
|
[繁體中文](./README.zh-tw.md) | [简体中文](./README.zh-cn.md)
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
@@ -31,6 +31,14 @@ For accessing free Gitea service (with a limited number of repositories), you ca
|
|||||||
|
|
||||||
To quickly deploy your own dedicated Gitea instance on Gitea Cloud, you can start a free trial at [cloud.gitea.com](https://cloud.gitea.com).
|
To quickly deploy your own dedicated Gitea instance on Gitea Cloud, you can start a free trial at [cloud.gitea.com](https://cloud.gitea.com).
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
You can find comprehensive documentation on our official [documentation website](https://docs.gitea.com/).
|
||||||
|
|
||||||
|
It includes installation, administration, usage, development, contributing guides, and more to help you get started and explore all features effectively.
|
||||||
|
|
||||||
|
If you have any suggestions or would like to contribute to it, you can visit the [documentation repository](https://gitea.com/gitea/docs)
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
From the root of the source tree, run:
|
From the root of the source tree, run:
|
||||||
@@ -44,7 +52,7 @@ or if SQLite support is required:
|
|||||||
The `build` target is split into two sub-targets:
|
The `build` target is split into two sub-targets:
|
||||||
|
|
||||||
- `make backend` which requires [Go Stable](https://go.dev/dl/), the required version is defined in [go.mod](/go.mod).
|
- `make backend` which requires [Go Stable](https://go.dev/dl/), the required version is defined in [go.mod](/go.mod).
|
||||||
- `make frontend` which requires [Node.js LTS](https://nodejs.org/en/download/) or greater.
|
- `make frontend` which requires [Node.js LTS](https://nodejs.org/en/download/) or greater and [pnpm](https://pnpm.io/installation).
|
||||||
|
|
||||||
Internet connectivity is required to download the go and npm modules. When building from the official source tarballs which include pre-built frontend files, the `frontend` target will not be triggered, making it possible to build without Node.js.
|
Internet connectivity is required to download the go and npm modules. When building from the official source tarballs which include pre-built frontend files, the `frontend` target will not be triggered, making it possible to build without Node.js.
|
||||||
|
|
||||||
@@ -52,6 +60,8 @@ More info: https://docs.gitea.com/installation/install-from-source
|
|||||||
|
|
||||||
## Using
|
## Using
|
||||||
|
|
||||||
|
After building, a binary file named `gitea` will be generated in the root of the source tree by default. To run it, use:
|
||||||
|
|
||||||
./gitea web
|
./gitea web
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
@@ -68,22 +78,25 @@ Expected workflow is: Fork -> Patch -> Push -> Pull Request
|
|||||||
|
|
||||||
## Translating
|
## Translating
|
||||||
|
|
||||||
Translations are done through Crowdin. If you want to translate to a new language ask one of the managers in the Crowdin project to add a new language there.
|
[](https://translate.gitea.com)
|
||||||
|
|
||||||
You can also just create an issue for adding a language or ask on discord on the #translation channel. If you need context or find some translation issues, you can leave a comment on the string or ask on Discord. For general translation questions there is a section in the docs. Currently a bit empty but we hope to fill it as questions pop up.
|
Translations are done through [Crowdin](https://translate.gitea.com). If you want to translate to a new language, ask one of the managers in the Crowdin project to add a new language there.
|
||||||
|
|
||||||
https://docs.gitea.com/contributing/localization
|
You can also just create an issue for adding a language or ask on Discord on the #translation channel. If you need context or find some translation issues, you can leave a comment on the string or ask on Discord. For general translation questions there is a section in the docs. Currently a bit empty, but we hope to fill it as questions pop up.
|
||||||
|
|
||||||
[](https://crowdin.com/project/gitea)
|
Get more information from [documentation](https://docs.gitea.com/contributing/localization).
|
||||||
|
|
||||||
## Further information
|
## Official and Third-Party Projects
|
||||||
|
|
||||||
For more information and instructions about how to install Gitea, please look at our [documentation](https://docs.gitea.com/).
|
We provide an official [go-sdk](https://gitea.com/gitea/go-sdk), a CLI tool called [tea](https://gitea.com/gitea/tea) and an [action runner](https://gitea.com/gitea/act_runner) for Gitea Action.
|
||||||
If you have questions that are not covered by the documentation, you can get in contact with us on our [Discord server](https://discord.gg/Gitea) or create a post in the [discourse forum](https://forum.gitea.com/).
|
|
||||||
|
|
||||||
We maintain a list of Gitea-related projects at [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea).
|
We maintain a list of Gitea-related projects at [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea), where you can discover more third-party projects, including SDKs, plugins, themes, and more.
|
||||||
|
|
||||||
The official Gitea CLI is developed at [gitea/tea](https://gitea.com/gitea/tea).
|
## Communication
|
||||||
|
|
||||||
|
[](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea")
|
||||||
|
|
||||||
|
If you have questions that are not covered by the [documentation](https://docs.gitea.com/), you can get in contact with us on our [Discord server](https://discord.gg/Gitea) or create a post in the [discourse forum](https://forum.gitea.com/).
|
||||||
|
|
||||||
## Authors
|
## Authors
|
||||||
|
|
||||||
@@ -122,18 +135,79 @@ Gitea is pronounced [/ɡɪ’ti:/](https://youtu.be/EM71-2uDAoY) as in "gi-tea"
|
|||||||
|
|
||||||
We're [working on it](https://github.com/go-gitea/gitea/issues/1029).
|
We're [working on it](https://github.com/go-gitea/gitea/issues/1029).
|
||||||
|
|
||||||
|
**Where can I find the security patches?**
|
||||||
|
|
||||||
|
In the [release log](https://github.com/go-gitea/gitea/releases) or the [change log](https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md), search for the keyword `SECURITY` to find the security patches.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the MIT License.
|
This project is licensed under the MIT License.
|
||||||
See the [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) file
|
See the [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) file
|
||||||
for the full license text.
|
for the full license text.
|
||||||
|
|
||||||
## Screenshots
|
## Further information
|
||||||
|
|
||||||
Looking for an overview of the interface? Check it out!
|
<details>
|
||||||
|
<summary>Looking for an overview of the interface? Check it out!</summary>
|
||||||
|
|
||||||
||||
|
### Login/Register Page
|
||||||
|:---:|:---:|:---:|
|
|
||||||
||||
|

|
||||||
||||
|

|
||||||
||||
|
|
||||||
|
### User Dashboard
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
### User Profile
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Explore
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
### Repository
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
#### Repository Issue
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
#### Repository Pull Requests
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
#### Repository Actions
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
#### Repository Activity
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
### Organization
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</details>
|
||||||
|
|||||||
206
README.zh-cn.md
Normal file
206
README.zh-cn.md
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
# Gitea
|
||||||
|
|
||||||
|
[](https://github.com/go-gitea/gitea/actions/workflows/release-nightly.yml?query=branch%3Amain "Release Nightly")
|
||||||
|
[](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea")
|
||||||
|
[](https://goreportcard.com/report/code.gitea.io/gitea "Go Report Card")
|
||||||
|
[](https://pkg.go.dev/code.gitea.io/gitea "GoDoc")
|
||||||
|
[](https://github.com/go-gitea/gitea/releases/latest "GitHub release")
|
||||||
|
[](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source")
|
||||||
|
[](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
|
||||||
|
[](https://opensource.org/licenses/MIT "License: MIT")
|
||||||
|
[](https://gitpod.io/#https://github.com/go-gitea/gitea)
|
||||||
|
[](https://translate.gitea.com "Crowdin")
|
||||||
|
|
||||||
|
[English](./README.md) | [繁體中文](./README.zh-tw.md)
|
||||||
|
|
||||||
|
## 目的
|
||||||
|
|
||||||
|
这个项目的目标是提供最简单、最快速、最无痛的方式来设置自托管的 Git 服务。
|
||||||
|
|
||||||
|
由于 Gitea 是用 Go 语言编写的,它可以在 Go 支持的所有平台和架构上运行,包括 Linux、macOS 和 Windows 的 x86、amd64、ARM 和 PowerPC 架构。这个项目自 2016 年 11 月从 [Gogs](https://gogs.io) [分叉](https://blog.gitea.com/welcome-to-gitea/) 而来,但已经有了很多变化。
|
||||||
|
|
||||||
|
在线演示可以访问 [demo.gitea.com](https://demo.gitea.com)。
|
||||||
|
|
||||||
|
要访问免费的 Gitea 服务(有一定数量的仓库限制),可以访问 [gitea.com](https://gitea.com/user/login)。
|
||||||
|
|
||||||
|
要快速部署您自己的专用 Gitea 实例,可以在 [cloud.gitea.com](https://cloud.gitea.com) 开始免费试用。
|
||||||
|
|
||||||
|
## 文件
|
||||||
|
|
||||||
|
您可以在我们的官方 [文件网站](https://docs.gitea.com/) 上找到全面的文件。
|
||||||
|
|
||||||
|
它包括安装、管理、使用、开发、贡献指南等,帮助您快速入门并有效地探索所有功能。
|
||||||
|
|
||||||
|
如果您有任何建议或想要贡献,可以访问 [文件仓库](https://gitea.com/gitea/docs)
|
||||||
|
|
||||||
|
## 构建
|
||||||
|
|
||||||
|
从源代码树的根目录运行:
|
||||||
|
|
||||||
|
TAGS="bindata" make build
|
||||||
|
|
||||||
|
如果需要 SQLite 支持:
|
||||||
|
|
||||||
|
TAGS="bindata sqlite sqlite_unlock_notify" make build
|
||||||
|
|
||||||
|
`build` 目标分为两个子目标:
|
||||||
|
|
||||||
|
- `make backend` 需要 [Go Stable](https://go.dev/dl/),所需版本在 [go.mod](/go.mod) 中定义。
|
||||||
|
- `make frontend` 需要 [Node.js LTS](https://nodejs.org/en/download/) 或更高版本。
|
||||||
|
|
||||||
|
需要互联网连接来下载 go 和 npm 模块。从包含预构建前端文件的官方源代码压缩包构建时,不会触发 `frontend` 目标,因此可以在没有 Node.js 的情况下构建。
|
||||||
|
|
||||||
|
更多信息:https://docs.gitea.com/installation/install-from-source
|
||||||
|
|
||||||
|
## 使用
|
||||||
|
|
||||||
|
构建后,默认情况下会在源代码树的根目录生成一个名为 `gitea` 的二进制文件。要运行它,请使用:
|
||||||
|
|
||||||
|
./gitea web
|
||||||
|
|
||||||
|
> [!注意]
|
||||||
|
> 如果您对使用我们的 API 感兴趣,我们提供了实验性支持,并附有 [文件](https://docs.gitea.com/api)。
|
||||||
|
|
||||||
|
## 贡献
|
||||||
|
|
||||||
|
预期的工作流程是:Fork -> Patch -> Push -> Pull Request
|
||||||
|
|
||||||
|
> [!注意]
|
||||||
|
>
|
||||||
|
> 1. **在开始进行 Pull Request 之前,您必须阅读 [贡献者指南](CONTRIBUTING.md)。**
|
||||||
|
> 2. 如果您在项目中发现了漏洞,请私下写信给 **security@gitea.io**。谢谢!
|
||||||
|
|
||||||
|
## 翻译
|
||||||
|
|
||||||
|
[](https://translate.gitea.com)
|
||||||
|
|
||||||
|
翻译通过 [Crowdin](https://translate.gitea.com) 进行。如果您想翻译成新的语言,请在 Crowdin 项目中请求管理员添加新语言。
|
||||||
|
|
||||||
|
您也可以创建一个 issue 来添加语言,或者在 discord 的 #translation 频道上询问。如果您需要上下文或发现一些翻译问题,可以在字符串上留言或在 Discord 上询问。对于一般的翻译问题,文档中有一个部分。目前有点空,但我们希望随着问题的出现而填充它。
|
||||||
|
|
||||||
|
更多信息请参阅 [文件](https://docs.gitea.com/contributing/localization)。
|
||||||
|
|
||||||
|
## 官方和第三方项目
|
||||||
|
|
||||||
|
我们提供了一个官方的 [go-sdk](https://gitea.com/gitea/go-sdk),一个名为 [tea](https://gitea.com/gitea/tea) 的 CLI 工具和一个 Gitea Action 的 [action runner](https://gitea.com/gitea/act_runner)。
|
||||||
|
|
||||||
|
我们在 [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea) 维护了一个 Gitea 相关项目的列表,您可以在那里发现更多的第三方项目,包括 SDK、插件、主题等。
|
||||||
|
|
||||||
|
## 通讯
|
||||||
|
|
||||||
|
[](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea")
|
||||||
|
|
||||||
|
如果您有任何文件未涵盖的问题,可以在我们的 [Discord 服务器](https://discord.gg/Gitea) 上与我们联系,或者在 [discourse 论坛](https://forum.gitea.com/) 上创建帖子。
|
||||||
|
|
||||||
|
## 作者
|
||||||
|
|
||||||
|
- [维护者](https://github.com/orgs/go-gitea/people)
|
||||||
|
- [贡献者](https://github.com/go-gitea/gitea/graphs/contributors)
|
||||||
|
- [翻译者](options/locale/TRANSLATORS)
|
||||||
|
|
||||||
|
## 支持者
|
||||||
|
|
||||||
|
感谢所有支持者! 🙏 [[成为支持者](https://opencollective.com/gitea#backer)]
|
||||||
|
|
||||||
|
<a href="https://opencollective.com/gitea#backers" target="_blank"><img src="https://opencollective.com/gitea/backers.svg?width=890"></a>
|
||||||
|
|
||||||
|
## 赞助商
|
||||||
|
|
||||||
|
通过成为赞助商来支持这个项目。您的标志将显示在这里,并带有链接到您的网站。 [[成为赞助商](https://opencollective.com/gitea#sponsor)]
|
||||||
|
|
||||||
|
<a href="https://opencollective.com/gitea/sponsor/0/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/0/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/gitea/sponsor/1/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/1/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/gitea/sponsor/2/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/2/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/gitea/sponsor/3/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/3/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/gitea/sponsor/4/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/4/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/gitea/sponsor/5/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/5/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/gitea/sponsor/6/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/6/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/gitea/sponsor/7/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/7/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/gitea/sponsor/8/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/8/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/gitea/sponsor/9/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/9/avatar.svg"></a>
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
**Gitea 怎么发音?**
|
||||||
|
|
||||||
|
Gitea 的发音是 [/ɡɪ’ti:/](https://youtu.be/EM71-2uDAoY),就像 "gi-tea" 一样,g 是硬音。
|
||||||
|
|
||||||
|
**为什么这个项目没有托管在 Gitea 实例上?**
|
||||||
|
|
||||||
|
我们正在 [努力](https://github.com/go-gitea/gitea/issues/1029)。
|
||||||
|
|
||||||
|
**在哪里可以找到安全补丁?**
|
||||||
|
|
||||||
|
在 [发布日志](https://github.com/go-gitea/gitea/releases) 或 [变更日志](https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md) 中,搜索关键词 `SECURITY` 以找到安全补丁。
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
这个项目是根据 MIT 许可证授权的。
|
||||||
|
请参阅 [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) 文件以获取完整的许可证文本。
|
||||||
|
|
||||||
|
## 进一步信息
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>寻找界面概述?查看这里!</summary>
|
||||||
|
|
||||||
|
### 登录/注册页面
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
### 用户仪表板
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
### 用户资料
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 探索
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
### 仓库
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
#### 仓库问题
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
#### 仓库拉取请求
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
#### 仓库操作
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
#### 仓库活动
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
### 组织
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</details>
|
||||||
206
README.zh-tw.md
Normal file
206
README.zh-tw.md
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
# Gitea
|
||||||
|
|
||||||
|
[](https://github.com/go-gitea/gitea/actions/workflows/release-nightly.yml?query=branch%3Amain "Release Nightly")
|
||||||
|
[](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea")
|
||||||
|
[](https://goreportcard.com/report/code.gitea.io/gitea "Go Report Card")
|
||||||
|
[](https://pkg.go.dev/code.gitea.io/gitea "GoDoc")
|
||||||
|
[](https://github.com/go-gitea/gitea/releases/latest "GitHub release")
|
||||||
|
[](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source")
|
||||||
|
[](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
|
||||||
|
[](https://opensource.org/licenses/MIT "License: MIT")
|
||||||
|
[](https://gitpod.io/#https://github.com/go-gitea/gitea)
|
||||||
|
[](https://translate.gitea.com "Crowdin")
|
||||||
|
|
||||||
|
[English](./README.md) | [简体中文](./README.zh-cn.md)
|
||||||
|
|
||||||
|
## 目的
|
||||||
|
|
||||||
|
這個項目的目標是提供最簡單、最快速、最無痛的方式來設置自託管的 Git 服務。
|
||||||
|
|
||||||
|
由於 Gitea 是用 Go 語言編寫的,它可以在 Go 支援的所有平台和架構上運行,包括 Linux、macOS 和 Windows 的 x86、amd64、ARM 和 PowerPC 架構。這個項目自 2016 年 11 月從 [Gogs](https://gogs.io) [分叉](https://blog.gitea.com/welcome-to-gitea/) 而來,但已經有了很多變化。
|
||||||
|
|
||||||
|
在線演示可以訪問 [demo.gitea.com](https://demo.gitea.com)。
|
||||||
|
|
||||||
|
要訪問免費的 Gitea 服務(有一定數量的倉庫限制),可以訪問 [gitea.com](https://gitea.com/user/login)。
|
||||||
|
|
||||||
|
要快速部署您自己的專用 Gitea 實例,可以在 [cloud.gitea.com](https://cloud.gitea.com) 開始免費試用。
|
||||||
|
|
||||||
|
## 文件
|
||||||
|
|
||||||
|
您可以在我們的官方 [文件網站](https://docs.gitea.com/) 上找到全面的文件。
|
||||||
|
|
||||||
|
它包括安裝、管理、使用、開發、貢獻指南等,幫助您快速入門並有效地探索所有功能。
|
||||||
|
|
||||||
|
如果您有任何建議或想要貢獻,可以訪問 [文件倉庫](https://gitea.com/gitea/docs)
|
||||||
|
|
||||||
|
## 構建
|
||||||
|
|
||||||
|
從源代碼樹的根目錄運行:
|
||||||
|
|
||||||
|
TAGS="bindata" make build
|
||||||
|
|
||||||
|
如果需要 SQLite 支援:
|
||||||
|
|
||||||
|
TAGS="bindata sqlite sqlite_unlock_notify" make build
|
||||||
|
|
||||||
|
`build` 目標分為兩個子目標:
|
||||||
|
|
||||||
|
- `make backend` 需要 [Go Stable](https://go.dev/dl/),所需版本在 [go.mod](/go.mod) 中定義。
|
||||||
|
- `make frontend` 需要 [Node.js LTS](https://nodejs.org/en/download/) 或更高版本。
|
||||||
|
|
||||||
|
需要互聯網連接來下載 go 和 npm 模塊。從包含預構建前端文件的官方源代碼壓縮包構建時,不會觸發 `frontend` 目標,因此可以在沒有 Node.js 的情況下構建。
|
||||||
|
|
||||||
|
更多信息:https://docs.gitea.com/installation/install-from-source
|
||||||
|
|
||||||
|
## 使用
|
||||||
|
|
||||||
|
構建後,默認情況下會在源代碼樹的根目錄生成一個名為 `gitea` 的二進制文件。要運行它,請使用:
|
||||||
|
|
||||||
|
./gitea web
|
||||||
|
|
||||||
|
> [!注意]
|
||||||
|
> 如果您對使用我們的 API 感興趣,我們提供了實驗性支援,並附有 [文件](https://docs.gitea.com/api)。
|
||||||
|
|
||||||
|
## 貢獻
|
||||||
|
|
||||||
|
預期的工作流程是:Fork -> Patch -> Push -> Pull Request
|
||||||
|
|
||||||
|
> [!注意]
|
||||||
|
>
|
||||||
|
> 1. **在開始進行 Pull Request 之前,您必須閱讀 [貢獻者指南](CONTRIBUTING.md)。**
|
||||||
|
> 2. 如果您在項目中發現了漏洞,請私下寫信給 **security@gitea.io**。謝謝!
|
||||||
|
|
||||||
|
## 翻譯
|
||||||
|
|
||||||
|
[](https://translate.gitea.com)
|
||||||
|
|
||||||
|
翻譯通過 [Crowdin](https://translate.gitea.com) 進行。如果您想翻譯成新的語言,請在 Crowdin 項目中請求管理員添加新語言。
|
||||||
|
|
||||||
|
您也可以創建一個 issue 來添加語言,或者在 discord 的 #translation 頻道上詢問。如果您需要上下文或發現一些翻譯問題,可以在字符串上留言或在 Discord 上詢問。對於一般的翻譯問題,文檔中有一個部分。目前有點空,但我們希望隨著問題的出現而填充它。
|
||||||
|
|
||||||
|
更多信息請參閱 [文件](https://docs.gitea.com/contributing/localization)。
|
||||||
|
|
||||||
|
## 官方和第三方項目
|
||||||
|
|
||||||
|
我們提供了一個官方的 [go-sdk](https://gitea.com/gitea/go-sdk),一個名為 [tea](https://gitea.com/gitea/tea) 的 CLI 工具和一個 Gitea Action 的 [action runner](https://gitea.com/gitea/act_runner)。
|
||||||
|
|
||||||
|
我們在 [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea) 維護了一個 Gitea 相關項目的列表,您可以在那裡發現更多的第三方項目,包括 SDK、插件、主題等。
|
||||||
|
|
||||||
|
## 通訊
|
||||||
|
|
||||||
|
[](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea")
|
||||||
|
|
||||||
|
如果您有任何文件未涵蓋的問題,可以在我們的 [Discord 服務器](https://discord.gg/Gitea) 上與我們聯繫,或者在 [discourse 論壇](https://forum.gitea.com/) 上創建帖子。
|
||||||
|
|
||||||
|
## 作者
|
||||||
|
|
||||||
|
- [維護者](https://github.com/orgs/go-gitea/people)
|
||||||
|
- [貢獻者](https://github.com/go-gitea/gitea/graphs/contributors)
|
||||||
|
- [翻譯者](options/locale/TRANSLATORS)
|
||||||
|
|
||||||
|
## 支持者
|
||||||
|
|
||||||
|
感謝所有支持者! 🙏 [[成為支持者](https://opencollective.com/gitea#backer)]
|
||||||
|
|
||||||
|
<a href="https://opencollective.com/gitea#backers" target="_blank"><img src="https://opencollective.com/gitea/backers.svg?width=890"></a>
|
||||||
|
|
||||||
|
## 贊助商
|
||||||
|
|
||||||
|
通過成為贊助商來支持這個項目。您的標誌將顯示在這裡,並帶有鏈接到您的網站。 [[成為贊助商](https://opencollective.com/gitea#sponsor)]
|
||||||
|
|
||||||
|
<a href="https://opencollective.com/gitea/sponsor/0/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/0/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/gitea/sponsor/1/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/1/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/gitea/sponsor/2/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/2/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/gitea/sponsor/3/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/3/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/gitea/sponsor/4/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/4/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/gitea/sponsor/5/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/5/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/gitea/sponsor/6/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/6/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/gitea/sponsor/7/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/7/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/gitea/sponsor/8/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/8/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/gitea/sponsor/9/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/9/avatar.svg"></a>
|
||||||
|
|
||||||
|
## 常見問題
|
||||||
|
|
||||||
|
**Gitea 怎麼發音?**
|
||||||
|
|
||||||
|
Gitea 的發音是 [/ɡɪ’ti:/](https://youtu.be/EM71-2uDAoY),就像 "gi-tea" 一樣,g 是硬音。
|
||||||
|
|
||||||
|
**為什麼這個項目沒有託管在 Gitea 實例上?**
|
||||||
|
|
||||||
|
我們正在 [努力](https://github.com/go-gitea/gitea/issues/1029)。
|
||||||
|
|
||||||
|
**在哪裡可以找到安全補丁?**
|
||||||
|
|
||||||
|
在 [發佈日誌](https://github.com/go-gitea/gitea/releases) 或 [變更日誌](https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md) 中,搜索關鍵詞 `SECURITY` 以找到安全補丁。
|
||||||
|
|
||||||
|
## 許可證
|
||||||
|
|
||||||
|
這個項目是根據 MIT 許可證授權的。
|
||||||
|
請參閱 [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) 文件以獲取完整的許可證文本。
|
||||||
|
|
||||||
|
## 進一步信息
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>尋找界面概述?查看這裡!</summary>
|
||||||
|
|
||||||
|
### 登錄/註冊頁面
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
### 用戶儀表板
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
### 用戶資料
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 探索
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
### 倉庫
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
#### 倉庫問題
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
#### 倉庫拉取請求
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
#### 倉庫操作
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
#### 倉庫活動
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
### 組織
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</details>
|
||||||
61
README_ZH.md
61
README_ZH.md
@@ -1,61 +0,0 @@
|
|||||||
# Gitea
|
|
||||||
|
|
||||||
[](https://github.com/go-gitea/gitea/actions/workflows/release-nightly.yml?query=branch%3Amain "Release Nightly")
|
|
||||||
[](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea")
|
|
||||||
[](https://goreportcard.com/report/code.gitea.io/gitea "Go Report Card")
|
|
||||||
[](https://pkg.go.dev/code.gitea.io/gitea "GoDoc")
|
|
||||||
[](https://github.com/go-gitea/gitea/releases/latest "GitHub release")
|
|
||||||
[](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source")
|
|
||||||
[](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
|
|
||||||
[](https://opensource.org/licenses/MIT "License: MIT")
|
|
||||||
[](https://gitpod.io/#https://github.com/go-gitea/gitea)
|
|
||||||
[](https://crowdin.com/project/gitea "Crowdin")
|
|
||||||
|
|
||||||
[View this document in English](./README.md)
|
|
||||||
|
|
||||||
## 目标
|
|
||||||
|
|
||||||
Gitea 的首要目标是创建一个极易安装,运行非常快速,安装和使用体验良好的自建 Git 服务。我们采用 Go 作为后端语言,这使我们只要生成一个可执行程序即可。并且他还支持跨平台,支持 Linux, macOS 和 Windows 以及各种架构,除了 x86,amd64,还包括 ARM 和 PowerPC。
|
|
||||||
|
|
||||||
如果你想试用在线演示和报告问题,请访问 [demo.gitea.com](https://demo.gitea.com/)。
|
|
||||||
|
|
||||||
如果你想使用免费的 Gitea 服务(有仓库数量限制),请访问 [gitea.com](https://gitea.com/user/login)。
|
|
||||||
|
|
||||||
如果你想在 Gitea Cloud 上快速部署你自己独享的 Gitea 实例,请访问 [cloud.gitea.com](https://cloud.gitea.com) 开始免费试用。
|
|
||||||
|
|
||||||
## 提示
|
|
||||||
|
|
||||||
1. **开始贡献代码之前请确保你已经看过了 [贡献者向导(英文)](CONTRIBUTING.md)**.
|
|
||||||
2. 所有的安全问题,请私下发送邮件给 **security@gitea.io**。谢谢!
|
|
||||||
3. 如果你要使用API,请参见 [API 文档](https://godoc.org/code.gitea.io/sdk/gitea).
|
|
||||||
|
|
||||||
## 文档
|
|
||||||
|
|
||||||
关于如何安装请访问我们的 [文档站](https://docs.gitea.com/zh-cn/category/installation),如果没有找到对应的文档,你也可以通过 [Discord - 英文](https://discord.gg/gitea) 和 QQ群 328432459 来和我们交流。
|
|
||||||
|
|
||||||
## 贡献流程
|
|
||||||
|
|
||||||
Fork -> Patch -> Push -> Pull Request
|
|
||||||
|
|
||||||
## 翻译
|
|
||||||
|
|
||||||
多语言翻译是基于Crowdin进行的.
|
|
||||||
[](https://crowdin.com/project/gitea)
|
|
||||||
|
|
||||||
## 作者
|
|
||||||
|
|
||||||
* [Maintainers](https://github.com/orgs/go-gitea/people)
|
|
||||||
* [Contributors](https://github.com/go-gitea/gitea/graphs/contributors)
|
|
||||||
* [Translators](options/locale/TRANSLATORS)
|
|
||||||
|
|
||||||
## 授权许可
|
|
||||||
|
|
||||||
本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) 文件中。
|
|
||||||
|
|
||||||
## 截图
|
|
||||||
|
|
||||||
||||
|
|
||||||
|:---:|:---:|:---:|
|
|
||||||
||||
|
|
||||||
||||
|
|
||||||
||||
|
|
||||||
54
SECURITY.md
54
SECURITY.md
@@ -14,12 +14,12 @@ Please **DO NOT** file a public issue, instead send your report privately to `se
|
|||||||
|
|
||||||
Due to the sensitive nature of security information, you can use the below GPG public key to encrypt your mail body.
|
Due to the sensitive nature of security information, you can use the below GPG public key to encrypt your mail body.
|
||||||
|
|
||||||
The PGP key is valid until July 9, 2025.
|
The PGP key is valid until July 4, 2026.
|
||||||
|
|
||||||
```
|
```
|
||||||
Key ID: 6FCD2D5B
|
Key ID: 6FCD2D5B
|
||||||
Key Type: RSA
|
Key Type: RSA
|
||||||
Expires: 7/9/2025
|
Expires: 7/4/2026
|
||||||
Key Size: 4096/4096
|
Key Size: 4096/4096
|
||||||
Fingerprint: 3DE0 3D1E 144A 7F06 9359 99DC AAFD 2381 6FCD 2D5B
|
Fingerprint: 3DE0 3D1E 144A 7F06 9359 99DC AAFD 2381 6FCD 2D5B
|
||||||
```
|
```
|
||||||
@@ -42,18 +42,18 @@ lzpAjnN9/KLtQroutrm+Ft0mdjDiJUeFVl1cOHDhoyfCsQh62HumoyZoZvqzQd6e
|
|||||||
AbN11nq6aViMe2Q3je1AbiBnRnQSHxt1Tc8X4IshO3MQK1Sk7oPI6LA5oQARAQAB
|
AbN11nq6aViMe2Q3je1AbiBnRnQSHxt1Tc8X4IshO3MQK1Sk7oPI6LA5oQARAQAB
|
||||||
tCJHaXRlYSBTZWN1cml0eSA8c2VjdXJpdHlAZ2l0ZWEuaW8+iQJXBBMBCABBAhsD
|
tCJHaXRlYSBTZWN1cml0eSA8c2VjdXJpdHlAZ2l0ZWEuaW8+iQJXBBMBCABBAhsD
|
||||||
BQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAFiEEPeA9HhRKfwaTWZncqv0jgW/N
|
BQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAFiEEPeA9HhRKfwaTWZncqv0jgW/N
|
||||||
LVsFAmaMse0FCQW4fW8ACgkQqv0jgW/NLVtXLg/+PF4G9Jhlui15BTNlEBJAV2P/
|
LVsFAmhoHmkFCQeT6esACgkQqv0jgW/NLVuFLRAAmjBQSKRAgs2bFIEj7HLAbDp4
|
||||||
1QlAV2krk0fP7tykn0FR9RfGIfVV/kwC1f+ouosYPQDDevl9LWdUIM+g94DtNo2o
|
f+XkdH+GsT3jRPOZ9QZgmtM+TfoE4yNgIVfOl+s4RdjM/W4QzqZuPQ55hbEHd056
|
||||||
7ACpcL3morvt5lVGpIZHL8TbX0qmFRXL/pB/cB+K6IwYvh2mrbp2zH+r4SCRyFYq
|
cJmm7B+6GsHFcdrPmh65sOCEIyh4+t45dUfeWpFsDPqm9j1UHXAJQIpB8vDEVAPH
|
||||||
BjgXYFTI1MylJ1ShAjU6Z+m3oJ+2xs5LzHS0X6zkTjzA2Zl4zQzciQ9T+wJcE7Zi
|
t+3wLCk8GMPJs1o5tIyMmaO23ngvkwn8eG7KgY+rp2PzObrb5g7ppci0ILzILkrp
|
||||||
HXdM1+YMF8KGNP8J9Rpug5oNDJ98lgZirRY7c3A/1xmYBiPnULwuuymdqEZO7l70
|
HVjZsEfUWRgSVF7LuU5ppqDKrlcqwUpQq6n3kGMZcLrCp6ACKP04TBmTfUxNwdL7
|
||||||
SeAlE1RWYX8kbOBnBb/KY4XwE3Vic1oEzc9DiPWVH1ElX86WNNsFzuyULiwoBoWg
|
I0N7apI2Pbct9T1Gv/lYAUFWyU2c3gh/EBLbO6BukaLOFRQHrtNfdJV/YnMPlcXr
|
||||||
pqZGhL9x1p5+46RGQSDczsHM7YGVtfYOiDo2PAVrmwsT0BnXnK8Oe3YIkvmUPEJu
|
LUJjK9K4eAH9DsrZqrisz/LthsC2BaNIN3KRMTk5YTYgmIh8GXzSgihORmtDFELC
|
||||||
OkLt0Z6A5n8pz8zhQzuApwBsK4ncJ8zTCpvz/pfKKqZC/Vnoh3gKGhDGvOZ+b5IJ
|
RroID3pTuS0zjXh+wpY9GuPTh7UW23p42Daxca4fAT4k5EclvDRUrL21xMopPMiL
|
||||||
0kUTe2JsbnwFixDUMDtacQ1op8XOyLoLVmgqLn0+Pws4XPBlMof2bioFir3yHKnP
|
HuNdELz4FVchRTy05PjzKVyjVInDNojE2KUxnjxZDzYJ6aT/g+coD5yfntYm8BEj
|
||||||
gNchsF1agrlSIo5GA8u4ga+IlCSfvFIKrl7+cxacKcJYt/vbOU5KcvVJI5EtHKCG
|
+ZzL0ndZES54hzKLpv7zwBQwFzam68clZYmDPILOPTflQDfpGEWmJK4undFU5obz
|
||||||
xfHjHY2ah1Qww7SxW6IXiRZZzPpsL2mBM2CD7N3qh9bV2s27wxYCdUodsIZbiyHe
|
ZsQRz0R3ulspChATbZxO0d5LX2obLpKO9X3b5VoO1KF+R8Vjw1Y0KxrNZ6rIcfqH
|
||||||
oWPzfBnkmiAN8KlZxHm5Ag0EYrVn/gEQALrFLQjCR3GjuHSindz0rd3Fnx/t7Sen
|
Z50QVQKSe9dm08K0ON+5Ag0EYrVn/gEQALrFLQjCR3GjuHSindz0rd3Fnx/t7Sen
|
||||||
T+p07yCSSoSlmnJHCQmwh4vfg1blyz0zZ4vkIhtpHsEgc+ZAG+WQXSsJ2iRz+eSN
|
T+p07yCSSoSlmnJHCQmwh4vfg1blyz0zZ4vkIhtpHsEgc+ZAG+WQXSsJ2iRz+eSN
|
||||||
GwoOQl4XC3n+QWkc1ws+btr48+6UqXIQU+F8TPQyx/PIgi2nZXJB7f5+mjCqsk46
|
GwoOQl4XC3n+QWkc1ws+btr48+6UqXIQU+F8TPQyx/PIgi2nZXJB7f5+mjCqsk46
|
||||||
XvH4nTr4kJjuqMSR/++wvre2qNQRa/q/dTsK0OaN/mJsdX6Oi+aGNaQJUhIG7F+E
|
XvH4nTr4kJjuqMSR/++wvre2qNQRa/q/dTsK0OaN/mJsdX6Oi+aGNaQJUhIG7F+E
|
||||||
@@ -65,19 +65,19 @@ s+GsP9I3cmWWQcKYxWHtE8xTXnNCVPFZQj2nwhJzae8ypfOtulBRA3dUKWGKuDH/
|
|||||||
axFENhUsT397aOU3qkP/od4a64JyNIEo4CTTSPVeWd7njsGqli2U3A4xL2CcyYvt
|
axFENhUsT397aOU3qkP/od4a64JyNIEo4CTTSPVeWd7njsGqli2U3A4xL2CcyYvt
|
||||||
D/MWcMBGEoLSNTswwKdom4FaJpn5KThnK/T0bQcmJblJhoCtppXisbexZnCpuS0x
|
D/MWcMBGEoLSNTswwKdom4FaJpn5KThnK/T0bQcmJblJhoCtppXisbexZnCpuS0x
|
||||||
Zdlm2T14KJ3LABEBAAGJAjwEGAEIACYCGwwWIQQ94D0eFEp/BpNZmdyq/SOBb80t
|
Zdlm2T14KJ3LABEBAAGJAjwEGAEIACYCGwwWIQQ94D0eFEp/BpNZmdyq/SOBb80t
|
||||||
WwUCZoyyjQUJBbh+DwAKCRCq/SOBb80tW18XD/9MXztmf01MT+1kZdBouZ/7Rp/7
|
WwUCaGgeJAUJB5PppgAKCRCq/SOBb80tW/NWEACB6Jrf0gWlk7e+hNCdnbM0ZVWU
|
||||||
9kuqo//B1G+RXau4oFtPqb67kNe2WaIc3u5B73PUHsMf3i6z4ib2KbMhZZerLn0O
|
f2sHNFfXxxsdhpcDgKbNHtkZb8nZgv8AX+5fTtUwMVa3vKcdw30xFiIM5N7cCIPV
|
||||||
dRglcuPeNWmsASY3dH/XVG0cT0zvvWegagd12TJEl3Vs+7XNrOw4cwDj9L1+GH9m
|
vg/5z5BtfEaitnabEUG2iiVDIy8IHXIcK10rX+7BosA3QDl2PsiBHwyi5G13lRk8
|
||||||
kSt4uaANWn/6a3RvMRhiVEYuNwhAzcKaactPmYqrLJgoVLbRSDkgyHaMQ2jKgLxk
|
zGTSNDuOalug33h5/lr2dPigamkq74Aoy29q8Rjad6GfWHipL2bFimgtY+Zdi0BH
|
||||||
ifS/fvluGV0ub2Po6DJiqfRpd1tDvPhe9y1+r1WFDZsOcvTcZUfSt/7dXMGfqGu0
|
NLk4EJXxj1SgVx5dtkQzWJReBA5M+FQ4QYQZBO+f4TDoOLmjui152uhkoLBQbGAa
|
||||||
2daVFlfeSXSALrDE5uc0UxodHCpP3sqRYDZevGLBRaaTkIjYXG/+N898+7K5WJF4
|
WWJFTVxm0bG5MXloEL3gA8DfU7XDwuW/sHJC5pBko8RpQViooOhckMepZV3Y83DK
|
||||||
xXOLWxM2cwGkG7eC9pugcDnBp9XlF7O+GBiZ05JUe5flXDQFZ+h3exjopu6KHF1B
|
bwLYa3JmPgj2rEv4993dvrJbQhpGd082HOxOsllCs8pgNq1SnXpWYfcGTgGKC3ts
|
||||||
RnzNy8LC0UKb+AuvRIOLV92a9Q9wGWU/jaVDu6nZ0umAeuSzxiHoDsonm0Fl9QAz
|
U8YZUUJUQ7mi2L8Tv3ix20c9EiGmA30JAmA8eZTC3cWup91ZkkVBFRml2czTXajd
|
||||||
2/xCokebuoeLrEK7R2af3X86mqq3sVO4ax+HPYChzOaVQBiHUW/TAldWcldYYphR
|
RWZ6GbHV5503ueDQcB8yBVgF3CSixs67+dGSbD3p86OqGrjAcJzM5TFbNKcnGLdE
|
||||||
/e2WsbmQfvCRtz/bZfo+aUVnrHNjzVMtF2SszdVmA/04Y8pS28MqtuRqhm5DPOOd
|
kGbZpNwAISy750lXzXKmyrh5RTCeTOQerbwCMBvHZO+HAevA/LXDTw2OAiSIQlP5
|
||||||
g1YeUywK5jRZ1twyo1kzJEFPLaoeaXaycsR1PMVBW0Urik5mrR/pOWq7PPoZoKb2
|
sYA4sFYLQ30OAkgJcmdp/pSgVj/erNtSN07ClrOpDb/uFpQymO6K2h0Pst3feNVK
|
||||||
lXYLE8bwkuQTmsyL1g==
|
9M2VbqL9C51z/wyHLg==
|
||||||
=9i7d
|
=SfZA
|
||||||
-----END PGP PUBLIC KEY BLOCK-----
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
229
assets/go-licenses.json
generated
229
assets/go-licenses.json
generated
File diff suppressed because one or more lines are too long
11
build.go
11
build.go
@@ -5,19 +5,10 @@
|
|||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
// Libraries that are included to vendor utilities used during build.
|
// Libraries that are included to vendor utilities used during Makefile build.
|
||||||
// These libraries will not be included in a normal compilation.
|
// These libraries will not be included in a normal compilation.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// for embed
|
|
||||||
_ "github.com/shurcooL/vfsgen"
|
|
||||||
|
|
||||||
// for cover merge
|
|
||||||
_ "golang.org/x/tools/cover"
|
|
||||||
|
|
||||||
// for vet
|
// for vet
|
||||||
_ "code.gitea.io/gitea-vet"
|
_ "code.gitea.io/gitea-vet"
|
||||||
|
|
||||||
// for swagger
|
|
||||||
_ "github.com/go-swagger/go-swagger/cmd/swagger"
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ func parseArgs() (mainOptions map[string]string, subCmd string, subArgs []string
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return mainOptions, subCmd, subArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
func showUsage() {
|
func showUsage() {
|
||||||
|
|||||||
@@ -6,87 +6,22 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/sha1"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/shurcooL/vfsgen"
|
"code.gitea.io/gitea/modules/assetfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func needsUpdate(dir, filename string) (bool, []byte) {
|
|
||||||
needRegen := false
|
|
||||||
_, err := os.Stat(filename)
|
|
||||||
if err != nil {
|
|
||||||
needRegen = true
|
|
||||||
}
|
|
||||||
|
|
||||||
oldHash, err := os.ReadFile(filename + ".hash")
|
|
||||||
if err != nil {
|
|
||||||
oldHash = []byte{}
|
|
||||||
}
|
|
||||||
|
|
||||||
hasher := sha1.New()
|
|
||||||
|
|
||||||
err = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
info, err := d.Info()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, _ = hasher.Write([]byte(d.Name()))
|
|
||||||
_, _ = hasher.Write([]byte(info.ModTime().String()))
|
|
||||||
_, _ = hasher.Write([]byte(strconv.FormatInt(info.Size(), 16)))
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return true, oldHash
|
|
||||||
}
|
|
||||||
|
|
||||||
newHash := hasher.Sum([]byte{})
|
|
||||||
|
|
||||||
if bytes.Compare(oldHash, newHash) != 0 {
|
|
||||||
return true, newHash
|
|
||||||
}
|
|
||||||
|
|
||||||
return needRegen, newHash
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) < 4 {
|
if len(os.Args) != 3 {
|
||||||
log.Fatal("Insufficient number of arguments. Need: directory packageName filename")
|
fmt.Println("usage: ./generate-bindata {local-directory} {bindata-filename}")
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
dir, packageName, filename := os.Args[1], os.Args[2], os.Args[3]
|
dir, filename := os.Args[1], os.Args[2]
|
||||||
var useGlobalModTime bool
|
fmt.Printf("generating bindata for %s to %s\n", dir, filename)
|
||||||
if len(os.Args) == 5 {
|
if err := assetfs.GenerateEmbedBindata(dir, filename); err != nil {
|
||||||
useGlobalModTime, _ = strconv.ParseBool(os.Args[4])
|
fmt.Printf("failed: %s\n", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
update, newHash := needsUpdate(dir, filename)
|
|
||||||
|
|
||||||
if !update {
|
|
||||||
fmt.Printf("bindata for %s already up-to-date\n", packageName)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("generating bindata for %s\n", packageName)
|
|
||||||
var fsTemplates http.FileSystem = http.Dir(dir)
|
|
||||||
err := vfsgen.Generate(fsTemplates, vfsgen.Options{
|
|
||||||
PackageName: packageName,
|
|
||||||
BuildTags: "bindata",
|
|
||||||
VariableName: "Assets",
|
|
||||||
Filename: filename,
|
|
||||||
UseGlobalModTime: useGlobalModTime,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("%v\n", err)
|
|
||||||
}
|
|
||||||
_ = os.WriteFile(filename+".hash", newHash, 0o666)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,176 +0,0 @@
|
|||||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
//go:build ignore
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/tar"
|
|
||||||
"compress/gzip"
|
|
||||||
"crypto/md5"
|
|
||||||
"encoding/hex"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/build/license"
|
|
||||||
"code.gitea.io/gitea/modules/json"
|
|
||||||
"code.gitea.io/gitea/modules/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var (
|
|
||||||
prefix = "gitea-licenses"
|
|
||||||
url = "https://api.github.com/repos/spdx/license-list-data/tarball"
|
|
||||||
githubApiToken = ""
|
|
||||||
githubUsername = ""
|
|
||||||
destination = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
flag.StringVar(&destination, "dest", "options/license/", "destination for the licenses")
|
|
||||||
flag.StringVar(&githubUsername, "username", "", "github username")
|
|
||||||
flag.StringVar(&githubApiToken, "token", "", "github api token")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
file, err := os.CreateTemp(os.TempDir(), prefix)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to create temp file. %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer util.Remove(file.Name())
|
|
||||||
|
|
||||||
if err := os.RemoveAll(destination); err != nil {
|
|
||||||
log.Fatalf("Cannot clean destination folder: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.MkdirAll(destination, 0o755); err != nil {
|
|
||||||
log.Fatalf("Cannot create destination: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to download archive. %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(githubApiToken) > 0 && len(githubUsername) > 0 {
|
|
||||||
req.SetBasicAuth(githubUsername, githubApiToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to download archive. %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if _, err := io.Copy(file, resp.Body); err != nil {
|
|
||||||
log.Fatalf("Failed to copy archive to file. %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := file.Seek(0, 0); err != nil {
|
|
||||||
log.Fatalf("Failed to reset seek on archive. %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
gz, err := gzip.NewReader(file)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to gunzip the archive. %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tr := tar.NewReader(gz)
|
|
||||||
aliasesFiles := make(map[string][]string)
|
|
||||||
for {
|
|
||||||
hdr, err := tr.Next()
|
|
||||||
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to iterate archive. %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.Contains(hdr.Name, "/text/") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if filepath.Ext(hdr.Name) != ".txt" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fileBaseName := filepath.Base(hdr.Name)
|
|
||||||
licenseName := strings.TrimSuffix(fileBaseName, ".txt")
|
|
||||||
|
|
||||||
if strings.HasPrefix(fileBaseName, "README") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(fileBaseName, "deprecated_") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
out, err := os.Create(path.Join(destination, licenseName))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to create new file. %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer out.Close()
|
|
||||||
|
|
||||||
// some license files have same content, so we need to detect these files and create a convert map into a json file
|
|
||||||
// Later we use this convert map to avoid adding same license content with different license name
|
|
||||||
h := md5.New()
|
|
||||||
// calculate md5 and write file in the same time
|
|
||||||
r := io.TeeReader(tr, h)
|
|
||||||
if _, err := io.Copy(out, r); err != nil {
|
|
||||||
log.Fatalf("Failed to write new file. %s", err)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("Written %s\n", out.Name())
|
|
||||||
|
|
||||||
md5 := hex.EncodeToString(h.Sum(nil))
|
|
||||||
aliasesFiles[md5] = append(aliasesFiles[md5], licenseName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate convert license name map
|
|
||||||
licenseAliases := make(map[string]string)
|
|
||||||
for _, fileNames := range aliasesFiles {
|
|
||||||
if len(fileNames) > 1 {
|
|
||||||
licenseName := license.GetLicenseNameFromAliases(fileNames)
|
|
||||||
if licenseName == "" {
|
|
||||||
// license name should not be empty as expected
|
|
||||||
// if it is empty, we need to rewrite the logic of GetLicenseNameFromAliases
|
|
||||||
log.Fatalf("GetLicenseNameFromAliases: license name is empty")
|
|
||||||
}
|
|
||||||
for _, fileName := range fileNames {
|
|
||||||
licenseAliases[fileName] = licenseName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// save convert license name map to file
|
|
||||||
b, err := json.Marshal(licenseAliases)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to create json bytes. %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
licenseAliasesDestination := filepath.Join(destination, "etc", "license-aliases.json")
|
|
||||||
if err := os.MkdirAll(filepath.Dir(licenseAliasesDestination), 0o755); err != nil {
|
|
||||||
log.Fatalf("Failed to create directory for license aliases json file. %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Create(licenseAliasesDestination)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to create license aliases json file. %s", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
if _, err = f.Write(b); err != nil {
|
|
||||||
log.Fatalf("Failed to write license aliases json file. %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Done")
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package license
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
func GetLicenseNameFromAliases(fnl []string) string {
|
|
||||||
if len(fnl) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
shortestItem := func(list []string) string {
|
|
||||||
s := list[0]
|
|
||||||
for _, l := range list[1:] {
|
|
||||||
if len(l) < len(s) {
|
|
||||||
s = l
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
allHasPrefix := func(list []string, s string) bool {
|
|
||||||
for _, l := range list {
|
|
||||||
if !strings.HasPrefix(l, s) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
sl := shortestItem(fnl)
|
|
||||||
slv := strings.Split(sl, "-")
|
|
||||||
var result string
|
|
||||||
for i := len(slv); i >= 0; i-- {
|
|
||||||
result = strings.Join(slv[:i], "-")
|
|
||||||
if allHasPrefix(fnl, result) {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package license
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetLicenseNameFromAliases(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
target string
|
|
||||||
inputs []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
// real case which you can find in license-aliases.json
|
|
||||||
target: "AGPL-1.0",
|
|
||||||
inputs: []string{
|
|
||||||
"AGPL-1.0-only",
|
|
||||||
"AGPL-1.0-or-late",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
target: "",
|
|
||||||
inputs: []string{
|
|
||||||
"APSL-1.0",
|
|
||||||
"AGPL-1.0-only",
|
|
||||||
"AGPL-1.0-or-late",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
result := GetLicenseNameFromAliases(tt.inputs)
|
|
||||||
assert.Equal(t, result, tt.target)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,12 +4,13 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/private"
|
"code.gitea.io/gitea/modules/private"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -17,7 +18,7 @@ var (
|
|||||||
CmdActions = &cli.Command{
|
CmdActions = &cli.Command{
|
||||||
Name: "actions",
|
Name: "actions",
|
||||||
Usage: "Manage Gitea Actions",
|
Usage: "Manage Gitea Actions",
|
||||||
Subcommands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
subcmdActionsGenRunnerToken,
|
subcmdActionsGenRunnerToken,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -38,10 +39,7 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func runGenerateActionsRunnerToken(c *cli.Context) error {
|
func runGenerateActionsRunnerToken(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
setting.MustInstalled()
|
setting.MustInstalled()
|
||||||
|
|
||||||
scope := c.String("scope")
|
scope := c.String("scope")
|
||||||
|
|||||||
45
cmd/admin.go
45
cmd/admin.go
@@ -15,7 +15,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
repo_module "code.gitea.io/gitea/modules/repository"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -23,7 +23,7 @@ var (
|
|||||||
CmdAdmin = &cli.Command{
|
CmdAdmin = &cli.Command{
|
||||||
Name: "admin",
|
Name: "admin",
|
||||||
Usage: "Perform common administrative operations",
|
Usage: "Perform common administrative operations",
|
||||||
Subcommands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
subcmdUser,
|
subcmdUser,
|
||||||
subcmdRepoSyncReleases,
|
subcmdRepoSyncReleases,
|
||||||
subcmdRegenerate,
|
subcmdRegenerate,
|
||||||
@@ -41,7 +41,7 @@ var (
|
|||||||
subcmdRegenerate = &cli.Command{
|
subcmdRegenerate = &cli.Command{
|
||||||
Name: "regenerate",
|
Name: "regenerate",
|
||||||
Usage: "Regenerate specific files",
|
Usage: "Regenerate specific files",
|
||||||
Subcommands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
microcmdRegenHooks,
|
microcmdRegenHooks,
|
||||||
microcmdRegenKeys,
|
microcmdRegenKeys,
|
||||||
},
|
},
|
||||||
@@ -50,15 +50,15 @@ var (
|
|||||||
subcmdAuth = &cli.Command{
|
subcmdAuth = &cli.Command{
|
||||||
Name: "auth",
|
Name: "auth",
|
||||||
Usage: "Modify external auth providers",
|
Usage: "Modify external auth providers",
|
||||||
Subcommands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
microcmdAuthAddOauth,
|
microcmdAuthAddOauth(),
|
||||||
microcmdAuthUpdateOauth,
|
microcmdAuthUpdateOauth(),
|
||||||
microcmdAuthAddLdapBindDn,
|
microcmdAuthAddLdapBindDn(),
|
||||||
microcmdAuthUpdateLdapBindDn,
|
microcmdAuthUpdateLdapBindDn(),
|
||||||
microcmdAuthAddLdapSimpleAuth,
|
microcmdAuthAddLdapSimpleAuth(),
|
||||||
microcmdAuthUpdateLdapSimpleAuth,
|
microcmdAuthUpdateLdapSimpleAuth(),
|
||||||
microcmdAuthAddSMTP,
|
microcmdAuthAddSMTP(),
|
||||||
microcmdAuthUpdateSMTP,
|
microcmdAuthUpdateSMTP(),
|
||||||
microcmdAuthList,
|
microcmdAuthList,
|
||||||
microcmdAuthDelete,
|
microcmdAuthDelete,
|
||||||
},
|
},
|
||||||
@@ -70,9 +70,9 @@ var (
|
|||||||
Action: runSendMail,
|
Action: runSendMail,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "title",
|
Name: "title",
|
||||||
Usage: `a title of a message`,
|
Usage: "a title of a message",
|
||||||
Value: "",
|
Required: true,
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "content",
|
Name: "content",
|
||||||
@@ -86,28 +86,27 @@ var (
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
idFlag = &cli.Int64Flag{
|
func idFlag() *cli.Int64Flag {
|
||||||
|
return &cli.Int64Flag{
|
||||||
Name: "id",
|
Name: "id",
|
||||||
Usage: "ID of authentication source",
|
Usage: "ID of authentication source",
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
func runRepoSyncReleases(_ *cli.Context) error {
|
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
|
func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error {
|
||||||
if err := initDB(ctx); err != nil {
|
if err := initDB(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := git.InitSimple(ctx); err != nil {
|
if err := git.InitSimple(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Trace("Synchronizing repository releases (this may take a while)")
|
log.Trace("Synchronizing repository releases (this may take a while)")
|
||||||
for page := 1; ; page++ {
|
for page := 1; ; page++ {
|
||||||
repos, count, err := repo_model.SearchRepositoryByName(ctx, &repo_model.SearchRepoOptions{
|
repos, count, err := repo_model.SearchRepositoryByName(ctx, repo_model.SearchRepoOptions{
|
||||||
ListOptions: db.ListOptions{
|
ListOptions: db.ListOptions{
|
||||||
PageSize: repo_model.RepositoryListDefaultPageSize,
|
PageSize: repo_model.RepositoryListDefaultPageSize,
|
||||||
Page: page,
|
Page: page,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@@ -13,14 +14,14 @@ import (
|
|||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
auth_service "code.gitea.io/gitea/services/auth"
|
auth_service "code.gitea.io/gitea/services/auth"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
microcmdAuthDelete = &cli.Command{
|
microcmdAuthDelete = &cli.Command{
|
||||||
Name: "delete",
|
Name: "delete",
|
||||||
Usage: "Delete specific auth source",
|
Usage: "Delete specific auth source",
|
||||||
Flags: []cli.Flag{idFlag},
|
Flags: []cli.Flag{idFlag()},
|
||||||
Action: runDeleteAuth,
|
Action: runDeleteAuth,
|
||||||
}
|
}
|
||||||
microcmdAuthList = &cli.Command{
|
microcmdAuthList = &cli.Command{
|
||||||
@@ -56,10 +57,7 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func runListAuth(c *cli.Context) error {
|
func runListAuth(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
if err := initDB(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -90,14 +88,11 @@ func runListAuth(c *cli.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDeleteAuth(c *cli.Context) error {
|
func runDeleteAuth(ctx context.Context, c *cli.Command) error {
|
||||||
if !c.IsSet("id") {
|
if !c.IsSet("id") {
|
||||||
return errors.New("--id flag is missing")
|
return errors.New("--id flag is missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
if err := initDB(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/auth"
|
"code.gitea.io/gitea/models/auth"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/services/auth/source/ldap"
|
"code.gitea.io/gitea/services/auth/source/ldap"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -23,8 +24,8 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func commonLdapCLIFlags() []cli.Flag {
|
||||||
commonLdapCLIFlags = []cli.Flag{
|
return []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "name",
|
Name: "name",
|
||||||
Usage: "Authentication name.",
|
Usage: "Authentication name.",
|
||||||
@@ -102,8 +103,10 @@ var (
|
|||||||
Usage: "The attribute of the user’s LDAP record containing the user’s avatar.",
|
Usage: "The attribute of the user’s LDAP record containing the user’s avatar.",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ldapBindDnCLIFlags = append(commonLdapCLIFlags,
|
func ldapBindDnCLIFlags() []cli.Flag {
|
||||||
|
return append(commonLdapCLIFlags(),
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "bind-dn",
|
Name: "bind-dn",
|
||||||
Usage: "The DN to bind to the LDAP server with when searching for the user.",
|
Usage: "The DN to bind to the LDAP server with when searching for the user.",
|
||||||
@@ -127,50 +130,88 @@ var (
|
|||||||
&cli.UintFlag{
|
&cli.UintFlag{
|
||||||
Name: "page-size",
|
Name: "page-size",
|
||||||
Usage: "Search page size.",
|
Usage: "Search page size.",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "enable-groups",
|
||||||
|
Usage: "Enable LDAP groups",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "group-search-base-dn",
|
||||||
|
Usage: "The LDAP base DN at which group accounts will be searched for",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "group-member-attribute",
|
||||||
|
Usage: "Group attribute containing list of users",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "group-user-attribute",
|
||||||
|
Usage: "User attribute listed in group",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "group-filter",
|
||||||
|
Usage: "Verify group membership in LDAP",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "group-team-map",
|
||||||
|
Usage: "Map LDAP groups to Organization teams",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "group-team-map-removal",
|
||||||
|
Usage: "Remove users from synchronized teams if user does not belong to corresponding LDAP group",
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
ldapSimpleAuthCLIFlags = append(commonLdapCLIFlags,
|
func ldapSimpleAuthCLIFlags() []cli.Flag {
|
||||||
|
return append(commonLdapCLIFlags(),
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "user-dn",
|
Name: "user-dn",
|
||||||
Usage: "The user's DN.",
|
Usage: "The user's DN.",
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
microcmdAuthAddLdapBindDn = &cli.Command{
|
func microcmdAuthAddLdapBindDn() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "add-ldap",
|
Name: "add-ldap",
|
||||||
Usage: "Add new LDAP (via Bind DN) authentication source",
|
Usage: "Add new LDAP (via Bind DN) authentication source",
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||||
return newAuthService().addLdapBindDn(c)
|
return newAuthService().addLdapBindDn(ctx, cmd)
|
||||||
},
|
},
|
||||||
Flags: ldapBindDnCLIFlags,
|
Flags: ldapBindDnCLIFlags(),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
microcmdAuthUpdateLdapBindDn = &cli.Command{
|
func microcmdAuthUpdateLdapBindDn() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "update-ldap",
|
Name: "update-ldap",
|
||||||
Usage: "Update existing LDAP (via Bind DN) authentication source",
|
Usage: "Update existing LDAP (via Bind DN) authentication source",
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||||
return newAuthService().updateLdapBindDn(c)
|
return newAuthService().updateLdapBindDn(ctx, cmd)
|
||||||
},
|
},
|
||||||
Flags: append([]cli.Flag{idFlag}, ldapBindDnCLIFlags...),
|
Flags: append([]cli.Flag{idFlag()}, ldapBindDnCLIFlags()...),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
microcmdAuthAddLdapSimpleAuth = &cli.Command{
|
func microcmdAuthAddLdapSimpleAuth() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "add-ldap-simple",
|
Name: "add-ldap-simple",
|
||||||
Usage: "Add new LDAP (simple auth) authentication source",
|
Usage: "Add new LDAP (simple auth) authentication source",
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||||
return newAuthService().addLdapSimpleAuth(c)
|
return newAuthService().addLdapSimpleAuth(ctx, cmd)
|
||||||
},
|
},
|
||||||
Flags: ldapSimpleAuthCLIFlags,
|
Flags: ldapSimpleAuthCLIFlags(),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
microcmdAuthUpdateLdapSimpleAuth = &cli.Command{
|
func microcmdAuthUpdateLdapSimpleAuth() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "update-ldap-simple",
|
Name: "update-ldap-simple",
|
||||||
Usage: "Update existing LDAP (simple auth) authentication source",
|
Usage: "Update existing LDAP (simple auth) authentication source",
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||||
return newAuthService().updateLdapSimpleAuth(c)
|
return newAuthService().updateLdapSimpleAuth(ctx, cmd)
|
||||||
},
|
},
|
||||||
Flags: append([]cli.Flag{idFlag}, ldapSimpleAuthCLIFlags...),
|
Flags: append([]cli.Flag{idFlag()}, ldapSimpleAuthCLIFlags()...),
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
// newAuthService creates a service with default functions.
|
// newAuthService creates a service with default functions.
|
||||||
func newAuthService() *authService {
|
func newAuthService() *authService {
|
||||||
@@ -182,8 +223,8 @@ func newAuthService() *authService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseAuthSource assigns values on authSource according to command line flags.
|
// parseAuthSourceLdap assigns values on authSource according to command line flags.
|
||||||
func parseAuthSource(c *cli.Context, authSource *auth.Source) {
|
func parseAuthSourceLdap(c *cli.Command, authSource *auth.Source) {
|
||||||
if c.IsSet("name") {
|
if c.IsSet("name") {
|
||||||
authSource.Name = c.String("name")
|
authSource.Name = c.String("name")
|
||||||
}
|
}
|
||||||
@@ -199,10 +240,11 @@ func parseAuthSource(c *cli.Context, authSource *auth.Source) {
|
|||||||
if c.IsSet("disable-synchronize-users") {
|
if c.IsSet("disable-synchronize-users") {
|
||||||
authSource.IsSyncEnabled = !c.Bool("disable-synchronize-users")
|
authSource.IsSyncEnabled = !c.Bool("disable-synchronize-users")
|
||||||
}
|
}
|
||||||
|
authSource.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseLdapConfig assigns values on config according to command line flags.
|
// parseLdapConfig assigns values on config according to command line flags.
|
||||||
func parseLdapConfig(c *cli.Context, config *ldap.Source) error {
|
func parseLdapConfig(c *cli.Command, config *ldap.Source) error {
|
||||||
if c.IsSet("name") {
|
if c.IsSet("name") {
|
||||||
config.Name = c.String("name")
|
config.Name = c.String("name")
|
||||||
}
|
}
|
||||||
@@ -215,7 +257,7 @@ func parseLdapConfig(c *cli.Context, config *ldap.Source) error {
|
|||||||
if c.IsSet("security-protocol") {
|
if c.IsSet("security-protocol") {
|
||||||
p, ok := findLdapSecurityProtocolByName(c.String("security-protocol"))
|
p, ok := findLdapSecurityProtocolByName(c.String("security-protocol"))
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("Unknown security protocol name: %s", c.String("security-protocol"))
|
return fmt.Errorf("unknown security protocol name: %s", c.String("security-protocol"))
|
||||||
}
|
}
|
||||||
config.SecurityProtocol = p
|
config.SecurityProtocol = p
|
||||||
}
|
}
|
||||||
@@ -270,8 +312,26 @@ func parseLdapConfig(c *cli.Context, config *ldap.Source) error {
|
|||||||
if c.IsSet("allow-deactivate-all") {
|
if c.IsSet("allow-deactivate-all") {
|
||||||
config.AllowDeactivateAll = c.Bool("allow-deactivate-all")
|
config.AllowDeactivateAll = c.Bool("allow-deactivate-all")
|
||||||
}
|
}
|
||||||
if c.IsSet("skip-local-2fa") {
|
if c.IsSet("enable-groups") {
|
||||||
config.SkipLocalTwoFA = c.Bool("skip-local-2fa")
|
config.GroupsEnabled = c.Bool("enable-groups")
|
||||||
|
}
|
||||||
|
if c.IsSet("group-search-base-dn") {
|
||||||
|
config.GroupDN = c.String("group-search-base-dn")
|
||||||
|
}
|
||||||
|
if c.IsSet("group-member-attribute") {
|
||||||
|
config.GroupMemberUID = c.String("group-member-attribute")
|
||||||
|
}
|
||||||
|
if c.IsSet("group-user-attribute") {
|
||||||
|
config.UserUID = c.String("group-user-attribute")
|
||||||
|
}
|
||||||
|
if c.IsSet("group-filter") {
|
||||||
|
config.GroupFilter = c.String("group-filter")
|
||||||
|
}
|
||||||
|
if c.IsSet("group-team-map") {
|
||||||
|
config.GroupTeamMap = c.String("group-team-map")
|
||||||
|
}
|
||||||
|
if c.IsSet("group-team-map-removal") {
|
||||||
|
config.GroupTeamMapRemoval = c.Bool("group-team-map-removal")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -289,32 +349,27 @@ func findLdapSecurityProtocolByName(name string) (ldap.SecurityProtocol, bool) {
|
|||||||
|
|
||||||
// getAuthSource gets the login source by its id defined in the command line flags.
|
// getAuthSource gets the login source by its id defined in the command line flags.
|
||||||
// It returns an error if the id is not set, does not match any source or if the source is not of expected type.
|
// It returns an error if the id is not set, does not match any source or if the source is not of expected type.
|
||||||
func (a *authService) getAuthSource(ctx context.Context, c *cli.Context, authType auth.Type) (*auth.Source, error) {
|
func (a *authService) getAuthSource(ctx context.Context, c *cli.Command, authType auth.Type) (*auth.Source, error) {
|
||||||
if err := argsSet(c, "id"); err != nil {
|
if err := argsSet(c, "id"); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
authSource, err := a.getAuthSourceByID(ctx, c.Int64("id"))
|
authSource, err := a.getAuthSourceByID(ctx, c.Int64("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if authSource.Type != authType {
|
if authSource.Type != authType {
|
||||||
return nil, fmt.Errorf("Invalid authentication type. expected: %s, actual: %s", authType.String(), authSource.Type.String())
|
return nil, fmt.Errorf("invalid authentication type. expected: %s, actual: %s", authType.String(), authSource.Type.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return authSource, nil
|
return authSource, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// addLdapBindDn adds a new LDAP via Bind DN authentication source.
|
// addLdapBindDn adds a new LDAP via Bind DN authentication source.
|
||||||
func (a *authService) addLdapBindDn(c *cli.Context) error {
|
func (a *authService) addLdapBindDn(ctx context.Context, c *cli.Command) error {
|
||||||
if err := argsSet(c, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute"); err != nil {
|
if err := argsSet(c, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := a.initDB(ctx); err != nil {
|
if err := a.initDB(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -327,7 +382,7 @@ func (a *authService) addLdapBindDn(c *cli.Context) error {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
parseAuthSource(c, authSource)
|
parseAuthSourceLdap(c, authSource)
|
||||||
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
|
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -336,10 +391,7 @@ func (a *authService) addLdapBindDn(c *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// updateLdapBindDn updates a new LDAP via Bind DN authentication source.
|
// updateLdapBindDn updates a new LDAP via Bind DN authentication source.
|
||||||
func (a *authService) updateLdapBindDn(c *cli.Context) error {
|
func (a *authService) updateLdapBindDn(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := a.initDB(ctx); err != nil {
|
if err := a.initDB(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -349,7 +401,7 @@ func (a *authService) updateLdapBindDn(c *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
parseAuthSource(c, authSource)
|
parseAuthSourceLdap(c, authSource)
|
||||||
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
|
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -358,14 +410,11 @@ func (a *authService) updateLdapBindDn(c *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// addLdapSimpleAuth adds a new LDAP (simple auth) authentication source.
|
// addLdapSimpleAuth adds a new LDAP (simple auth) authentication source.
|
||||||
func (a *authService) addLdapSimpleAuth(c *cli.Context) error {
|
func (a *authService) addLdapSimpleAuth(ctx context.Context, c *cli.Command) error {
|
||||||
if err := argsSet(c, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute"); err != nil {
|
if err := argsSet(c, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := a.initDB(ctx); err != nil {
|
if err := a.initDB(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -378,7 +427,7 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
parseAuthSource(c, authSource)
|
parseAuthSourceLdap(c, authSource)
|
||||||
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
|
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -387,10 +436,7 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// updateLdapSimpleAuth updates a new LDAP (simple auth) authentication source.
|
// updateLdapSimpleAuth updates a new LDAP (simple auth) authentication source.
|
||||||
func (a *authService) updateLdapSimpleAuth(c *cli.Context) error {
|
func (a *authService) updateLdapSimpleAuth(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := a.initDB(ctx); err != nil {
|
if err := a.initDB(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -400,7 +446,7 @@ func (a *authService) updateLdapSimpleAuth(c *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
parseAuthSource(c, authSource)
|
parseAuthSourceLdap(c, authSource)
|
||||||
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
|
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,17 +8,16 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/auth"
|
"code.gitea.io/gitea/models/auth"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
"code.gitea.io/gitea/services/auth/source/ldap"
|
"code.gitea.io/gitea/services/auth/source/ldap"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAddLdapBindDn(t *testing.T) {
|
func TestAddLdapBindDn(t *testing.T) {
|
||||||
// Mock cli functions to do not exit on error
|
// Mock cli functions to do not exit on error
|
||||||
osExiter := cli.OsExiter
|
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
|
||||||
defer func() { cli.OsExiter = osExiter }()
|
|
||||||
cli.OsExiter = func(code int) {}
|
|
||||||
|
|
||||||
// Test cases
|
// Test cases
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
@@ -51,6 +50,13 @@ func TestAddLdapBindDn(t *testing.T) {
|
|||||||
"--attributes-in-bind",
|
"--attributes-in-bind",
|
||||||
"--synchronize-users",
|
"--synchronize-users",
|
||||||
"--page-size", "99",
|
"--page-size", "99",
|
||||||
|
"--enable-groups",
|
||||||
|
"--group-search-base-dn", "ou=group,dc=full-domain-bind,dc=org",
|
||||||
|
"--group-member-attribute", "memberUid",
|
||||||
|
"--group-user-attribute", "uid",
|
||||||
|
"--group-filter", "(|(cn=gitea_users)(cn=admins))",
|
||||||
|
"--group-team-map", `{"cn=my-group,cn=groups,dc=example,dc=org": {"MyGiteaOrganization": ["MyGiteaTeam1", "MyGiteaTeam2"]}}`,
|
||||||
|
"--group-team-map-removal",
|
||||||
},
|
},
|
||||||
source: &auth.Source{
|
source: &auth.Source{
|
||||||
Type: auth.LDAP,
|
Type: auth.LDAP,
|
||||||
@@ -78,6 +84,13 @@ func TestAddLdapBindDn(t *testing.T) {
|
|||||||
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)",
|
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)",
|
||||||
RestrictedFilter: "(memberOf=cn=restricted-group,ou=example,dc=full-domain-bind,dc=org)",
|
RestrictedFilter: "(memberOf=cn=restricted-group,ou=example,dc=full-domain-bind,dc=org)",
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
|
GroupsEnabled: true,
|
||||||
|
GroupDN: "ou=group,dc=full-domain-bind,dc=org",
|
||||||
|
GroupMemberUID: "memberUid",
|
||||||
|
UserUID: "uid",
|
||||||
|
GroupFilter: "(|(cn=gitea_users)(cn=admins))",
|
||||||
|
GroupTeamMap: `{"cn=my-group,cn=groups,dc=example,dc=org": {"MyGiteaOrganization": ["MyGiteaTeam1", "MyGiteaTeam2"]}}`,
|
||||||
|
GroupTeamMapRemoval: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -121,7 +134,7 @@ func TestAddLdapBindDn(t *testing.T) {
|
|||||||
"--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)",
|
"--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)",
|
||||||
"--email-attribute", "mail",
|
"--email-attribute", "mail",
|
||||||
},
|
},
|
||||||
errMsg: "Unknown security protocol name: zzzzz",
|
errMsg: "unknown security protocol name: zzzzz",
|
||||||
},
|
},
|
||||||
// case 3
|
// case 3
|
||||||
{
|
{
|
||||||
@@ -215,22 +228,23 @@ func TestAddLdapBindDn(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
||||||
assert.FailNow(t, "case %d: should not call updateAuthSource", n)
|
assert.FailNow(t, "updateAuthSource called", "case %d: should not call updateAuthSource", n)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) {
|
getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) {
|
||||||
assert.FailNow(t, "case %d: should not call getAuthSourceByID", n)
|
assert.FailNow(t, "getAuthSourceByID called", "case %d: should not call getAuthSourceByID", n)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a copy of command to test
|
// Create a copy of command to test
|
||||||
app := cli.NewApp()
|
app := cli.Command{
|
||||||
app.Flags = microcmdAuthAddLdapBindDn.Flags
|
Flags: microcmdAuthAddLdapBindDn().Flags,
|
||||||
app.Action = service.addLdapBindDn
|
Action: service.addLdapBindDn,
|
||||||
|
}
|
||||||
|
|
||||||
// Run it
|
// Run it
|
||||||
err := app.Run(c.args)
|
err := app.Run(t.Context(), c.args)
|
||||||
if c.errMsg != "" {
|
if c.errMsg != "" {
|
||||||
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
|
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
|
||||||
} else {
|
} else {
|
||||||
@@ -242,9 +256,7 @@ func TestAddLdapBindDn(t *testing.T) {
|
|||||||
|
|
||||||
func TestAddLdapSimpleAuth(t *testing.T) {
|
func TestAddLdapSimpleAuth(t *testing.T) {
|
||||||
// Mock cli functions to do not exit on error
|
// Mock cli functions to do not exit on error
|
||||||
osExiter := cli.OsExiter
|
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
|
||||||
defer func() { cli.OsExiter = osExiter }()
|
|
||||||
cli.OsExiter = func(code int) {}
|
|
||||||
|
|
||||||
// Test cases
|
// Test cases
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
@@ -334,12 +346,12 @@ func TestAddLdapSimpleAuth(t *testing.T) {
|
|||||||
"--name", "ldap (simple auth) source",
|
"--name", "ldap (simple auth) source",
|
||||||
"--security-protocol", "zzzzz",
|
"--security-protocol", "zzzzz",
|
||||||
"--host", "ldap-server",
|
"--host", "ldap-server",
|
||||||
"--port", "123",
|
"--port", "1234",
|
||||||
"--user-filter", "(&(objectClass=posixAccount)(cn=%s))",
|
"--user-filter", "(&(objectClass=posixAccount)(cn=%s))",
|
||||||
"--email-attribute", "mail",
|
"--email-attribute", "mail",
|
||||||
"--user-dn", "cn=%s,ou=Users,dc=domain,dc=org",
|
"--user-dn", "cn=%s,ou=Users,dc=domain,dc=org",
|
||||||
},
|
},
|
||||||
errMsg: "Unknown security protocol name: zzzzz",
|
errMsg: "unknown security protocol name: zzzzz",
|
||||||
},
|
},
|
||||||
// case 3
|
// case 3
|
||||||
{
|
{
|
||||||
@@ -446,22 +458,23 @@ func TestAddLdapSimpleAuth(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
||||||
assert.FailNow(t, "case %d: should not call updateAuthSource", n)
|
assert.FailNow(t, "updateAuthSource called", "case %d: should not call updateAuthSource", n)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) {
|
getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) {
|
||||||
assert.FailNow(t, "case %d: should not call getAuthSourceByID", n)
|
assert.FailNow(t, "getAuthSourceById called", "case %d: should not call getAuthSourceByID", n)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a copy of command to test
|
// Create a copy of command to test
|
||||||
app := cli.NewApp()
|
app := &cli.Command{
|
||||||
app.Flags = microcmdAuthAddLdapSimpleAuth.Flags
|
Flags: microcmdAuthAddLdapSimpleAuth().Flags,
|
||||||
app.Action = service.addLdapSimpleAuth
|
Action: service.addLdapSimpleAuth,
|
||||||
|
}
|
||||||
|
|
||||||
// Run it
|
// Run it
|
||||||
err := app.Run(c.args)
|
err := app.Run(t.Context(), c.args)
|
||||||
if c.errMsg != "" {
|
if c.errMsg != "" {
|
||||||
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
|
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
|
||||||
} else {
|
} else {
|
||||||
@@ -473,9 +486,7 @@ func TestAddLdapSimpleAuth(t *testing.T) {
|
|||||||
|
|
||||||
func TestUpdateLdapBindDn(t *testing.T) {
|
func TestUpdateLdapBindDn(t *testing.T) {
|
||||||
// Mock cli functions to do not exit on error
|
// Mock cli functions to do not exit on error
|
||||||
osExiter := cli.OsExiter
|
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
|
||||||
defer func() { cli.OsExiter = osExiter }()
|
|
||||||
cli.OsExiter = func(code int) {}
|
|
||||||
|
|
||||||
// Test cases
|
// Test cases
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
@@ -510,6 +521,13 @@ func TestUpdateLdapBindDn(t *testing.T) {
|
|||||||
"--bind-password", "secret-bind-full",
|
"--bind-password", "secret-bind-full",
|
||||||
"--synchronize-users",
|
"--synchronize-users",
|
||||||
"--page-size", "99",
|
"--page-size", "99",
|
||||||
|
"--enable-groups",
|
||||||
|
"--group-search-base-dn", "ou=group,dc=full-domain-bind,dc=org",
|
||||||
|
"--group-member-attribute", "memberUid",
|
||||||
|
"--group-user-attribute", "uid",
|
||||||
|
"--group-filter", "(|(cn=gitea_users)(cn=admins))",
|
||||||
|
"--group-team-map", `{"cn=my-group,cn=groups,dc=example,dc=org": {"MyGiteaOrganization": ["MyGiteaTeam1", "MyGiteaTeam2"]}}`,
|
||||||
|
"--group-team-map-removal",
|
||||||
},
|
},
|
||||||
id: 23,
|
id: 23,
|
||||||
existingAuthSource: &auth.Source{
|
existingAuthSource: &auth.Source{
|
||||||
@@ -545,6 +563,13 @@ func TestUpdateLdapBindDn(t *testing.T) {
|
|||||||
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)",
|
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)",
|
||||||
RestrictedFilter: "(memberOf=cn=restricted-group,ou=example,dc=full-domain-bind,dc=org)",
|
RestrictedFilter: "(memberOf=cn=restricted-group,ou=example,dc=full-domain-bind,dc=org)",
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
|
GroupsEnabled: true,
|
||||||
|
GroupDN: "ou=group,dc=full-domain-bind,dc=org",
|
||||||
|
GroupMemberUID: "memberUid",
|
||||||
|
UserUID: "uid",
|
||||||
|
GroupFilter: "(|(cn=gitea_users)(cn=admins))",
|
||||||
|
GroupTeamMap: `{"cn=my-group,cn=groups,dc=example,dc=org": {"MyGiteaOrganization": ["MyGiteaTeam1", "MyGiteaTeam2"]}}`,
|
||||||
|
GroupTeamMapRemoval: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -836,7 +861,7 @@ func TestUpdateLdapBindDn(t *testing.T) {
|
|||||||
"--id", "1",
|
"--id", "1",
|
||||||
"--security-protocol", "xxxxx",
|
"--security-protocol", "xxxxx",
|
||||||
},
|
},
|
||||||
errMsg: "Unknown security protocol name: xxxxx",
|
errMsg: "unknown security protocol name: xxxxx",
|
||||||
},
|
},
|
||||||
// case 22
|
// case 22
|
||||||
{
|
{
|
||||||
@@ -855,7 +880,7 @@ func TestUpdateLdapBindDn(t *testing.T) {
|
|||||||
Type: auth.OAuth2,
|
Type: auth.OAuth2,
|
||||||
Cfg: &ldap.Source{},
|
Cfg: &ldap.Source{},
|
||||||
},
|
},
|
||||||
errMsg: "Invalid authentication type. expected: LDAP (via BindDN), actual: OAuth2",
|
errMsg: "invalid authentication type. expected: LDAP (via BindDN), actual: OAuth2",
|
||||||
},
|
},
|
||||||
// case 24
|
// case 24
|
||||||
{
|
{
|
||||||
@@ -897,7 +922,7 @@ func TestUpdateLdapBindDn(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
createAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
createAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
||||||
assert.FailNow(t, "case %d: should not call createAuthSource", n)
|
assert.FailNow(t, "createAuthSource called", "case %d: should not call createAuthSource", n)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
||||||
@@ -919,12 +944,12 @@ func TestUpdateLdapBindDn(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a copy of command to test
|
// Create a copy of command to test
|
||||||
app := cli.NewApp()
|
app := cli.Command{
|
||||||
app.Flags = microcmdAuthUpdateLdapBindDn.Flags
|
Flags: microcmdAuthUpdateLdapBindDn().Flags,
|
||||||
app.Action = service.updateLdapBindDn
|
Action: service.updateLdapBindDn,
|
||||||
|
}
|
||||||
// Run it
|
// Run it
|
||||||
err := app.Run(c.args)
|
err := app.Run(t.Context(), c.args)
|
||||||
if c.errMsg != "" {
|
if c.errMsg != "" {
|
||||||
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
|
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
|
||||||
} else {
|
} else {
|
||||||
@@ -936,9 +961,7 @@ func TestUpdateLdapBindDn(t *testing.T) {
|
|||||||
|
|
||||||
func TestUpdateLdapSimpleAuth(t *testing.T) {
|
func TestUpdateLdapSimpleAuth(t *testing.T) {
|
||||||
// Mock cli functions to do not exit on error
|
// Mock cli functions to do not exit on error
|
||||||
osExiter := cli.OsExiter
|
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
|
||||||
defer func() { cli.OsExiter = osExiter }()
|
|
||||||
cli.OsExiter = func(code int) {}
|
|
||||||
|
|
||||||
// Test cases
|
// Test cases
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
@@ -1229,7 +1252,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
|
|||||||
"--id", "1",
|
"--id", "1",
|
||||||
"--security-protocol", "xxxxx",
|
"--security-protocol", "xxxxx",
|
||||||
},
|
},
|
||||||
errMsg: "Unknown security protocol name: xxxxx",
|
errMsg: "unknown security protocol name: xxxxx",
|
||||||
},
|
},
|
||||||
// case 18
|
// case 18
|
||||||
{
|
{
|
||||||
@@ -1248,7 +1271,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
|
|||||||
Type: auth.PAM,
|
Type: auth.PAM,
|
||||||
Cfg: &ldap.Source{},
|
Cfg: &ldap.Source{},
|
||||||
},
|
},
|
||||||
errMsg: "Invalid authentication type. expected: LDAP (simple auth), actual: PAM",
|
errMsg: "invalid authentication type. expected: LDAP (simple auth), actual: PAM",
|
||||||
},
|
},
|
||||||
// case 20
|
// case 20
|
||||||
{
|
{
|
||||||
@@ -1287,7 +1310,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
createAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
createAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
||||||
assert.FailNow(t, "case %d: should not call createAuthSource", n)
|
assert.FailNow(t, "createAuthSource called", "case %d: should not call createAuthSource", n)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
||||||
@@ -1309,12 +1332,12 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a copy of command to test
|
// Create a copy of command to test
|
||||||
app := cli.NewApp()
|
app := cli.Command{
|
||||||
app.Flags = microcmdAuthUpdateLdapSimpleAuth.Flags
|
Flags: microcmdAuthUpdateLdapSimpleAuth().Flags,
|
||||||
app.Action = service.updateLdapSimpleAuth
|
Action: service.updateLdapSimpleAuth,
|
||||||
|
}
|
||||||
// Run it
|
// Run it
|
||||||
err := app.Run(c.args)
|
err := app.Run(t.Context(), c.args)
|
||||||
if c.errMsg != "" {
|
if c.errMsg != "" {
|
||||||
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
|
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,18 +4,20 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/services/auth/source/oauth2"
|
"code.gitea.io/gitea/services/auth/source/oauth2"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func oauthCLIFlags() []cli.Flag {
|
||||||
oauthCLIFlags = []cli.Flag{
|
return []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "name",
|
Name: "name",
|
||||||
Value: "",
|
Value: "",
|
||||||
@@ -85,6 +87,14 @@ var (
|
|||||||
Value: nil,
|
Value: nil,
|
||||||
Usage: "Scopes to request when to authenticate against this OAuth2 source",
|
Usage: "Scopes to request when to authenticate against this OAuth2 source",
|
||||||
},
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "ssh-public-key-claim-name",
|
||||||
|
Usage: "Claim name that provides SSH public keys",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "full-name-claim-name",
|
||||||
|
Usage: "Claim name that provides user's full name",
|
||||||
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "required-claim-name",
|
Name: "required-claim-name",
|
||||||
Value: "",
|
Value: "",
|
||||||
@@ -120,23 +130,34 @@ var (
|
|||||||
Usage: "Activate automatic team membership removal depending on groups",
|
Usage: "Activate automatic team membership removal depending on groups",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
microcmdAuthAddOauth = &cli.Command{
|
func microcmdAuthAddOauth() *cli.Command {
|
||||||
Name: "add-oauth",
|
return &cli.Command{
|
||||||
Usage: "Add new Oauth authentication source",
|
Name: "add-oauth",
|
||||||
Action: runAddOauth,
|
Usage: "Add new Oauth authentication source",
|
||||||
Flags: oauthCLIFlags,
|
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||||
|
return newAuthService().runAddOauth(ctx, cmd)
|
||||||
|
},
|
||||||
|
Flags: oauthCLIFlags(),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
microcmdAuthUpdateOauth = &cli.Command{
|
func microcmdAuthUpdateOauth() *cli.Command {
|
||||||
Name: "update-oauth",
|
return &cli.Command{
|
||||||
Usage: "Update existing Oauth authentication source",
|
Name: "update-oauth",
|
||||||
Action: runUpdateOauth,
|
Usage: "Update existing Oauth authentication source",
|
||||||
Flags: append(oauthCLIFlags[:1], append([]cli.Flag{idFlag}, oauthCLIFlags[1:]...)...),
|
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||||
|
return newAuthService().runUpdateOauth(ctx, cmd)
|
||||||
|
},
|
||||||
|
Flags: append(oauthCLIFlags()[:1], append([]cli.Flag{&cli.Int64Flag{
|
||||||
|
Name: "id",
|
||||||
|
Usage: "ID of authentication source",
|
||||||
|
}}, oauthCLIFlags()[1:]...)...),
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
func parseOAuth2Config(c *cli.Context) *oauth2.Source {
|
func parseOAuth2Config(c *cli.Command) *oauth2.Source {
|
||||||
var customURLMapping *oauth2.CustomURLMapping
|
var customURLMapping *oauth2.CustomURLMapping
|
||||||
if c.IsSet("use-custom-urls") {
|
if c.IsSet("use-custom-urls") {
|
||||||
customURLMapping = &oauth2.CustomURLMapping{
|
customURLMapping = &oauth2.CustomURLMapping{
|
||||||
@@ -156,7 +177,6 @@ func parseOAuth2Config(c *cli.Context) *oauth2.Source {
|
|||||||
OpenIDConnectAutoDiscoveryURL: c.String("auto-discover-url"),
|
OpenIDConnectAutoDiscoveryURL: c.String("auto-discover-url"),
|
||||||
CustomURLMapping: customURLMapping,
|
CustomURLMapping: customURLMapping,
|
||||||
IconURL: c.String("icon-url"),
|
IconURL: c.String("icon-url"),
|
||||||
SkipLocalTwoFA: c.Bool("skip-local-2fa"),
|
|
||||||
Scopes: c.StringSlice("scopes"),
|
Scopes: c.StringSlice("scopes"),
|
||||||
RequiredClaimName: c.String("required-claim-name"),
|
RequiredClaimName: c.String("required-claim-name"),
|
||||||
RequiredClaimValue: c.String("required-claim-value"),
|
RequiredClaimValue: c.String("required-claim-value"),
|
||||||
@@ -165,14 +185,13 @@ func parseOAuth2Config(c *cli.Context) *oauth2.Source {
|
|||||||
RestrictedGroup: c.String("restricted-group"),
|
RestrictedGroup: c.String("restricted-group"),
|
||||||
GroupTeamMap: c.String("group-team-map"),
|
GroupTeamMap: c.String("group-team-map"),
|
||||||
GroupTeamMapRemoval: c.Bool("group-team-map-removal"),
|
GroupTeamMapRemoval: c.Bool("group-team-map-removal"),
|
||||||
|
SSHPublicKeyClaimName: c.String("ssh-public-key-claim-name"),
|
||||||
|
FullNameClaimName: c.String("full-name-claim-name"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runAddOauth(c *cli.Context) error {
|
func (a *authService) runAddOauth(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
if err := a.initDB(ctx); err != nil {
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,27 +203,25 @@ func runAddOauth(c *cli.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return auth_model.CreateSource(ctx, &auth_model.Source{
|
return a.createAuthSource(ctx, &auth_model.Source{
|
||||||
Type: auth_model.OAuth2,
|
Type: auth_model.OAuth2,
|
||||||
Name: c.String("name"),
|
Name: c.String("name"),
|
||||||
IsActive: true,
|
IsActive: true,
|
||||||
Cfg: config,
|
Cfg: config,
|
||||||
|
TwoFactorPolicy: util.Iif(c.Bool("skip-local-2fa"), "skip", ""),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func runUpdateOauth(c *cli.Context) error {
|
func (a *authService) runUpdateOauth(ctx context.Context, c *cli.Command) error {
|
||||||
if !c.IsSet("id") {
|
if !c.IsSet("id") {
|
||||||
return errors.New("--id flag is missing")
|
return errors.New("--id flag is missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := installSignals()
|
if err := a.initDB(ctx); err != nil {
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
source, err := auth_model.GetSourceByID(ctx, c.Int64("id"))
|
source, err := a.getAuthSourceByID(ctx, c.Int64("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -261,6 +278,12 @@ func runUpdateOauth(c *cli.Context) error {
|
|||||||
if c.IsSet("group-team-map-removal") {
|
if c.IsSet("group-team-map-removal") {
|
||||||
oAuth2Config.GroupTeamMapRemoval = c.Bool("group-team-map-removal")
|
oAuth2Config.GroupTeamMapRemoval = c.Bool("group-team-map-removal")
|
||||||
}
|
}
|
||||||
|
if c.IsSet("ssh-public-key-claim-name") {
|
||||||
|
oAuth2Config.SSHPublicKeyClaimName = c.String("ssh-public-key-claim-name")
|
||||||
|
}
|
||||||
|
if c.IsSet("full-name-claim-name") {
|
||||||
|
oAuth2Config.FullNameClaimName = c.String("full-name-claim-name")
|
||||||
|
}
|
||||||
|
|
||||||
// update custom URL mapping
|
// update custom URL mapping
|
||||||
customURLMapping := &oauth2.CustomURLMapping{}
|
customURLMapping := &oauth2.CustomURLMapping{}
|
||||||
@@ -294,6 +317,6 @@ func runUpdateOauth(c *cli.Context) error {
|
|||||||
|
|
||||||
oAuth2Config.CustomURLMapping = customURLMapping
|
oAuth2Config.CustomURLMapping = customURLMapping
|
||||||
source.Cfg = oAuth2Config
|
source.Cfg = oAuth2Config
|
||||||
|
source.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "")
|
||||||
return auth_model.UpdateSource(ctx, source)
|
return a.updateAuthSource(ctx, source)
|
||||||
}
|
}
|
||||||
|
|||||||
343
cmd/admin_auth_oauth_test.go
Normal file
343
cmd/admin_auth_oauth_test.go
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
|
"code.gitea.io/gitea/services/auth/source/oauth2"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/urfave/cli/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAddOauth(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
source *auth_model.Source
|
||||||
|
errMsg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid config",
|
||||||
|
args: []string{
|
||||||
|
"--name", "test",
|
||||||
|
"--provider", "github",
|
||||||
|
"--key", "some_key",
|
||||||
|
"--secret", "some_secret",
|
||||||
|
},
|
||||||
|
source: &auth_model.Source{
|
||||||
|
Type: auth_model.OAuth2,
|
||||||
|
Name: "test",
|
||||||
|
IsActive: true,
|
||||||
|
Cfg: &oauth2.Source{
|
||||||
|
Scopes: []string{},
|
||||||
|
Provider: "github",
|
||||||
|
ClientID: "some_key",
|
||||||
|
ClientSecret: "some_secret",
|
||||||
|
},
|
||||||
|
TwoFactorPolicy: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid config with openid connect",
|
||||||
|
args: []string{
|
||||||
|
"--name", "test",
|
||||||
|
"--provider", "openidConnect",
|
||||||
|
"--key", "some_key",
|
||||||
|
"--secret", "some_secret",
|
||||||
|
"--auto-discover-url", "https://example.com",
|
||||||
|
},
|
||||||
|
source: &auth_model.Source{
|
||||||
|
Type: auth_model.OAuth2,
|
||||||
|
Name: "test",
|
||||||
|
IsActive: true,
|
||||||
|
Cfg: &oauth2.Source{
|
||||||
|
Scopes: []string{},
|
||||||
|
Provider: "openidConnect",
|
||||||
|
ClientID: "some_key",
|
||||||
|
ClientSecret: "some_secret",
|
||||||
|
OpenIDConnectAutoDiscoveryURL: "https://example.com",
|
||||||
|
},
|
||||||
|
TwoFactorPolicy: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid config with options",
|
||||||
|
args: []string{
|
||||||
|
"--name", "test",
|
||||||
|
"--provider", "gitlab",
|
||||||
|
"--key", "some_key",
|
||||||
|
"--secret", "some_secret",
|
||||||
|
"--use-custom-urls", "true",
|
||||||
|
"--custom-token-url", "https://example.com/token",
|
||||||
|
"--custom-auth-url", "https://example.com/auth",
|
||||||
|
"--custom-profile-url", "https://example.com/profile",
|
||||||
|
"--custom-email-url", "https://example.com/email",
|
||||||
|
"--custom-tenant-id", "some_tenant",
|
||||||
|
"--icon-url", "https://example.com/icon",
|
||||||
|
"--scopes", "scope1,scope2",
|
||||||
|
"--skip-local-2fa", "true",
|
||||||
|
"--required-claim-name", "claim_name",
|
||||||
|
"--required-claim-value", "claim_value",
|
||||||
|
"--group-claim-name", "group_name",
|
||||||
|
"--admin-group", "admin",
|
||||||
|
"--restricted-group", "restricted",
|
||||||
|
"--group-team-map", `{"group1": [1,2]}`,
|
||||||
|
"--group-team-map-removal=true",
|
||||||
|
"--ssh-public-key-claim-name", "attr_ssh_pub_key",
|
||||||
|
"--full-name-claim-name", "attr_full_name",
|
||||||
|
},
|
||||||
|
source: &auth_model.Source{
|
||||||
|
Type: auth_model.OAuth2,
|
||||||
|
Name: "test",
|
||||||
|
IsActive: true,
|
||||||
|
Cfg: &oauth2.Source{
|
||||||
|
Provider: "gitlab",
|
||||||
|
ClientID: "some_key",
|
||||||
|
ClientSecret: "some_secret",
|
||||||
|
CustomURLMapping: &oauth2.CustomURLMapping{
|
||||||
|
TokenURL: "https://example.com/token",
|
||||||
|
AuthURL: "https://example.com/auth",
|
||||||
|
ProfileURL: "https://example.com/profile",
|
||||||
|
EmailURL: "https://example.com/email",
|
||||||
|
Tenant: "some_tenant",
|
||||||
|
},
|
||||||
|
IconURL: "https://example.com/icon",
|
||||||
|
Scopes: []string{"scope1", "scope2"},
|
||||||
|
RequiredClaimName: "claim_name",
|
||||||
|
RequiredClaimValue: "claim_value",
|
||||||
|
GroupClaimName: "group_name",
|
||||||
|
AdminGroup: "admin",
|
||||||
|
RestrictedGroup: "restricted",
|
||||||
|
GroupTeamMap: `{"group1": [1,2]}`,
|
||||||
|
GroupTeamMapRemoval: true,
|
||||||
|
SSHPublicKeyClaimName: "attr_ssh_pub_key",
|
||||||
|
FullNameClaimName: "attr_full_name",
|
||||||
|
},
|
||||||
|
TwoFactorPolicy: "skip",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
var createdSource *auth_model.Source
|
||||||
|
a := &authService{
|
||||||
|
initDB: func(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
createAuthSource: func(ctx context.Context, source *auth_model.Source) error {
|
||||||
|
createdSource = source
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app := &cli.Command{
|
||||||
|
Flags: microcmdAuthAddOauth().Flags,
|
||||||
|
Action: a.runAddOauth,
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{"oauth-test"}
|
||||||
|
args = append(args, tc.args...)
|
||||||
|
|
||||||
|
err := app.Run(t.Context(), args)
|
||||||
|
|
||||||
|
if tc.errMsg != "" {
|
||||||
|
assert.EqualError(t, err, tc.errMsg)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tc.source, createdSource)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateOauth(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
id int64
|
||||||
|
existingAuthSource *auth_model.Source
|
||||||
|
authSource *auth_model.Source
|
||||||
|
errMsg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing id",
|
||||||
|
args: []string{
|
||||||
|
"--name", "test",
|
||||||
|
},
|
||||||
|
errMsg: "--id flag is missing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid config",
|
||||||
|
id: 1,
|
||||||
|
existingAuthSource: &auth_model.Source{
|
||||||
|
ID: 1,
|
||||||
|
Type: auth_model.OAuth2,
|
||||||
|
Name: "old name",
|
||||||
|
IsActive: true,
|
||||||
|
Cfg: &oauth2.Source{
|
||||||
|
Provider: "github",
|
||||||
|
ClientID: "old_key",
|
||||||
|
ClientSecret: "old_secret",
|
||||||
|
},
|
||||||
|
TwoFactorPolicy: "",
|
||||||
|
},
|
||||||
|
args: []string{
|
||||||
|
"--id", "1",
|
||||||
|
"--name", "test",
|
||||||
|
"--provider", "gitlab",
|
||||||
|
"--key", "new_key",
|
||||||
|
"--secret", "new_secret",
|
||||||
|
},
|
||||||
|
authSource: &auth_model.Source{
|
||||||
|
ID: 1,
|
||||||
|
Type: auth_model.OAuth2,
|
||||||
|
Name: "test",
|
||||||
|
IsActive: true,
|
||||||
|
Cfg: &oauth2.Source{
|
||||||
|
Provider: "gitlab",
|
||||||
|
ClientID: "new_key",
|
||||||
|
ClientSecret: "new_secret",
|
||||||
|
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||||
|
},
|
||||||
|
TwoFactorPolicy: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid config with options",
|
||||||
|
id: 1,
|
||||||
|
existingAuthSource: &auth_model.Source{
|
||||||
|
ID: 1,
|
||||||
|
Type: auth_model.OAuth2,
|
||||||
|
Name: "old name",
|
||||||
|
IsActive: true,
|
||||||
|
Cfg: &oauth2.Source{
|
||||||
|
Provider: "gitlab",
|
||||||
|
ClientID: "old_key",
|
||||||
|
ClientSecret: "old_secret",
|
||||||
|
CustomURLMapping: &oauth2.CustomURLMapping{
|
||||||
|
TokenURL: "https://old.example.com/token",
|
||||||
|
AuthURL: "https://old.example.com/auth",
|
||||||
|
ProfileURL: "https://old.example.com/profile",
|
||||||
|
EmailURL: "https://old.example.com/email",
|
||||||
|
Tenant: "old_tenant",
|
||||||
|
},
|
||||||
|
IconURL: "https://old.example.com/icon",
|
||||||
|
Scopes: []string{"old_scope1", "old_scope2"},
|
||||||
|
RequiredClaimName: "old_claim_name",
|
||||||
|
RequiredClaimValue: "old_claim_value",
|
||||||
|
GroupClaimName: "old_group_name",
|
||||||
|
AdminGroup: "old_admin",
|
||||||
|
RestrictedGroup: "old_restricted",
|
||||||
|
GroupTeamMap: `{"old_group1": [1,2]}`,
|
||||||
|
GroupTeamMapRemoval: true,
|
||||||
|
SSHPublicKeyClaimName: "old_ssh_pub_key",
|
||||||
|
FullNameClaimName: "old_full_name",
|
||||||
|
},
|
||||||
|
TwoFactorPolicy: "",
|
||||||
|
},
|
||||||
|
args: []string{
|
||||||
|
"--id", "1",
|
||||||
|
"--name", "test",
|
||||||
|
"--provider", "github",
|
||||||
|
"--key", "new_key",
|
||||||
|
"--secret", "new_secret",
|
||||||
|
"--use-custom-urls", "true",
|
||||||
|
"--custom-token-url", "https://example.com/token",
|
||||||
|
"--custom-auth-url", "https://example.com/auth",
|
||||||
|
"--custom-profile-url", "https://example.com/profile",
|
||||||
|
"--custom-email-url", "https://example.com/email",
|
||||||
|
"--custom-tenant-id", "new_tenant",
|
||||||
|
"--icon-url", "https://example.com/icon",
|
||||||
|
"--scopes", "scope1,scope2",
|
||||||
|
"--skip-local-2fa=true",
|
||||||
|
"--required-claim-name", "claim_name",
|
||||||
|
"--required-claim-value", "claim_value",
|
||||||
|
"--group-claim-name", "group_name",
|
||||||
|
"--admin-group", "admin",
|
||||||
|
"--restricted-group", "restricted",
|
||||||
|
"--group-team-map", `{"group1": [1,2]}`,
|
||||||
|
"--group-team-map-removal=false",
|
||||||
|
"--ssh-public-key-claim-name", "new_ssh_pub_key",
|
||||||
|
"--full-name-claim-name", "new_full_name",
|
||||||
|
},
|
||||||
|
authSource: &auth_model.Source{
|
||||||
|
ID: 1,
|
||||||
|
Type: auth_model.OAuth2,
|
||||||
|
Name: "test",
|
||||||
|
IsActive: true,
|
||||||
|
Cfg: &oauth2.Source{
|
||||||
|
Provider: "github",
|
||||||
|
ClientID: "new_key",
|
||||||
|
ClientSecret: "new_secret",
|
||||||
|
CustomURLMapping: &oauth2.CustomURLMapping{
|
||||||
|
TokenURL: "https://example.com/token",
|
||||||
|
AuthURL: "https://example.com/auth",
|
||||||
|
ProfileURL: "https://example.com/profile",
|
||||||
|
EmailURL: "https://example.com/email",
|
||||||
|
Tenant: "new_tenant",
|
||||||
|
},
|
||||||
|
IconURL: "https://example.com/icon",
|
||||||
|
Scopes: []string{"scope1", "scope2"},
|
||||||
|
RequiredClaimName: "claim_name",
|
||||||
|
RequiredClaimValue: "claim_value",
|
||||||
|
GroupClaimName: "group_name",
|
||||||
|
AdminGroup: "admin",
|
||||||
|
RestrictedGroup: "restricted",
|
||||||
|
GroupTeamMap: `{"group1": [1,2]}`,
|
||||||
|
GroupTeamMapRemoval: false,
|
||||||
|
SSHPublicKeyClaimName: "new_ssh_pub_key",
|
||||||
|
FullNameClaimName: "new_full_name",
|
||||||
|
},
|
||||||
|
TwoFactorPolicy: "skip",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
a := &authService{
|
||||||
|
initDB: func(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
getAuthSourceByID: func(ctx context.Context, id int64) (*auth_model.Source, error) {
|
||||||
|
return &auth_model.Source{
|
||||||
|
ID: 1,
|
||||||
|
Type: auth_model.OAuth2,
|
||||||
|
Name: "test",
|
||||||
|
IsActive: true,
|
||||||
|
Cfg: &oauth2.Source{
|
||||||
|
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||||
|
},
|
||||||
|
TwoFactorPolicy: "skip",
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
updateAuthSource: func(ctx context.Context, source *auth_model.Source) error {
|
||||||
|
assert.Equal(t, tc.authSource, source)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app := &cli.Command{
|
||||||
|
Flags: microcmdAuthUpdateOauth().Flags,
|
||||||
|
Action: a.runUpdateOauth,
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{"oauth-test"}
|
||||||
|
args = append(args, tc.args...)
|
||||||
|
|
||||||
|
err := app.Run(t.Context(), args)
|
||||||
|
|
||||||
|
if tc.errMsg != "" {
|
||||||
|
assert.EqualError(t, err, tc.errMsg)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -11,11 +12,11 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/services/auth/source/smtp"
|
"code.gitea.io/gitea/services/auth/source/smtp"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func smtpCLIFlags() []cli.Flag {
|
||||||
smtpCLIFlags = []cli.Flag{
|
return []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "name",
|
Name: "name",
|
||||||
Value: "",
|
Value: "",
|
||||||
@@ -38,12 +39,10 @@ var (
|
|||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "force-smtps",
|
Name: "force-smtps",
|
||||||
Usage: "SMTPS is always used on port 465. Set this to force SMTPS on other ports.",
|
Usage: "SMTPS is always used on port 465. Set this to force SMTPS on other ports.",
|
||||||
Value: true,
|
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "skip-verify",
|
Name: "skip-verify",
|
||||||
Usage: "Skip TLS verify.",
|
Usage: "Skip TLS verify.",
|
||||||
Value: true,
|
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "helo-hostname",
|
Name: "helo-hostname",
|
||||||
@@ -53,7 +52,6 @@ var (
|
|||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "disable-helo",
|
Name: "disable-helo",
|
||||||
Usage: "Disable SMTP helo.",
|
Usage: "Disable SMTP helo.",
|
||||||
Value: true,
|
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "allowed-domains",
|
Name: "allowed-domains",
|
||||||
@@ -63,7 +61,6 @@ var (
|
|||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "skip-local-2fa",
|
Name: "skip-local-2fa",
|
||||||
Usage: "Skip 2FA to log on.",
|
Usage: "Skip 2FA to log on.",
|
||||||
Value: true,
|
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "active",
|
Name: "active",
|
||||||
@@ -71,23 +68,34 @@ var (
|
|||||||
Value: true,
|
Value: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
microcmdAuthAddSMTP = &cli.Command{
|
func microcmdAuthUpdateSMTP() *cli.Command {
|
||||||
Name: "add-smtp",
|
return &cli.Command{
|
||||||
Usage: "Add new SMTP authentication source",
|
Name: "update-smtp",
|
||||||
Action: runAddSMTP,
|
Usage: "Update existing SMTP authentication source",
|
||||||
Flags: smtpCLIFlags,
|
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||||
|
return newAuthService().runUpdateSMTP(ctx, cmd)
|
||||||
|
},
|
||||||
|
Flags: append(smtpCLIFlags()[:1], append([]cli.Flag{&cli.Int64Flag{
|
||||||
|
Name: "id",
|
||||||
|
Usage: "ID of authentication source",
|
||||||
|
}}, smtpCLIFlags()[1:]...)...),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
microcmdAuthUpdateSMTP = &cli.Command{
|
func microcmdAuthAddSMTP() *cli.Command {
|
||||||
Name: "update-smtp",
|
return &cli.Command{
|
||||||
Usage: "Update existing SMTP authentication source",
|
Name: "add-smtp",
|
||||||
Action: runUpdateSMTP,
|
Usage: "Add new SMTP authentication source",
|
||||||
Flags: append(smtpCLIFlags[:1], append([]cli.Flag{idFlag}, smtpCLIFlags[1:]...)...),
|
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||||
|
return newAuthService().runAddSMTP(ctx, cmd)
|
||||||
|
},
|
||||||
|
Flags: smtpCLIFlags(),
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
|
func parseSMTPConfig(c *cli.Command, conf *smtp.Source) error {
|
||||||
if c.IsSet("auth-type") {
|
if c.IsSet("auth-type") {
|
||||||
conf.Auth = c.String("auth-type")
|
conf.Auth = c.String("auth-type")
|
||||||
validAuthTypes := []string{"PLAIN", "LOGIN", "CRAM-MD5"}
|
validAuthTypes := []string{"PLAIN", "LOGIN", "CRAM-MD5"}
|
||||||
@@ -117,17 +125,11 @@ func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
|
|||||||
if c.IsSet("disable-helo") {
|
if c.IsSet("disable-helo") {
|
||||||
conf.DisableHelo = c.Bool("disable-helo")
|
conf.DisableHelo = c.Bool("disable-helo")
|
||||||
}
|
}
|
||||||
if c.IsSet("skip-local-2fa") {
|
|
||||||
conf.SkipLocalTwoFA = c.Bool("skip-local-2fa")
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runAddSMTP(c *cli.Context) error {
|
func (a *authService) runAddSMTP(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
if err := a.initDB(ctx); err != nil {
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,27 +157,25 @@ func runAddSMTP(c *cli.Context) error {
|
|||||||
smtpConfig.Auth = "PLAIN"
|
smtpConfig.Auth = "PLAIN"
|
||||||
}
|
}
|
||||||
|
|
||||||
return auth_model.CreateSource(ctx, &auth_model.Source{
|
return a.createAuthSource(ctx, &auth_model.Source{
|
||||||
Type: auth_model.SMTP,
|
Type: auth_model.SMTP,
|
||||||
Name: c.String("name"),
|
Name: c.String("name"),
|
||||||
IsActive: active,
|
IsActive: active,
|
||||||
Cfg: &smtpConfig,
|
Cfg: &smtpConfig,
|
||||||
|
TwoFactorPolicy: util.Iif(c.Bool("skip-local-2fa"), "skip", ""),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func runUpdateSMTP(c *cli.Context) error {
|
func (a *authService) runUpdateSMTP(ctx context.Context, c *cli.Command) error {
|
||||||
if !c.IsSet("id") {
|
if !c.IsSet("id") {
|
||||||
return errors.New("--id flag is missing")
|
return errors.New("--id flag is missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := installSignals()
|
if err := a.initDB(ctx); err != nil {
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
source, err := auth_model.GetSourceByID(ctx, c.Int64("id"))
|
source, err := a.getAuthSourceByID(ctx, c.Int64("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -195,6 +195,6 @@ func runUpdateSMTP(c *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
source.Cfg = smtpConfig
|
source.Cfg = smtpConfig
|
||||||
|
source.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "")
|
||||||
return auth_model.UpdateSource(ctx, source)
|
return a.updateAuthSource(ctx, source)
|
||||||
}
|
}
|
||||||
271
cmd/admin_auth_smtp_test.go
Normal file
271
cmd/admin_auth_smtp_test.go
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
|
"code.gitea.io/gitea/services/auth/source/smtp"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/urfave/cli/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAddSMTP(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
source *auth_model.Source
|
||||||
|
errMsg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing name",
|
||||||
|
args: []string{
|
||||||
|
"--host", "localhost",
|
||||||
|
"--port", "25",
|
||||||
|
},
|
||||||
|
errMsg: "name must be set",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing host",
|
||||||
|
args: []string{
|
||||||
|
"--name", "test",
|
||||||
|
"--port", "25",
|
||||||
|
},
|
||||||
|
errMsg: "host must be set",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing port",
|
||||||
|
args: []string{
|
||||||
|
"--name", "test",
|
||||||
|
"--host", "localhost",
|
||||||
|
},
|
||||||
|
errMsg: "port must be set",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid config",
|
||||||
|
args: []string{
|
||||||
|
"--name", "test",
|
||||||
|
"--host", "localhost",
|
||||||
|
"--port", "25",
|
||||||
|
},
|
||||||
|
source: &auth_model.Source{
|
||||||
|
Type: auth_model.SMTP,
|
||||||
|
Name: "test",
|
||||||
|
IsActive: true,
|
||||||
|
Cfg: &smtp.Source{
|
||||||
|
Auth: "PLAIN",
|
||||||
|
Host: "localhost",
|
||||||
|
Port: 25,
|
||||||
|
},
|
||||||
|
TwoFactorPolicy: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid config with options",
|
||||||
|
args: []string{
|
||||||
|
"--name", "test",
|
||||||
|
"--host", "localhost",
|
||||||
|
"--port", "25",
|
||||||
|
"--auth-type", "LOGIN",
|
||||||
|
"--force-smtps",
|
||||||
|
"--skip-verify",
|
||||||
|
"--helo-hostname", "example.com",
|
||||||
|
"--disable-helo=true",
|
||||||
|
"--allowed-domains", "example.com,example.org",
|
||||||
|
"--skip-local-2fa",
|
||||||
|
"--active=false",
|
||||||
|
},
|
||||||
|
source: &auth_model.Source{
|
||||||
|
Type: auth_model.SMTP,
|
||||||
|
Name: "test",
|
||||||
|
IsActive: false,
|
||||||
|
Cfg: &smtp.Source{
|
||||||
|
Auth: "LOGIN",
|
||||||
|
Host: "localhost",
|
||||||
|
Port: 25,
|
||||||
|
ForceSMTPS: true,
|
||||||
|
SkipVerify: true,
|
||||||
|
HeloHostname: "example.com",
|
||||||
|
DisableHelo: true,
|
||||||
|
AllowedDomains: "example.com,example.org",
|
||||||
|
},
|
||||||
|
TwoFactorPolicy: "skip",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
a := &authService{
|
||||||
|
initDB: func(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
createAuthSource: func(ctx context.Context, source *auth_model.Source) error {
|
||||||
|
assert.Equal(t, tc.source, source)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := &cli.Command{
|
||||||
|
Flags: microcmdAuthAddSMTP().Flags,
|
||||||
|
Action: a.runAddSMTP,
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{"smtp-test"}
|
||||||
|
args = append(args, tc.args...)
|
||||||
|
|
||||||
|
t.Log(args)
|
||||||
|
err := cmd.Run(t.Context(), args)
|
||||||
|
|
||||||
|
if tc.errMsg != "" {
|
||||||
|
assert.EqualError(t, err, tc.errMsg)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateSMTP(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
existingAuthSource *auth_model.Source
|
||||||
|
authSource *auth_model.Source
|
||||||
|
errMsg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing id",
|
||||||
|
args: []string{
|
||||||
|
"--name", "test",
|
||||||
|
"--host", "localhost",
|
||||||
|
"--port", "25",
|
||||||
|
},
|
||||||
|
errMsg: "--id flag is missing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid config",
|
||||||
|
existingAuthSource: &auth_model.Source{
|
||||||
|
ID: 1,
|
||||||
|
Type: auth_model.SMTP,
|
||||||
|
Name: "old name",
|
||||||
|
IsActive: true,
|
||||||
|
Cfg: &smtp.Source{
|
||||||
|
Auth: "PLAIN",
|
||||||
|
Host: "old host",
|
||||||
|
Port: 26,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: []string{
|
||||||
|
"--id", "1",
|
||||||
|
"--name", "test",
|
||||||
|
"--host", "localhost",
|
||||||
|
"--port", "25",
|
||||||
|
},
|
||||||
|
authSource: &auth_model.Source{
|
||||||
|
ID: 1,
|
||||||
|
Type: auth_model.SMTP,
|
||||||
|
Name: "test",
|
||||||
|
IsActive: true,
|
||||||
|
Cfg: &smtp.Source{
|
||||||
|
Auth: "PLAIN",
|
||||||
|
Host: "localhost",
|
||||||
|
Port: 25,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid config with options",
|
||||||
|
existingAuthSource: &auth_model.Source{
|
||||||
|
ID: 1,
|
||||||
|
Type: auth_model.SMTP,
|
||||||
|
Name: "old name",
|
||||||
|
IsActive: true,
|
||||||
|
Cfg: &smtp.Source{
|
||||||
|
Auth: "PLAIN",
|
||||||
|
Host: "old host",
|
||||||
|
Port: 26,
|
||||||
|
HeloHostname: "old.example.com",
|
||||||
|
AllowedDomains: "old.example.com",
|
||||||
|
},
|
||||||
|
TwoFactorPolicy: "",
|
||||||
|
},
|
||||||
|
args: []string{
|
||||||
|
"--id", "1",
|
||||||
|
"--name", "test",
|
||||||
|
"--host", "localhost",
|
||||||
|
"--port", "25",
|
||||||
|
"--auth-type", "LOGIN",
|
||||||
|
"--force-smtps",
|
||||||
|
"--skip-verify",
|
||||||
|
"--helo-hostname", "example.com",
|
||||||
|
"--disable-helo",
|
||||||
|
"--allowed-domains", "example.com,example.org",
|
||||||
|
"--skip-local-2fa",
|
||||||
|
"--active=false",
|
||||||
|
},
|
||||||
|
authSource: &auth_model.Source{
|
||||||
|
ID: 1,
|
||||||
|
Type: auth_model.SMTP,
|
||||||
|
Name: "test",
|
||||||
|
IsActive: false,
|
||||||
|
Cfg: &smtp.Source{
|
||||||
|
Auth: "LOGIN",
|
||||||
|
Host: "localhost",
|
||||||
|
Port: 25,
|
||||||
|
ForceSMTPS: true,
|
||||||
|
SkipVerify: true,
|
||||||
|
HeloHostname: "example.com",
|
||||||
|
DisableHelo: true,
|
||||||
|
AllowedDomains: "example.com,example.org",
|
||||||
|
},
|
||||||
|
TwoFactorPolicy: "skip",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
a := &authService{
|
||||||
|
initDB: func(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
getAuthSourceByID: func(ctx context.Context, id int64) (*auth_model.Source, error) {
|
||||||
|
return &auth_model.Source{
|
||||||
|
ID: 1,
|
||||||
|
Type: auth_model.SMTP,
|
||||||
|
Name: "test",
|
||||||
|
IsActive: true,
|
||||||
|
Cfg: &smtp.Source{
|
||||||
|
Auth: "PLAIN",
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
|
||||||
|
updateAuthSource: func(ctx context.Context, source *auth_model.Source) error {
|
||||||
|
assert.Equal(t, tc.authSource, source)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app := &cli.Command{
|
||||||
|
Flags: microcmdAuthUpdateSMTP().Flags,
|
||||||
|
Action: a.runUpdateSMTP,
|
||||||
|
}
|
||||||
|
args := []string{"smtp-tests"}
|
||||||
|
args = append(args, tc.args...)
|
||||||
|
|
||||||
|
err := app.Run(t.Context(), args)
|
||||||
|
|
||||||
|
if tc.errMsg != "" {
|
||||||
|
assert.EqualError(t, err, tc.errMsg)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,11 +4,13 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"code.gitea.io/gitea/modules/graceful"
|
||||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||||
repo_service "code.gitea.io/gitea/services/repository"
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -25,20 +27,14 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func runRegenerateHooks(_ *cli.Context) error {
|
func runRegenerateHooks(ctx context.Context, _ *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
if err := initDB(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext())
|
return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext())
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRegenerateKeys(_ *cli.Context) error {
|
func runRegenerateKeys(ctx context.Context, _ *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
if err := initDB(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,18 +4,18 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var subcmdUser = &cli.Command{
|
var subcmdUser = &cli.Command{
|
||||||
Name: "user",
|
Name: "user",
|
||||||
Usage: "Modify users",
|
Usage: "Modify users",
|
||||||
Subcommands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
microcmdUserCreate,
|
microcmdUserCreate(),
|
||||||
microcmdUserList,
|
microcmdUserList,
|
||||||
microcmdUserChangePassword,
|
microcmdUserChangePassword(),
|
||||||
microcmdUserDelete,
|
microcmdUserDelete(),
|
||||||
microcmdUserGenerateAccessToken,
|
microcmdUserGenerateAccessToken,
|
||||||
microcmdUserMustChangePassword,
|
microcmdUserMustChangePassword(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
@@ -13,44 +14,41 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
user_service "code.gitea.io/gitea/services/user"
|
user_service "code.gitea.io/gitea/services/user"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var microcmdUserChangePassword = &cli.Command{
|
func microcmdUserChangePassword() *cli.Command {
|
||||||
Name: "change-password",
|
return &cli.Command{
|
||||||
Usage: "Change a user's password",
|
Name: "change-password",
|
||||||
Action: runChangePassword,
|
Usage: "Change a user's password",
|
||||||
Flags: []cli.Flag{
|
Action: runChangePassword,
|
||||||
&cli.StringFlag{
|
Flags: []cli.Flag{
|
||||||
Name: "username",
|
&cli.StringFlag{
|
||||||
Aliases: []string{"u"},
|
Name: "username",
|
||||||
Value: "",
|
Aliases: []string{"u"},
|
||||||
Usage: "The user to change password for",
|
Usage: "The user to change password for",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "password",
|
||||||
|
Aliases: []string{"p"},
|
||||||
|
Usage: "New password to set for user",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "must-change-password",
|
||||||
|
Usage: "User must change password (can be disabled by --must-change-password=false)",
|
||||||
|
Value: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
}
|
||||||
Name: "password",
|
|
||||||
Aliases: []string{"p"},
|
|
||||||
Value: "",
|
|
||||||
Usage: "New password to set for user",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "must-change-password",
|
|
||||||
Usage: "User must change password (can be disabled by --must-change-password=false)",
|
|
||||||
Value: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runChangePassword(c *cli.Context) error {
|
func runChangePassword(ctx context.Context, c *cli.Command) error {
|
||||||
if err := argsSet(c, "username", "password"); err != nil {
|
if !setting.IsInTesting {
|
||||||
return err
|
if err := initDB(ctx); err != nil {
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := user_model.GetUserByName(ctx, c.String("username"))
|
user, err := user_model.GetUserByName(ctx, c.String("username"))
|
||||||
|
|||||||
91
cmd/admin_user_change_password_test.go
Normal file
91
cmd/admin_user_change_password_test.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestChangePasswordCommand(t *testing.T) {
|
||||||
|
ctx := t.Context()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
require.NoError(t, db.TruncateBeans(t.Context(), &user_model.User{}))
|
||||||
|
}()
|
||||||
|
|
||||||
|
t.Run("change password successfully", func(t *testing.T) {
|
||||||
|
// defer func() {
|
||||||
|
// require.NoError(t, db.TruncateBeans(t.Context(), &user_model.User{}))
|
||||||
|
// }()
|
||||||
|
// Prepare test user
|
||||||
|
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
err := microcmdUserCreate().Run(ctx, []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// load test user
|
||||||
|
userBase := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
|
||||||
|
// Change the password
|
||||||
|
err = microcmdUserChangePassword().Run(ctx, []string{"change-password", "--username", "testuser", "--password", "newpassword"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Verify the password has been changed
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
assert.NotEqual(t, userBase.Passwd, user.Passwd)
|
||||||
|
assert.NotEqual(t, userBase.Salt, user.Salt)
|
||||||
|
|
||||||
|
// Additional check for must-change-password flag
|
||||||
|
require.NoError(t, microcmdUserChangePassword().Run(ctx, []string{"change-password", "--username", "testuser", "--password", "anotherpassword", "--must-change-password=false"}))
|
||||||
|
user = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
assert.False(t, user.MustChangePassword)
|
||||||
|
|
||||||
|
require.NoError(t, microcmdUserChangePassword().Run(ctx, []string{"change-password", "--username", "testuser", "--password", "yetanotherpassword", "--must-change-password"}))
|
||||||
|
user = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
assert.True(t, user.MustChangePassword)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("failure cases", func(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "user does not exist",
|
||||||
|
args: []string{"change-password", "--username", "nonexistentuser", "--password", "newpassword"},
|
||||||
|
expectedErr: "user does not exist",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing username",
|
||||||
|
args: []string{"change-password", "--password", "newpassword"},
|
||||||
|
expectedErr: `"username" not set`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing password",
|
||||||
|
args: []string{"change-password", "--username", "testuser"},
|
||||||
|
expectedErr: `"password" not set`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "too short password",
|
||||||
|
args: []string{"change-password", "--username", "testuser", "--password", "1"},
|
||||||
|
expectedErr: "password is not long enough",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := microcmdUserChangePassword().Run(ctx, tc.args)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), tc.expectedErr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
@@ -15,69 +16,110 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var microcmdUserCreate = &cli.Command{
|
func microcmdUserCreate() *cli.Command {
|
||||||
Name: "create",
|
return &cli.Command{
|
||||||
Usage: "Create a new user in database",
|
Name: "create",
|
||||||
Action: runCreateUser,
|
Usage: "Create a new user in database",
|
||||||
Flags: []cli.Flag{
|
Action: runCreateUser,
|
||||||
&cli.StringFlag{
|
MutuallyExclusiveFlags: []cli.MutuallyExclusiveFlags{
|
||||||
Name: "name",
|
{
|
||||||
Usage: "Username. DEPRECATED: use username instead",
|
Flags: [][]cli.Flag{
|
||||||
|
{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Usage: "Username. DEPRECATED: use username instead",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "username",
|
||||||
|
Usage: "Username",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
Flags: []cli.Flag{
|
||||||
Name: "username",
|
&cli.StringFlag{
|
||||||
Usage: "Username",
|
Name: "user-type",
|
||||||
|
Usage: "Set user's type: individual or bot",
|
||||||
|
Value: "individual",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "password",
|
||||||
|
Usage: "User password",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "email",
|
||||||
|
Usage: "User email address",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "admin",
|
||||||
|
Usage: "User is an admin",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "random-password",
|
||||||
|
Usage: "Generate a random password for the user",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "must-change-password",
|
||||||
|
Usage: "User must change password after initial login, defaults to true for all users except the first one (can be disabled by --must-change-password=false)",
|
||||||
|
HideDefault: true,
|
||||||
|
},
|
||||||
|
&cli.IntFlag{
|
||||||
|
Name: "random-password-length",
|
||||||
|
Usage: "Length of the random password to be generated",
|
||||||
|
Value: 12,
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "access-token",
|
||||||
|
Usage: "Generate access token for the user",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "access-token-name",
|
||||||
|
Usage: `Name of the generated access token`,
|
||||||
|
Value: "gitea-admin",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "access-token-scopes",
|
||||||
|
Usage: `Scopes of the generated access token, comma separated. Examples: "all", "public-only,read:issue", "write:repository,write:user"`,
|
||||||
|
Value: "all",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "restricted",
|
||||||
|
Usage: "Make a restricted user account",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "fullname",
|
||||||
|
Usage: `The full, human-readable name of the user`,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
}
|
||||||
Name: "password",
|
|
||||||
Usage: "User password",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "email",
|
|
||||||
Usage: "User email address",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "admin",
|
|
||||||
Usage: "User is an admin",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "random-password",
|
|
||||||
Usage: "Generate a random password for the user",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "must-change-password",
|
|
||||||
Usage: "User must change password after initial login, defaults to true for all users except the first one (can be disabled by --must-change-password=false)",
|
|
||||||
DisableDefaultText: true,
|
|
||||||
},
|
|
||||||
&cli.IntFlag{
|
|
||||||
Name: "random-password-length",
|
|
||||||
Usage: "Length of the random password to be generated",
|
|
||||||
Value: 12,
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "access-token",
|
|
||||||
Usage: "Generate access token for the user",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "restricted",
|
|
||||||
Usage: "Make a restricted user account",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCreateUser(c *cli.Context) error {
|
func runCreateUser(ctx context.Context, c *cli.Command) error {
|
||||||
if err := argsSet(c, "email"); err != nil {
|
// this command highly depends on the many setting options (create org, visibility, etc.), so it must have a full setting load first
|
||||||
return err
|
// duplicate setting loading should be safe at the moment, but it should be refactored & improved in the future.
|
||||||
}
|
setting.LoadSettings()
|
||||||
|
|
||||||
if c.IsSet("name") && c.IsSet("username") {
|
userTypes := map[string]user_model.UserType{
|
||||||
return errors.New("cannot set both --name and --username flags")
|
"individual": user_model.UserTypeIndividual,
|
||||||
|
"bot": user_model.UserTypeBot,
|
||||||
}
|
}
|
||||||
if !c.IsSet("name") && !c.IsSet("username") {
|
userType, ok := userTypes[c.String("user-type")]
|
||||||
return errors.New("one of --name or --username flags must be set")
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid user type: %s", c.String("user-type"))
|
||||||
|
}
|
||||||
|
if userType != user_model.UserTypeIndividual {
|
||||||
|
// Some other commands like "change-password" also only support individual users.
|
||||||
|
// It needs to clarify the "password" behavior for bot users in the future.
|
||||||
|
// At the moment, we do not allow setting password for bot users.
|
||||||
|
if c.IsSet("password") || c.IsSet("random-password") {
|
||||||
|
return errors.New("password can only be set for individual users")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.IsSet("password") && c.IsSet("random-password") {
|
if c.IsSet("password") && c.IsSet("random-password") {
|
||||||
@@ -89,16 +131,12 @@ func runCreateUser(c *cli.Context) error {
|
|||||||
username = c.String("username")
|
username = c.String("username")
|
||||||
} else {
|
} else {
|
||||||
username = c.String("name")
|
username = c.String("name")
|
||||||
_, _ = fmt.Fprintf(c.App.ErrWriter, "--name flag is deprecated. Use --username instead.\n")
|
_, _ = fmt.Fprintf(c.ErrWriter, "--name flag is deprecated. Use --username instead.\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := c.Context
|
|
||||||
if !setting.IsInTesting {
|
if !setting.IsInTesting {
|
||||||
// FIXME: need to refactor the "installSignals/initDB" related code later
|
// FIXME: need to refactor the "initDB" related code later
|
||||||
// it doesn't make sense to call it in (almost) every command action function
|
// it doesn't make sense to call it in (almost) every command action function
|
||||||
var cancel context.CancelFunc
|
|
||||||
ctx, cancel = installSignals()
|
|
||||||
defer cancel()
|
|
||||||
if err := initDB(ctx); err != nil {
|
if err := initDB(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -114,16 +152,19 @@ func runCreateUser(c *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Printf("generated random password is '%s'\n", password)
|
fmt.Printf("generated random password is '%s'\n", password)
|
||||||
} else {
|
} else if userType == user_model.UserTypeIndividual {
|
||||||
return errors.New("must set either password or random-password flag")
|
return errors.New("must set either password or random-password flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
isAdmin := c.Bool("admin")
|
isAdmin := c.Bool("admin")
|
||||||
mustChangePassword := true // always default to true
|
mustChangePassword := true // always default to true
|
||||||
if c.IsSet("must-change-password") {
|
if c.IsSet("must-change-password") {
|
||||||
|
if userType != user_model.UserTypeIndividual {
|
||||||
|
return errors.New("must-change-password flag can only be set for individual users")
|
||||||
|
}
|
||||||
// if the flag is set, use the value provided by the user
|
// if the flag is set, use the value provided by the user
|
||||||
mustChangePassword = c.Bool("must-change-password")
|
mustChangePassword = c.Bool("must-change-password")
|
||||||
} else {
|
} else if userType == user_model.UserTypeIndividual {
|
||||||
// check whether there are users in the database
|
// check whether there are users in the database
|
||||||
hasUserRecord, err := db.IsTableNotEmpty(&user_model.User{})
|
hasUserRecord, err := db.IsTableNotEmpty(&user_model.User{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -147,10 +188,12 @@ func runCreateUser(c *cli.Context) error {
|
|||||||
u := &user_model.User{
|
u := &user_model.User{
|
||||||
Name: username,
|
Name: username,
|
||||||
Email: c.String("email"),
|
Email: c.String("email"),
|
||||||
Passwd: password,
|
|
||||||
IsAdmin: isAdmin,
|
IsAdmin: isAdmin,
|
||||||
|
Type: userType,
|
||||||
|
Passwd: password,
|
||||||
MustChangePassword: mustChangePassword,
|
MustChangePassword: mustChangePassword,
|
||||||
Visibility: visibility,
|
Visibility: visibility,
|
||||||
|
FullName: c.String("fullname"),
|
||||||
}
|
}
|
||||||
|
|
||||||
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
||||||
@@ -158,23 +201,40 @@ func runCreateUser(c *cli.Context) error {
|
|||||||
IsRestricted: restricted,
|
IsRestricted: restricted,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var accessTokenName string
|
||||||
|
var accessTokenScope auth_model.AccessTokenScope
|
||||||
|
if c.IsSet("access-token") {
|
||||||
|
accessTokenName = strings.TrimSpace(c.String("access-token-name"))
|
||||||
|
if accessTokenName == "" {
|
||||||
|
return errors.New("access-token-name cannot be empty")
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
accessTokenScope, err = auth_model.AccessTokenScope(c.String("access-token-scopes")).Normalize()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid access token scope provided: %w", err)
|
||||||
|
}
|
||||||
|
if !accessTokenScope.HasPermissionScope() {
|
||||||
|
return errors.New("access token does not have any permission")
|
||||||
|
}
|
||||||
|
} else if c.IsSet("access-token-name") || c.IsSet("access-token-scopes") {
|
||||||
|
return errors.New("access-token-name and access-token-scopes flags are only valid when access-token flag is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
// arguments should be prepared before creating the user & access token, in case there is anything wrong
|
||||||
|
|
||||||
|
// create the user
|
||||||
if err := user_model.CreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil {
|
if err := user_model.CreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil {
|
||||||
return fmt.Errorf("CreateUser: %w", err)
|
return fmt.Errorf("CreateUser: %w", err)
|
||||||
}
|
}
|
||||||
|
fmt.Printf("New user '%s' has been successfully created!\n", username)
|
||||||
|
|
||||||
if c.Bool("access-token") {
|
// create the access token
|
||||||
t := &auth_model.AccessToken{
|
if accessTokenScope != "" {
|
||||||
Name: "gitea-admin",
|
t := &auth_model.AccessToken{Name: accessTokenName, UID: u.ID, Scope: accessTokenScope}
|
||||||
UID: u.ID,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := auth_model.NewAccessToken(ctx, t); err != nil {
|
if err := auth_model.NewAccessToken(ctx, t); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Access token was successfully created... %s\n", t.Token)
|
fmt.Printf("Access token was successfully created... %s\n", t.Token)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("New user '%s' has been successfully created!\n", username)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,37 +8,127 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAdminUserCreate(t *testing.T) {
|
func TestAdminUserCreate(t *testing.T) {
|
||||||
app := NewMainApp(AppVersion{})
|
|
||||||
|
|
||||||
reset := func() {
|
reset := func() {
|
||||||
assert.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
|
require.NoError(t, db.TruncateBeans(t.Context(), &user_model.User{}))
|
||||||
assert.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{}))
|
require.NoError(t, db.TruncateBeans(t.Context(), &user_model.EmailAddress{}))
|
||||||
|
require.NoError(t, db.TruncateBeans(t.Context(), &auth_model.AccessToken{}))
|
||||||
}
|
}
|
||||||
|
|
||||||
type createCheck struct{ IsAdmin, MustChangePassword bool }
|
t.Run("MustChangePassword", func(t *testing.T) {
|
||||||
createUser := func(name, args string) createCheck {
|
type check struct {
|
||||||
assert.NoError(t, app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s --password foobar", name, name, args))))
|
IsAdmin bool
|
||||||
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name})
|
MustChangePassword bool
|
||||||
return createCheck{u.IsAdmin, u.MustChangePassword}
|
}
|
||||||
|
|
||||||
|
createCheck := func(name, args string) check {
|
||||||
|
require.NoError(t, microcmdUserCreate().Run(t.Context(), strings.Fields(fmt.Sprintf("create --username %s --email %s@gitea.local %s --password foobar", name, name, args))))
|
||||||
|
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name})
|
||||||
|
return check{IsAdmin: u.IsAdmin, MustChangePassword: u.MustChangePassword}
|
||||||
|
}
|
||||||
|
reset()
|
||||||
|
assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u", ""), "first non-admin user doesn't need to change password")
|
||||||
|
|
||||||
|
reset()
|
||||||
|
assert.Equal(t, check{IsAdmin: true, MustChangePassword: false}, createCheck("u", "--admin"), "first admin user doesn't need to change password")
|
||||||
|
|
||||||
|
reset()
|
||||||
|
assert.Equal(t, check{IsAdmin: true, MustChangePassword: true}, createCheck("u", "--admin --must-change-password"))
|
||||||
|
assert.Equal(t, check{IsAdmin: true, MustChangePassword: true}, createCheck("u2", "--admin"))
|
||||||
|
assert.Equal(t, check{IsAdmin: true, MustChangePassword: false}, createCheck("u3", "--admin --must-change-password=false"))
|
||||||
|
assert.Equal(t, check{IsAdmin: false, MustChangePassword: true}, createCheck("u4", ""))
|
||||||
|
assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u5", "--must-change-password=false"))
|
||||||
|
})
|
||||||
|
|
||||||
|
createUser := func(name string, args ...string) error {
|
||||||
|
return microcmdUserCreate().Run(t.Context(), append([]string{"create", "--username", name, "--email", name + "@gitea.local"}, args...))
|
||||||
}
|
}
|
||||||
reset()
|
|
||||||
assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: false}, createUser("u", ""), "first non-admin user doesn't need to change password")
|
|
||||||
|
|
||||||
reset()
|
t.Run("UserType", func(t *testing.T) {
|
||||||
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: false}, createUser("u", "--admin"), "first admin user doesn't need to change password")
|
reset()
|
||||||
|
assert.ErrorContains(t, createUser("u", "--user-type", "invalid"), "invalid user type")
|
||||||
|
assert.ErrorContains(t, createUser("u", "--user-type", "bot", "--password", "123"), "can only be set for individual users")
|
||||||
|
assert.ErrorContains(t, createUser("u", "--user-type", "bot", "--must-change-password"), "can only be set for individual users")
|
||||||
|
|
||||||
reset()
|
assert.NoError(t, createUser("u", "--user-type", "bot"))
|
||||||
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: true}, createUser("u", "--admin --must-change-password"))
|
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "u"})
|
||||||
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: true}, createUser("u2", "--admin"))
|
assert.Equal(t, user_model.UserTypeBot, u.Type)
|
||||||
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: false}, createUser("u3", "--admin --must-change-password=false"))
|
assert.Empty(t, u.Passwd)
|
||||||
assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: true}, createUser("u4", ""))
|
})
|
||||||
assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: false}, createUser("u5", "--must-change-password=false"))
|
|
||||||
|
t.Run("AccessToken", func(t *testing.T) {
|
||||||
|
// no generated access token
|
||||||
|
reset()
|
||||||
|
assert.NoError(t, createUser("u", "--random-password"))
|
||||||
|
assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
|
||||||
|
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
|
||||||
|
|
||||||
|
// using "--access-token" only means "all" access
|
||||||
|
reset()
|
||||||
|
assert.NoError(t, createUser("u", "--random-password", "--access-token"))
|
||||||
|
assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
|
||||||
|
assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{}))
|
||||||
|
accessToken := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "gitea-admin"})
|
||||||
|
hasScopes, err := accessToken.Scope.HasScope(auth_model.AccessTokenScopeWriteAdmin, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, hasScopes)
|
||||||
|
|
||||||
|
// using "--access-token" with name & scopes
|
||||||
|
reset()
|
||||||
|
assert.NoError(t, createUser("u", "--random-password", "--access-token", "--access-token-name", "new-token-name", "--access-token-scopes", "read:issue,read:user"))
|
||||||
|
assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
|
||||||
|
assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{}))
|
||||||
|
accessToken = unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "new-token-name"})
|
||||||
|
hasScopes, err = accessToken.Scope.HasScope(auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopeReadUser)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, hasScopes)
|
||||||
|
hasScopes, err = accessToken.Scope.HasScope(auth_model.AccessTokenScopeWriteAdmin, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.False(t, hasScopes)
|
||||||
|
|
||||||
|
// using "--access-token-name" without "--access-token"
|
||||||
|
reset()
|
||||||
|
err = createUser("u", "--random-password", "--access-token-name", "new-token-name")
|
||||||
|
assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
|
||||||
|
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
|
||||||
|
assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set")
|
||||||
|
|
||||||
|
// using "--access-token-scopes" without "--access-token"
|
||||||
|
reset()
|
||||||
|
err = createUser("u", "--random-password", "--access-token-scopes", "read:issue")
|
||||||
|
assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
|
||||||
|
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
|
||||||
|
assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set")
|
||||||
|
|
||||||
|
// empty permission
|
||||||
|
reset()
|
||||||
|
err = createUser("u", "--random-password", "--access-token", "--access-token-scopes", "public-only")
|
||||||
|
assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
|
||||||
|
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
|
||||||
|
assert.ErrorContains(t, err, "access token does not have any permission")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("UserFields", func(t *testing.T) {
|
||||||
|
reset()
|
||||||
|
assert.NoError(t, createUser("u-FullNameWithSpace", "--random-password", "--fullname", "First O'Middle Last"))
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &user_model.User{
|
||||||
|
Name: "u-FullNameWithSpace",
|
||||||
|
LowerName: "u-fullnamewithspace",
|
||||||
|
FullName: "First O'Middle Last",
|
||||||
|
Email: "u-FullNameWithSpace@gitea.local",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NoError(t, createUser("u-FullNameEmpty", "--random-password", "--fullname", ""))
|
||||||
|
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "u-fullnameempty"})
|
||||||
|
assert.Empty(t, u.FullName)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,53 +4,56 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"code.gitea.io/gitea/modules/storage"
|
||||||
user_service "code.gitea.io/gitea/services/user"
|
user_service "code.gitea.io/gitea/services/user"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var microcmdUserDelete = &cli.Command{
|
func microcmdUserDelete() *cli.Command {
|
||||||
Name: "delete",
|
return &cli.Command{
|
||||||
Usage: "Delete specific user by id, name or email",
|
Name: "delete",
|
||||||
Flags: []cli.Flag{
|
Usage: "Delete specific user by id, name or email",
|
||||||
&cli.Int64Flag{
|
Flags: []cli.Flag{
|
||||||
Name: "id",
|
&cli.Int64Flag{
|
||||||
Usage: "ID of user of the user to delete",
|
Name: "id",
|
||||||
|
Usage: "ID of user of the user to delete",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "username",
|
||||||
|
Aliases: []string{"u"},
|
||||||
|
Usage: "Username of the user to delete",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "email",
|
||||||
|
Aliases: []string{"e"},
|
||||||
|
Usage: "Email of the user to delete",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "purge",
|
||||||
|
Usage: "Purge user, all their repositories, organizations and comments",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
Action: runDeleteUser,
|
||||||
Name: "username",
|
}
|
||||||
Aliases: []string{"u"},
|
|
||||||
Usage: "Username of the user to delete",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "email",
|
|
||||||
Aliases: []string{"e"},
|
|
||||||
Usage: "Email of the user to delete",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "purge",
|
|
||||||
Usage: "Purge user, all their repositories, organizations and comments",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: runDeleteUser,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDeleteUser(c *cli.Context) error {
|
func runDeleteUser(ctx context.Context, c *cli.Command) error {
|
||||||
if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") {
|
if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") {
|
||||||
return errors.New("You must provide the id, username or email of a user to delete")
|
return errors.New("You must provide the id, username or email of a user to delete")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := installSignals()
|
if !setting.IsInTesting {
|
||||||
defer cancel()
|
if err := initDB(ctx); err != nil {
|
||||||
|
return err
|
||||||
if err := initDB(ctx); err != nil {
|
}
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := storage.Init(); err != nil {
|
if err := storage.Init(); err != nil {
|
||||||
@@ -70,11 +73,11 @@ func runDeleteUser(c *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if c.IsSet("username") && user.LowerName != strings.ToLower(strings.TrimSpace(c.String("username"))) {
|
if c.IsSet("username") && user.LowerName != strings.ToLower(strings.TrimSpace(c.String("username"))) {
|
||||||
return fmt.Errorf("The user %s who has email %s does not match the provided username %s", user.Name, c.String("email"), c.String("username"))
|
return fmt.Errorf("the user %s who has email %s does not match the provided username %s", user.Name, c.String("email"), c.String("username"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.IsSet("id") && user.ID != c.Int64("id") {
|
if c.IsSet("id") && user.ID != c.Int64("id") {
|
||||||
return fmt.Errorf("The user %s does not match the provided id %d", user.Name, c.Int64("id"))
|
return fmt.Errorf("the user %s does not match the provided id %d", user.Name, c.Int64("id"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return user_service.DeleteUser(ctx, user, c.Bool("purge"))
|
return user_service.DeleteUser(ctx, user, c.Bool("purge"))
|
||||||
|
|||||||
111
cmd/admin_user_delete_test.go
Normal file
111
cmd/admin_user_delete_test.go
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAdminUserDelete(t *testing.T) {
|
||||||
|
ctx := t.Context()
|
||||||
|
defer func() {
|
||||||
|
require.NoError(t, db.TruncateBeans(t.Context(), &user_model.User{}))
|
||||||
|
require.NoError(t, db.TruncateBeans(t.Context(), &user_model.EmailAddress{}))
|
||||||
|
require.NoError(t, db.TruncateBeans(t.Context(), &auth_model.AccessToken{}))
|
||||||
|
}()
|
||||||
|
|
||||||
|
setupTestUser := func(t *testing.T) {
|
||||||
|
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("delete user by id", func(t *testing.T) {
|
||||||
|
setupTestUser(t)
|
||||||
|
|
||||||
|
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--id", strconv.FormatInt(u.ID, 10)})
|
||||||
|
require.NoError(t, err)
|
||||||
|
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
})
|
||||||
|
t.Run("delete user by username", func(t *testing.T) {
|
||||||
|
setupTestUser(t)
|
||||||
|
|
||||||
|
err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--username", "testuser"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
})
|
||||||
|
t.Run("delete user by email", func(t *testing.T) {
|
||||||
|
setupTestUser(t)
|
||||||
|
|
||||||
|
err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--email", "testuser@gitea.local"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
})
|
||||||
|
t.Run("delete user by all 3 attributes", func(t *testing.T) {
|
||||||
|
setupTestUser(t)
|
||||||
|
|
||||||
|
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
err := microcmdUserDelete().Run(ctx, []string{"delete", "--id", strconv.FormatInt(u.ID, 10), "--username", "testuser", "--email", "testuser@gitea.local"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdminUserDeleteFailure(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no user to delete",
|
||||||
|
args: []string{"delete", "--username", "nonexistentuser"},
|
||||||
|
expectedErr: "user does not exist",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "user exists but provided username does not match",
|
||||||
|
args: []string{"delete", "--email", "testuser@gitea.local", "--username", "wrongusername"},
|
||||||
|
expectedErr: "the user testuser who has email testuser@gitea.local does not match the provided username wrongusername",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "user exists but provided id does not match",
|
||||||
|
args: []string{"delete", "--username", "testuser", "--id", "999"},
|
||||||
|
expectedErr: "the user testuser does not match the provided id 999",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no required flags are provided",
|
||||||
|
args: []string{"delete"},
|
||||||
|
expectedErr: "You must provide the id, username or email of a user to delete",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
ctx := t.Context()
|
||||||
|
if strings.Contains(tc.name, "user exists") {
|
||||||
|
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := microcmdUserDelete().Run(ctx, tc.args)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), tc.expectedErr)
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, db.TruncateBeans(t.Context(), &user_model.User{}))
|
||||||
|
require.NoError(t, db.TruncateBeans(t.Context(), &user_model.EmailAddress{}))
|
||||||
|
require.NoError(t, db.TruncateBeans(t.Context(), &auth_model.AccessToken{}))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,13 +4,14 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var microcmdUserGenerateAccessToken = &cli.Command{
|
var microcmdUserGenerateAccessToken = &cli.Command{
|
||||||
@@ -34,21 +35,18 @@ var microcmdUserGenerateAccessToken = &cli.Command{
|
|||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "scopes",
|
Name: "scopes",
|
||||||
Value: "",
|
Value: "all",
|
||||||
Usage: "Comma separated list of scopes to apply to access token",
|
Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: runGenerateAccessToken,
|
Action: runGenerateAccessToken,
|
||||||
}
|
}
|
||||||
|
|
||||||
func runGenerateAccessToken(c *cli.Context) error {
|
func runGenerateAccessToken(ctx context.Context, c *cli.Command) error {
|
||||||
if !c.IsSet("username") {
|
if !c.IsSet("username") {
|
||||||
return errors.New("You must provide a username to generate a token for")
|
return errors.New("you must provide a username to generate a token for")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
if err := initDB(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -77,6 +75,9 @@ func runGenerateAccessToken(c *cli.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid access token scope provided: %w", err)
|
return fmt.Errorf("invalid access token scope provided: %w", err)
|
||||||
}
|
}
|
||||||
|
if !accessTokenScope.HasPermissionScope() {
|
||||||
|
return errors.New("access token does not have any permission")
|
||||||
|
}
|
||||||
t.Scope = accessTokenScope
|
t.Scope = accessTokenScope
|
||||||
|
|
||||||
// create the token
|
// create the token
|
||||||
|
|||||||
@@ -4,13 +4,14 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var microcmdUserList = &cli.Command{
|
var microcmdUserList = &cli.Command{
|
||||||
@@ -25,10 +26,7 @@ var microcmdUserList = &cli.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func runListUsers(c *cli.Context) error {
|
func runListUsers(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
if err := initDB(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,40 +4,41 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var microcmdUserMustChangePassword = &cli.Command{
|
func microcmdUserMustChangePassword() *cli.Command {
|
||||||
Name: "must-change-password",
|
return &cli.Command{
|
||||||
Usage: "Set the must change password flag for the provided users or all users",
|
Name: "must-change-password",
|
||||||
Action: runMustChangePassword,
|
Usage: "Set the must change password flag for the provided users or all users",
|
||||||
Flags: []cli.Flag{
|
Action: runMustChangePassword,
|
||||||
&cli.BoolFlag{
|
Flags: []cli.Flag{
|
||||||
Name: "all",
|
&cli.BoolFlag{
|
||||||
Aliases: []string{"A"},
|
Name: "all",
|
||||||
Usage: "All users must change password, except those explicitly excluded with --exclude",
|
Aliases: []string{"A"},
|
||||||
|
Usage: "All users must change password, except those explicitly excluded with --exclude",
|
||||||
|
},
|
||||||
|
&cli.StringSliceFlag{
|
||||||
|
Name: "exclude",
|
||||||
|
Aliases: []string{"e"},
|
||||||
|
Usage: "Do not change the must-change-password flag for these users",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "unset",
|
||||||
|
Usage: "Instead of setting the must-change-password flag, unset it",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
&cli.StringSliceFlag{
|
}
|
||||||
Name: "exclude",
|
|
||||||
Aliases: []string{"e"},
|
|
||||||
Usage: "Do not change the must-change-password flag for these users",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "unset",
|
|
||||||
Usage: "Instead of setting the must-change-password flag, unset it",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runMustChangePassword(c *cli.Context) error {
|
func runMustChangePassword(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if c.NArg() == 0 && !c.IsSet("all") {
|
if c.NArg() == 0 && !c.IsSet("all") {
|
||||||
return errors.New("either usernames or --all must be provided")
|
return errors.New("either usernames or --all must be provided")
|
||||||
}
|
}
|
||||||
@@ -46,8 +47,10 @@ func runMustChangePassword(c *cli.Context) error {
|
|||||||
all := c.Bool("all")
|
all := c.Bool("all")
|
||||||
exclude := c.StringSlice("exclude")
|
exclude := c.StringSlice("exclude")
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
if !setting.IsInTesting {
|
||||||
return err
|
if err := initDB(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err := user_model.SetMustChangePassword(ctx, all, mustChangePassword, c.Args().Slice(), exclude)
|
n, err := user_model.SetMustChangePassword(ctx, all, mustChangePassword, c.Args().Slice(), exclude)
|
||||||
|
|||||||
78
cmd/admin_user_must_change_password_test.go
Normal file
78
cmd/admin_user_must_change_password_test.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMustChangePassword(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
require.NoError(t, db.TruncateBeans(t.Context(), &user_model.User{}))
|
||||||
|
}()
|
||||||
|
err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuserexclude", "--email", "testuserexclude@gitea.local", "--random-password"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
// Reset password change flag
|
||||||
|
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--unset"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
assert.False(t, testUser.MustChangePassword)
|
||||||
|
testUserExclude := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
|
||||||
|
assert.False(t, testUserExclude.MustChangePassword)
|
||||||
|
|
||||||
|
// Make all users change password
|
||||||
|
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
assert.True(t, testUser.MustChangePassword)
|
||||||
|
testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
|
||||||
|
assert.True(t, testUserExclude.MustChangePassword)
|
||||||
|
|
||||||
|
// Reset password change flag but exclude all tested users
|
||||||
|
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--unset", "--exclude", "testuser,testuserexclude"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
assert.True(t, testUser.MustChangePassword)
|
||||||
|
testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
|
||||||
|
assert.True(t, testUserExclude.MustChangePassword)
|
||||||
|
|
||||||
|
// Reset password change flag by listing multiple users
|
||||||
|
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--unset", "testuser", "testuserexclude"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
assert.False(t, testUser.MustChangePassword)
|
||||||
|
testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
|
||||||
|
assert.False(t, testUserExclude.MustChangePassword)
|
||||||
|
|
||||||
|
// Exclude a user from all user
|
||||||
|
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--exclude", "testuserexclude"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
assert.True(t, testUser.MustChangePassword)
|
||||||
|
testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
|
||||||
|
assert.False(t, testUserExclude.MustChangePassword)
|
||||||
|
|
||||||
|
// Unset a flag for single user
|
||||||
|
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--unset", "testuser"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
assert.False(t, testUser.MustChangePassword)
|
||||||
|
testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
|
||||||
|
assert.False(t, testUserExclude.MustChangePassword)
|
||||||
|
}
|
||||||
128
cmd/cert.go
128
cmd/cert.go
@@ -6,6 +6,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
@@ -13,6 +14,7 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
@@ -20,47 +22,59 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdCert represents the available cert sub-command.
|
// cmdCert represents the available cert sub-command.
|
||||||
var CmdCert = &cli.Command{
|
func cmdCert() *cli.Command {
|
||||||
Name: "cert",
|
return &cli.Command{
|
||||||
Usage: "Generate self-signed certificate",
|
Name: "cert",
|
||||||
Description: `Generate a self-signed X.509 certificate for a TLS server.
|
Usage: "Generate self-signed certificate",
|
||||||
|
Description: `Generate a self-signed X.509 certificate for a TLS server.
|
||||||
Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`,
|
Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`,
|
||||||
Action: runCert,
|
Action: runCert,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "host",
|
Name: "host",
|
||||||
Value: "",
|
Usage: "Comma-separated hostnames and IPs to generate a certificate for",
|
||||||
Usage: "Comma-separated hostnames and IPs to generate a certificate for",
|
Required: true,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "ecdsa-curve",
|
||||||
|
Value: "",
|
||||||
|
Usage: "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521",
|
||||||
|
},
|
||||||
|
&cli.IntFlag{
|
||||||
|
Name: "rsa-bits",
|
||||||
|
Value: 3072,
|
||||||
|
Usage: "Size of RSA key to generate. Ignored if --ecdsa-curve is set",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "start-date",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Creation date formatted as Jan 1 15:04:05 2011",
|
||||||
|
},
|
||||||
|
&cli.DurationFlag{
|
||||||
|
Name: "duration",
|
||||||
|
Value: 365 * 24 * time.Hour,
|
||||||
|
Usage: "Duration that certificate is valid for",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "ca",
|
||||||
|
Usage: "whether this cert should be its own Certificate Authority",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "out",
|
||||||
|
Value: "cert.pem",
|
||||||
|
Usage: "Path to the file where there certificate will be saved",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "keyout",
|
||||||
|
Value: "key.pem",
|
||||||
|
Usage: "Path to the file where there certificate key will be saved",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
}
|
||||||
Name: "ecdsa-curve",
|
|
||||||
Value: "",
|
|
||||||
Usage: "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521",
|
|
||||||
},
|
|
||||||
&cli.IntFlag{
|
|
||||||
Name: "rsa-bits",
|
|
||||||
Value: 3072,
|
|
||||||
Usage: "Size of RSA key to generate. Ignored if --ecdsa-curve is set",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "start-date",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Creation date formatted as Jan 1 15:04:05 2011",
|
|
||||||
},
|
|
||||||
&cli.DurationFlag{
|
|
||||||
Name: "duration",
|
|
||||||
Value: 365 * 24 * time.Hour,
|
|
||||||
Usage: "Duration that certificate is valid for",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "ca",
|
|
||||||
Usage: "whether this cert should be its own Certificate Authority",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func publicKey(priv any) any {
|
func publicKey(priv any) any {
|
||||||
@@ -89,11 +103,7 @@ func pemBlockForKey(priv any) *pem.Block {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCert(c *cli.Context) error {
|
func runCert(_ context.Context, c *cli.Command) error {
|
||||||
if err := argsSet(c, "host"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var priv any
|
var priv any
|
||||||
var err error
|
var err error
|
||||||
switch c.String("ecdsa-curve") {
|
switch c.String("ecdsa-curve") {
|
||||||
@@ -108,17 +118,17 @@ func runCert(c *cli.Context) error {
|
|||||||
case "P521":
|
case "P521":
|
||||||
priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
|
priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
|
||||||
default:
|
default:
|
||||||
log.Fatalf("Unrecognized elliptic curve: %q", c.String("ecdsa-curve"))
|
err = fmt.Errorf("unrecognized elliptic curve: %q", c.String("ecdsa-curve"))
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to generate private key: %v", err)
|
return fmt.Errorf("failed to generate private key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var notBefore time.Time
|
var notBefore time.Time
|
||||||
if startDate := c.String("start-date"); startDate != "" {
|
if startDate := c.String("start-date"); startDate != "" {
|
||||||
notBefore, err = time.Parse("Jan 2 15:04:05 2006", startDate)
|
notBefore, err = time.Parse("Jan 2 15:04:05 2006", startDate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to parse creation date: %v", err)
|
return fmt.Errorf("failed to parse creation date %w", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
notBefore = time.Now()
|
notBefore = time.Now()
|
||||||
@@ -129,7 +139,7 @@ func runCert(c *cli.Context) error {
|
|||||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to generate serial number: %v", err)
|
return fmt.Errorf("failed to generate serial number: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
template := x509.Certificate{
|
template := x509.Certificate{
|
||||||
@@ -146,8 +156,8 @@ func runCert(c *cli.Context) error {
|
|||||||
BasicConstraintsValid: true,
|
BasicConstraintsValid: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
hosts := strings.Split(c.String("host"), ",")
|
hosts := strings.SplitSeq(c.String("host"), ",")
|
||||||
for _, h := range hosts {
|
for h := range hosts {
|
||||||
if ip := net.ParseIP(h); ip != nil {
|
if ip := net.ParseIP(h); ip != nil {
|
||||||
template.IPAddresses = append(template.IPAddresses, ip)
|
template.IPAddresses = append(template.IPAddresses, ip)
|
||||||
} else {
|
} else {
|
||||||
@@ -162,35 +172,35 @@ func runCert(c *cli.Context) error {
|
|||||||
|
|
||||||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
|
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to create certificate: %v", err)
|
return fmt.Errorf("failed to create certificate: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
certOut, err := os.Create("cert.pem")
|
certOut, err := os.Create(c.String("out"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to open cert.pem for writing: %v", err)
|
return fmt.Errorf("failed to open %s for writing: %w", c.String("keyout"), err)
|
||||||
}
|
}
|
||||||
err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to encode certificate: %v", err)
|
return fmt.Errorf("failed to encode certificate: %w", err)
|
||||||
}
|
}
|
||||||
err = certOut.Close()
|
err = certOut.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to write cert: %v", err)
|
return fmt.Errorf("failed to write cert: %w", err)
|
||||||
}
|
}
|
||||||
log.Println("Written cert.pem")
|
fmt.Fprintf(c.Writer, "Written cert to %s\n", c.String("out"))
|
||||||
|
|
||||||
keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
|
keyOut, err := os.OpenFile(c.String("keyout"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to open key.pem for writing: %v", err)
|
return fmt.Errorf("failed to open %s for writing: %w", c.String("keyout"), err)
|
||||||
}
|
}
|
||||||
err = pem.Encode(keyOut, pemBlockForKey(priv))
|
err = pem.Encode(keyOut, pemBlockForKey(priv))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to encode key: %v", err)
|
return fmt.Errorf("failed to encode key: %w", err)
|
||||||
}
|
}
|
||||||
err = keyOut.Close()
|
err = keyOut.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to write key: %v", err)
|
return fmt.Errorf("failed to write key: %w", err)
|
||||||
}
|
}
|
||||||
log.Println("Written key.pem")
|
fmt.Fprintf(c.Writer, "Written key to %s\n", c.String("keyout"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
123
cmd/cert_test.go
Normal file
123
cmd/cert_test.go
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCertCommand(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "RSA cert generation",
|
||||||
|
args: []string{
|
||||||
|
"cert-test",
|
||||||
|
"--host", "localhost",
|
||||||
|
"--rsa-bits", "2048",
|
||||||
|
"--duration", "1h",
|
||||||
|
"--start-date", "Jan 1 00:00:00 2024",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ECDSA cert generation",
|
||||||
|
args: []string{
|
||||||
|
"cert-test",
|
||||||
|
"--host", "localhost",
|
||||||
|
"--ecdsa-curve", "P256",
|
||||||
|
"--duration", "1h",
|
||||||
|
"--start-date", "Jan 1 00:00:00 2024",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mixed host, certificate authority",
|
||||||
|
args: []string{
|
||||||
|
"cert-test",
|
||||||
|
"--host", "localhost,127.0.0.1",
|
||||||
|
"--duration", "1h",
|
||||||
|
"--start-date", "Jan 1 00:00:00 2024",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
app := cmdCert()
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
|
||||||
|
certFile := filepath.Join(tempDir, "cert.pem")
|
||||||
|
keyFile := filepath.Join(tempDir, "key.pem")
|
||||||
|
|
||||||
|
err := app.Run(t.Context(), append(c.args, "--out", certFile, "--keyout", keyFile))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.FileExists(t, certFile)
|
||||||
|
assert.FileExists(t, keyFile)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCertCommandFailures(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
errMsg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Start Date Parsing failure",
|
||||||
|
args: []string{
|
||||||
|
"cert-test",
|
||||||
|
"--host", "localhost",
|
||||||
|
"--start-date", "invalid-date",
|
||||||
|
},
|
||||||
|
errMsg: "parsing time",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Unknown curve",
|
||||||
|
args: []string{
|
||||||
|
"cert-test",
|
||||||
|
"--host", "localhost",
|
||||||
|
"--ecdsa-curve", "invalid-curve",
|
||||||
|
},
|
||||||
|
errMsg: "unrecognized elliptic curve",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Key generation failure",
|
||||||
|
args: []string{
|
||||||
|
"cert-test",
|
||||||
|
"--host", "localhost",
|
||||||
|
"--rsa-bits", "invalid-bits",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Missing parameters",
|
||||||
|
args: []string{
|
||||||
|
"cert-test",
|
||||||
|
},
|
||||||
|
errMsg: `"host" not set`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
app := cmdCert()
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
|
||||||
|
certFile := filepath.Join(tempDir, "cert.pem")
|
||||||
|
keyFile := filepath.Join(tempDir, "key.pem")
|
||||||
|
err := app.Run(t.Context(), append(c.args, "--out", certFile, "--keyout", keyFile))
|
||||||
|
require.Error(t, err)
|
||||||
|
if c.errMsg != "" {
|
||||||
|
assert.ErrorContains(t, err, c.errMsg)
|
||||||
|
}
|
||||||
|
assert.NoFileExists(t, certFile)
|
||||||
|
assert.NoFileExists(t, keyFile)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
31
cmd/cmd.go
31
cmd/cmd.go
@@ -18,20 +18,19 @@ import (
|
|||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// argsSet checks that all the required arguments are set. args is a list of
|
// argsSet checks that all the required arguments are set. args is a list of
|
||||||
// arguments that must be set in the passed Context.
|
// arguments that must be set in the passed Context.
|
||||||
func argsSet(c *cli.Context, args ...string) error {
|
func argsSet(c *cli.Command, args ...string) error {
|
||||||
for _, a := range args {
|
for _, a := range args {
|
||||||
if !c.IsSet(a) {
|
if !c.IsSet(a) {
|
||||||
return errors.New(a + " is not set")
|
return errors.New(a + " is not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
if util.IsEmptyString(c.String(a)) {
|
if c.Value(a) == nil {
|
||||||
return errors.New(a + " is required")
|
return errors.New(a + " is required")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,7 +108,7 @@ func setupConsoleLogger(level log.Level, colorize bool, out io.Writer) {
|
|||||||
log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer)
|
log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func globalBool(c *cli.Context, name string) bool {
|
func globalBool(c *cli.Command, name string) bool {
|
||||||
for _, ctx := range c.Lineage() {
|
for _, ctx := range c.Lineage() {
|
||||||
if ctx.Bool(name) {
|
if ctx.Bool(name) {
|
||||||
return true
|
return true
|
||||||
@@ -120,8 +119,14 @@ func globalBool(c *cli.Context, name string) bool {
|
|||||||
|
|
||||||
// PrepareConsoleLoggerLevel by default, use INFO level for console logger, but some sub-commands (for git/ssh protocol) shouldn't output any log to stdout.
|
// PrepareConsoleLoggerLevel by default, use INFO level for console logger, but some sub-commands (for git/ssh protocol) shouldn't output any log to stdout.
|
||||||
// Any log appears in git stdout pipe will break the git protocol, eg: client can't push and hangs forever.
|
// Any log appears in git stdout pipe will break the git protocol, eg: client can't push and hangs forever.
|
||||||
func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(*cli.Context) error {
|
func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(context.Context, *cli.Command) (context.Context, error) {
|
||||||
return func(c *cli.Context) error {
|
return func(ctx context.Context, c *cli.Command) (context.Context, error) {
|
||||||
|
if setting.InstallLock {
|
||||||
|
// During config loading, there might also be logs (for example: deprecation warnings).
|
||||||
|
// It must make sure that console logger is set up before config is loaded.
|
||||||
|
log.Error("Config is loaded before console logger is setup, it will cause bugs. Please fix it.")
|
||||||
|
return nil, errors.New("console logger must be setup before config is loaded")
|
||||||
|
}
|
||||||
level := defaultLevel
|
level := defaultLevel
|
||||||
if globalBool(c, "quiet") {
|
if globalBool(c, "quiet") {
|
||||||
level = log.FATAL
|
level = log.FATAL
|
||||||
@@ -130,6 +135,16 @@ func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(*cli.Context) error
|
|||||||
level = log.TRACE
|
level = log.TRACE
|
||||||
}
|
}
|
||||||
log.SetConsoleLogger(log.DEFAULT, "console-default", level)
|
log.SetConsoleLogger(log.DEFAULT, "console-default", level)
|
||||||
return nil
|
return ctx, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isValidDefaultSubCommand(cmd *cli.Command) (string, bool) {
|
||||||
|
// Dirty patch for urfave/cli's strange design.
|
||||||
|
// "./gitea bad-cmd" should not start the web server.
|
||||||
|
rootArgs := cmd.Root().Args().Slice()
|
||||||
|
if len(rootArgs) != 0 && rootArgs[0] != cmd.Name {
|
||||||
|
return rootArgs[0], false
|
||||||
|
}
|
||||||
|
return "", true
|
||||||
|
}
|
||||||
|
|||||||
38
cmd/cmd_test.go
Normal file
38
cmd/cmd_test.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/urfave/cli/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDefaultCommand(t *testing.T) {
|
||||||
|
test := func(t *testing.T, args []string, expectedRetName string, expectedRetValid bool) {
|
||||||
|
called := false
|
||||||
|
cmd := &cli.Command{
|
||||||
|
DefaultCommand: "test",
|
||||||
|
Commands: []*cli.Command{
|
||||||
|
{
|
||||||
|
Name: "test",
|
||||||
|
Action: func(ctx context.Context, command *cli.Command) error {
|
||||||
|
retName, retValid := isValidDefaultSubCommand(command)
|
||||||
|
assert.Equal(t, expectedRetName, retName)
|
||||||
|
assert.Equal(t, expectedRetValid, retValid)
|
||||||
|
called = true
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.NoError(t, cmd.Run(t.Context(), args))
|
||||||
|
assert.True(t, called)
|
||||||
|
}
|
||||||
|
test(t, []string{"./gitea"}, "", true)
|
||||||
|
test(t, []string{"./gitea", "test"}, "", true)
|
||||||
|
test(t, []string{"./gitea", "other"}, "other", false)
|
||||||
|
}
|
||||||
18
cmd/docs.go
18
cmd/docs.go
@@ -4,11 +4,13 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
cli_docs "github.com/urfave/cli-docs/v3"
|
||||||
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdDocs represents the available docs sub-command.
|
// CmdDocs represents the available docs sub-command.
|
||||||
@@ -30,16 +32,16 @@ var CmdDocs = &cli.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDocs(ctx *cli.Context) error {
|
func runDocs(_ context.Context, cmd *cli.Command) error {
|
||||||
docs, err := ctx.App.ToMarkdown()
|
docs, err := cli_docs.ToMarkdown(cmd.Root())
|
||||||
if ctx.Bool("man") {
|
if cmd.Bool("man") {
|
||||||
docs, err = ctx.App.ToMan()
|
docs, err = cli_docs.ToMan(cmd.Root())
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ctx.Bool("man") {
|
if !cmd.Bool("man") {
|
||||||
// Clean up markdown. The following bug was fixed in v2, but is present in v1.
|
// Clean up markdown. The following bug was fixed in v2, but is present in v1.
|
||||||
// It affects markdown output (even though the issue is referring to man pages)
|
// It affects markdown output (even though the issue is referring to man pages)
|
||||||
// https://github.com/urfave/cli/issues/1040
|
// https://github.com/urfave/cli/issues/1040
|
||||||
@@ -51,8 +53,8 @@ func runDocs(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
out := os.Stdout
|
out := os.Stdout
|
||||||
if ctx.String("output") != "" {
|
if cmd.String("output") != "" {
|
||||||
fi, err := os.Create(ctx.String("output"))
|
fi, err := os.Create(cmd.String("output"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
golog "log"
|
golog "log"
|
||||||
"os"
|
"os"
|
||||||
@@ -19,7 +20,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/services/doctor"
|
"code.gitea.io/gitea/services/doctor"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,7 +30,7 @@ var CmdDoctor = &cli.Command{
|
|||||||
Usage: "Diagnose and optionally fix problems, convert or re-create database tables",
|
Usage: "Diagnose and optionally fix problems, convert or re-create database tables",
|
||||||
Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
|
Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
|
||||||
|
|
||||||
Subcommands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
cmdDoctorCheck,
|
cmdDoctorCheck,
|
||||||
cmdRecreateTable,
|
cmdRecreateTable,
|
||||||
cmdDoctorConvert,
|
cmdDoctorConvert,
|
||||||
@@ -92,16 +93,13 @@ You should back-up your database before doing this and ensure that your database
|
|||||||
Action: runRecreateTable,
|
Action: runRecreateTable,
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRecreateTable(ctx *cli.Context) error {
|
func runRecreateTable(ctx context.Context, cmd *cli.Command) error {
|
||||||
stdCtx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// Redirect the default golog to here
|
// Redirect the default golog to here
|
||||||
golog.SetFlags(0)
|
golog.SetFlags(0)
|
||||||
golog.SetPrefix("")
|
golog.SetPrefix("")
|
||||||
golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info))
|
golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info))
|
||||||
|
|
||||||
debug := ctx.Bool("debug")
|
debug := cmd.Bool("debug")
|
||||||
setting.MustInstalled()
|
setting.MustInstalled()
|
||||||
setting.LoadDBSetting()
|
setting.LoadDBSetting()
|
||||||
|
|
||||||
@@ -112,15 +110,15 @@ func runRecreateTable(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setting.Database.LogSQL = debug
|
setting.Database.LogSQL = debug
|
||||||
if err := db.InitEngine(stdCtx); err != nil {
|
if err := db.InitEngine(ctx); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
fmt.Println("Check if you are using the right config file. You can use a --config directive to specify one.")
|
fmt.Println("Check if you are using the right config file. You can use a --config directive to specify one.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
args := ctx.Args()
|
args := cmd.Args()
|
||||||
names := make([]string, 0, ctx.NArg())
|
names := make([]string, 0, cmd.NArg())
|
||||||
for i := 0; i < ctx.NArg(); i++ {
|
for i := 0; i < cmd.NArg(); i++ {
|
||||||
names = append(names, args.Get(i))
|
names = append(names, args.Get(i))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,24 +128,25 @@ func runRecreateTable(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
recreateTables := migrate_base.RecreateTables(beans...)
|
recreateTables := migrate_base.RecreateTables(beans...)
|
||||||
|
|
||||||
return db.InitEngineWithMigration(stdCtx, func(x *xorm.Engine) error {
|
return db.InitEngineWithMigration(context.Background(), func(ctx context.Context, x *xorm.Engine) error {
|
||||||
if err := migrations.EnsureUpToDate(x); err != nil {
|
if err := migrations.EnsureUpToDate(ctx, x); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return recreateTables(x)
|
return recreateTables(x)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) {
|
func setupDoctorDefaultLogger(cmd *cli.Command, colorize bool) {
|
||||||
// Silence the default loggers
|
// Silence the default loggers
|
||||||
setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr)
|
setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr)
|
||||||
|
|
||||||
logFile := ctx.String("log-file")
|
logFile := cmd.String("log-file")
|
||||||
if logFile == "" {
|
switch logFile {
|
||||||
|
case "":
|
||||||
return // if no doctor log-file is set, do not show any log from default logger
|
return // if no doctor log-file is set, do not show any log from default logger
|
||||||
} else if logFile == "-" {
|
case "-":
|
||||||
setupConsoleLogger(log.TRACE, colorize, os.Stdout)
|
setupConsoleLogger(log.TRACE, colorize, os.Stdout)
|
||||||
} else {
|
default:
|
||||||
logFile, _ = filepath.Abs(logFile)
|
logFile, _ = filepath.Abs(logFile)
|
||||||
writeMode := log.WriterMode{Level: log.TRACE, WriterOption: log.WriterFileOption{FileName: logFile}}
|
writeMode := log.WriterMode{Level: log.TRACE, WriterOption: log.WriterFileOption{FileName: logFile}}
|
||||||
writer, err := log.NewEventWriter("console-to-file", "file", writeMode)
|
writer, err := log.NewEventWriter("console-to-file", "file", writeMode)
|
||||||
@@ -159,23 +158,20 @@ func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDoctorCheck(ctx *cli.Context) error {
|
func runDoctorCheck(ctx context.Context, cmd *cli.Command) error {
|
||||||
stdCtx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
colorize := log.CanColorStdout
|
colorize := log.CanColorStdout
|
||||||
if ctx.IsSet("color") {
|
if cmd.IsSet("color") {
|
||||||
colorize = ctx.Bool("color")
|
colorize = cmd.Bool("color")
|
||||||
}
|
}
|
||||||
|
|
||||||
setupDoctorDefaultLogger(ctx, colorize)
|
setupDoctorDefaultLogger(cmd, colorize)
|
||||||
|
|
||||||
// Finally redirect the default golang's log to here
|
// Finally redirect the default golang's log to here
|
||||||
golog.SetFlags(0)
|
golog.SetFlags(0)
|
||||||
golog.SetPrefix("")
|
golog.SetPrefix("")
|
||||||
golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info))
|
golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info))
|
||||||
|
|
||||||
if ctx.IsSet("list") {
|
if cmd.IsSet("list") {
|
||||||
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
|
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
|
||||||
_, _ = w.Write([]byte("Default\tName\tTitle\n"))
|
_, _ = w.Write([]byte("Default\tName\tTitle\n"))
|
||||||
doctor.SortChecks(doctor.Checks)
|
doctor.SortChecks(doctor.Checks)
|
||||||
@@ -193,12 +189,12 @@ func runDoctorCheck(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var checks []*doctor.Check
|
var checks []*doctor.Check
|
||||||
if ctx.Bool("all") {
|
if cmd.Bool("all") {
|
||||||
checks = make([]*doctor.Check, len(doctor.Checks))
|
checks = make([]*doctor.Check, len(doctor.Checks))
|
||||||
copy(checks, doctor.Checks)
|
copy(checks, doctor.Checks)
|
||||||
} else if ctx.IsSet("run") {
|
} else if cmd.IsSet("run") {
|
||||||
addDefault := ctx.Bool("default")
|
addDefault := cmd.Bool("default")
|
||||||
runNamesSet := container.SetOf(ctx.StringSlice("run")...)
|
runNamesSet := container.SetOf(cmd.StringSlice("run")...)
|
||||||
for _, check := range doctor.Checks {
|
for _, check := range doctor.Checks {
|
||||||
if (addDefault && check.IsDefault) || runNamesSet.Contains(check.Name) {
|
if (addDefault && check.IsDefault) || runNamesSet.Contains(check.Name) {
|
||||||
checks = append(checks, check)
|
checks = append(checks, check)
|
||||||
@@ -215,5 +211,5 @@ func runDoctorCheck(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return doctor.RunChecks(stdCtx, colorize, ctx.Bool("fix"), checks)
|
return doctor.RunChecks(ctx, colorize, cmd.Bool("fix"), checks)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,14 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// cmdDoctorConvert represents the available convert sub-command.
|
// cmdDoctorConvert represents the available convert sub-command.
|
||||||
@@ -21,11 +22,8 @@ var cmdDoctorConvert = &cli.Command{
|
|||||||
Action: runDoctorConvert,
|
Action: runDoctorConvert,
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDoctorConvert(ctx *cli.Context) error {
|
func runDoctorConvert(ctx context.Context, cmd *cli.Command) error {
|
||||||
stdCtx, cancel := installSignals()
|
if err := initDB(ctx); err != nil {
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := initDB(stdCtx); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"code.gitea.io/gitea/services/doctor"
|
"code.gitea.io/gitea/services/doctor"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDoctorRun(t *testing.T) {
|
func TestDoctorRun(t *testing.T) {
|
||||||
@@ -22,12 +22,13 @@ func TestDoctorRun(t *testing.T) {
|
|||||||
|
|
||||||
SkipDatabaseInitialization: true,
|
SkipDatabaseInitialization: true,
|
||||||
})
|
})
|
||||||
app := cli.NewApp()
|
app := &cli.Command{
|
||||||
app.Commands = []*cli.Command{cmdDoctorCheck}
|
Commands: []*cli.Command{cmdDoctorCheck},
|
||||||
err := app.Run([]string{"./gitea", "check", "--run", "test-check"})
|
}
|
||||||
|
err := app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check"})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
err = app.Run([]string{"./gitea", "check", "--run", "no-such"})
|
err = app.Run(t.Context(), []string{"./gitea", "check", "--run", "no-such"})
|
||||||
assert.ErrorContains(t, err, `unknown checks: "no-such"`)
|
assert.ErrorContains(t, err, `unknown checks: "no-such"`)
|
||||||
err = app.Run([]string{"./gitea", "check", "--run", "test-check,no-such"})
|
err = app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check,no-such"})
|
||||||
assert.ErrorContains(t, err, `unknown checks: "no-such"`)
|
assert.ErrorContains(t, err, `unknown checks: "no-such"`)
|
||||||
}
|
}
|
||||||
|
|||||||
77
cmd/dump.go
77
cmd/dump.go
@@ -5,7 +5,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -20,8 +20,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"gitea.com/go-chi/session"
|
"gitea.com/go-chi/session"
|
||||||
"github.com/mholt/archiver/v3"
|
"github.com/urfave/cli/v3"
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdDump represents the available dump sub-command.
|
// CmdDump represents the available dump sub-command.
|
||||||
@@ -93,7 +92,7 @@ var CmdDump = &cli.Command{
|
|||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "type",
|
Name: "type",
|
||||||
Usage: fmt.Sprintf(`Dump output format, default to "zip", supported types: %s`, strings.Join(dump.SupportedOutputTypes, ", ")),
|
Usage: `Dump output format, default to "zip", supported types: ` + strings.Join(dump.SupportedOutputTypes, ", "),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -102,17 +101,17 @@ func fatal(format string, args ...any) {
|
|||||||
log.Fatal(format, args...)
|
log.Fatal(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDump(ctx *cli.Context) error {
|
func runDump(ctx context.Context, cmd *cli.Command) error {
|
||||||
setting.MustInstalled()
|
setting.MustInstalled()
|
||||||
|
|
||||||
quite := ctx.Bool("quiet")
|
quite := cmd.Bool("quiet")
|
||||||
verbose := ctx.Bool("verbose")
|
verbose := cmd.Bool("verbose")
|
||||||
if verbose && quite {
|
if verbose && quite {
|
||||||
fatal("Option --quiet and --verbose cannot both be set")
|
fatal("Option --quiet and --verbose cannot both be set")
|
||||||
}
|
}
|
||||||
|
|
||||||
// outFileName is either "-" or a file name (will be made absolute)
|
// outFileName is either "-" or a file name (will be made absolute)
|
||||||
outFileName, outType := dump.PrepareFileNameAndType(ctx.String("file"), ctx.String("type"))
|
outFileName, outType := dump.PrepareFileNameAndType(cmd.String("file"), cmd.String("type"))
|
||||||
if outType == "" {
|
if outType == "" {
|
||||||
fatal("Invalid output type")
|
fatal("Invalid output type")
|
||||||
}
|
}
|
||||||
@@ -137,10 +136,7 @@ func runDump(ctx *cli.Context) error {
|
|||||||
setting.DisableLoggerInit()
|
setting.DisableLoggerInit()
|
||||||
setting.LoadSettings() // cannot access session settings otherwise
|
setting.LoadSettings() // cannot access session settings otherwise
|
||||||
|
|
||||||
stdCtx, cancel := installSignals()
|
err := db.InitEngine(ctx)
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
err := db.InitEngine(stdCtx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -149,24 +145,20 @@ func runDump(ctx *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
archiverGeneric, err := archiver.ByExtension("." + outType)
|
dumper, err := dump.NewDumper(ctx, outType, outFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatal("Unable to get archiver for extension: %v", err)
|
fatal("Failed to create archive %q: %v", outFile, err)
|
||||||
}
|
return err
|
||||||
|
|
||||||
archiverWriter := archiverGeneric.(archiver.Writer)
|
|
||||||
if err := archiverWriter.Create(outFile); err != nil {
|
|
||||||
fatal("Creating archiver.Writer failed: %v", err)
|
|
||||||
}
|
|
||||||
defer archiverWriter.Close()
|
|
||||||
|
|
||||||
dumper := &dump.Dumper{
|
|
||||||
Writer: archiverWriter,
|
|
||||||
Verbose: verbose,
|
|
||||||
}
|
}
|
||||||
|
dumper.Verbose = verbose
|
||||||
dumper.GlobalExcludeAbsPath(outFileName)
|
dumper.GlobalExcludeAbsPath(outFileName)
|
||||||
|
defer func() {
|
||||||
|
if err := dumper.Close(); err != nil {
|
||||||
|
fatal("Failed to save archive %q: %v", outFileName, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
if ctx.IsSet("skip-repository") && ctx.Bool("skip-repository") {
|
if cmd.IsSet("skip-repository") && cmd.Bool("skip-repository") {
|
||||||
log.Info("Skip dumping local repositories")
|
log.Info("Skip dumping local repositories")
|
||||||
} else {
|
} else {
|
||||||
log.Info("Dumping local repositories... %s", setting.RepoRootPath)
|
log.Info("Dumping local repositories... %s", setting.RepoRootPath)
|
||||||
@@ -174,7 +166,7 @@ func runDump(ctx *cli.Context) error {
|
|||||||
fatal("Failed to include repositories: %v", err)
|
fatal("Failed to include repositories: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.IsSet("skip-lfs-data") && ctx.Bool("skip-lfs-data") {
|
if cmd.IsSet("skip-lfs-data") && cmd.Bool("skip-lfs-data") {
|
||||||
log.Info("Skip dumping LFS data")
|
log.Info("Skip dumping LFS data")
|
||||||
} else if !setting.LFS.StartServer {
|
} else if !setting.LFS.StartServer {
|
||||||
log.Info("LFS isn't enabled. Skip dumping LFS data")
|
log.Info("LFS isn't enabled. Skip dumping LFS data")
|
||||||
@@ -183,18 +175,18 @@ func runDump(ctx *cli.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return dumper.AddReader(object, info, path.Join("data", "lfs", objPath))
|
return dumper.AddFileByReader(object, info, path.Join("data", "lfs", objPath))
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
fatal("Failed to dump LFS objects: %v", err)
|
fatal("Failed to dump LFS objects: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.Bool("skip-db") {
|
if cmd.Bool("skip-db") {
|
||||||
// Ensure that we don't dump the database file that may reside in setting.AppDataPath or elsewhere.
|
// Ensure that we don't dump the database file that may reside in setting.AppDataPath or elsewhere.
|
||||||
dumper.GlobalExcludeAbsPath(setting.Database.Path)
|
dumper.GlobalExcludeAbsPath(setting.Database.Path)
|
||||||
log.Info("Skipping database")
|
log.Info("Skipping database")
|
||||||
} else {
|
} else {
|
||||||
tmpDir := ctx.String("tempdir")
|
tmpDir := cmd.String("tempdir")
|
||||||
if _, err := os.Stat(tmpDir); os.IsNotExist(err) {
|
if _, err := os.Stat(tmpDir); os.IsNotExist(err) {
|
||||||
fatal("Path does not exist: %s", tmpDir)
|
fatal("Path does not exist: %s", tmpDir)
|
||||||
}
|
}
|
||||||
@@ -210,7 +202,7 @@ func runDump(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
targetDBType := ctx.String("database")
|
targetDBType := cmd.String("database")
|
||||||
if len(targetDBType) > 0 && targetDBType != setting.Database.Type.String() {
|
if len(targetDBType) > 0 && targetDBType != setting.Database.Type.String() {
|
||||||
log.Info("Dumping database %s => %s...", setting.Database.Type, targetDBType)
|
log.Info("Dumping database %s => %s...", setting.Database.Type, targetDBType)
|
||||||
} else {
|
} else {
|
||||||
@@ -221,17 +213,17 @@ func runDump(ctx *cli.Context) error {
|
|||||||
fatal("Failed to dump database: %v", err)
|
fatal("Failed to dump database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = dumper.AddFile("gitea-db.sql", dbDump.Name()); err != nil {
|
if err = dumper.AddFileByPath("gitea-db.sql", dbDump.Name()); err != nil {
|
||||||
fatal("Failed to include gitea-db.sql: %v", err)
|
fatal("Failed to include gitea-db.sql: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Adding custom configuration file from %s", setting.CustomConf)
|
log.Info("Adding custom configuration file from %s", setting.CustomConf)
|
||||||
if err = dumper.AddFile("app.ini", setting.CustomConf); err != nil {
|
if err = dumper.AddFileByPath("app.ini", setting.CustomConf); err != nil {
|
||||||
fatal("Failed to include specified app.ini: %v", err)
|
fatal("Failed to include specified app.ini: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.IsSet("skip-custom-dir") && ctx.Bool("skip-custom-dir") {
|
if cmd.IsSet("skip-custom-dir") && cmd.Bool("skip-custom-dir") {
|
||||||
log.Info("Skipping custom directory")
|
log.Info("Skipping custom directory")
|
||||||
} else {
|
} else {
|
||||||
customDir, err := os.Stat(setting.CustomPath)
|
customDir, err := os.Stat(setting.CustomPath)
|
||||||
@@ -264,7 +256,7 @@ func runDump(ctx *cli.Context) error {
|
|||||||
excludes = append(excludes, opts.ProviderConfig)
|
excludes = append(excludes, opts.ProviderConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.IsSet("skip-index") && ctx.Bool("skip-index") {
|
if cmd.IsSet("skip-index") && cmd.Bool("skip-index") {
|
||||||
excludes = append(excludes, setting.Indexer.RepoPath)
|
excludes = append(excludes, setting.Indexer.RepoPath)
|
||||||
excludes = append(excludes, setting.Indexer.IssuePath)
|
excludes = append(excludes, setting.Indexer.IssuePath)
|
||||||
}
|
}
|
||||||
@@ -273,25 +265,26 @@ func runDump(ctx *cli.Context) error {
|
|||||||
excludes = append(excludes, setting.LFS.Storage.Path)
|
excludes = append(excludes, setting.LFS.Storage.Path)
|
||||||
excludes = append(excludes, setting.Attachment.Storage.Path)
|
excludes = append(excludes, setting.Attachment.Storage.Path)
|
||||||
excludes = append(excludes, setting.Packages.Storage.Path)
|
excludes = append(excludes, setting.Packages.Storage.Path)
|
||||||
|
excludes = append(excludes, setting.RepoArchive.Storage.Path)
|
||||||
excludes = append(excludes, setting.Log.RootPath)
|
excludes = append(excludes, setting.Log.RootPath)
|
||||||
if err := dumper.AddRecursiveExclude("data", setting.AppDataPath, excludes); err != nil {
|
if err := dumper.AddRecursiveExclude("data", setting.AppDataPath, excludes); err != nil {
|
||||||
fatal("Failed to include data directory: %v", err)
|
fatal("Failed to include data directory: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") {
|
if cmd.IsSet("skip-attachment-data") && cmd.Bool("skip-attachment-data") {
|
||||||
log.Info("Skip dumping attachment data")
|
log.Info("Skip dumping attachment data")
|
||||||
} else if err := storage.Attachments.IterateObjects("", func(objPath string, object storage.Object) error {
|
} else if err := storage.Attachments.IterateObjects("", func(objPath string, object storage.Object) error {
|
||||||
info, err := object.Stat()
|
info, err := object.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return dumper.AddReader(object, info, path.Join("data", "attachments", objPath))
|
return dumper.AddFileByReader(object, info, path.Join("data", "attachments", objPath))
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
fatal("Failed to dump attachments: %v", err)
|
fatal("Failed to dump attachments: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.IsSet("skip-package-data") && ctx.Bool("skip-package-data") {
|
if cmd.IsSet("skip-package-data") && cmd.Bool("skip-package-data") {
|
||||||
log.Info("Skip dumping package data")
|
log.Info("Skip dumping package data")
|
||||||
} else if !setting.Packages.Enabled {
|
} else if !setting.Packages.Enabled {
|
||||||
log.Info("Packages isn't enabled. Skip dumping package data")
|
log.Info("Packages isn't enabled. Skip dumping package data")
|
||||||
@@ -300,7 +293,7 @@ func runDump(ctx *cli.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return dumper.AddReader(object, info, path.Join("data", "packages", objPath))
|
return dumper.AddFileByReader(object, info, path.Join("data", "packages", objPath))
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
fatal("Failed to dump packages: %v", err)
|
fatal("Failed to dump packages: %v", err)
|
||||||
}
|
}
|
||||||
@@ -308,7 +301,7 @@ func runDump(ctx *cli.Context) error {
|
|||||||
// Doesn't check if LogRootPath exists before processing --skip-log intentionally,
|
// Doesn't check if LogRootPath exists before processing --skip-log intentionally,
|
||||||
// ensuring that it's clear the dump is skipped whether the directory's initialized
|
// ensuring that it's clear the dump is skipped whether the directory's initialized
|
||||||
// yet or not.
|
// yet or not.
|
||||||
if ctx.IsSet("skip-log") && ctx.Bool("skip-log") {
|
if cmd.IsSet("skip-log") && cmd.Bool("skip-log") {
|
||||||
log.Info("Skip dumping log files")
|
log.Info("Skip dumping log files")
|
||||||
} else {
|
} else {
|
||||||
isExist, err := util.IsExist(setting.Log.RootPath)
|
isExist, err := util.IsExist(setting.Log.RootPath)
|
||||||
@@ -325,10 +318,6 @@ func runDump(ctx *cli.Context) error {
|
|||||||
if outFileName == "-" {
|
if outFileName == "-" {
|
||||||
log.Info("Finish dumping to stdout")
|
log.Info("Finish dumping to stdout")
|
||||||
} else {
|
} else {
|
||||||
if err = archiverWriter.Close(); err != nil {
|
|
||||||
_ = os.Remove(outFileName)
|
|
||||||
fatal("Failed to save %q: %v", outFileName, err)
|
|
||||||
}
|
|
||||||
if err = os.Chmod(outFileName, 0o600); err != nil {
|
if err = os.Chmod(outFileName, 0o600); err != nil {
|
||||||
log.Info("Can't change file access permissions mask to 0600: %v", err)
|
log.Info("Can't change file access permissions mask to 0600: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
"code.gitea.io/gitea/services/convert"
|
"code.gitea.io/gitea/services/convert"
|
||||||
"code.gitea.io/gitea/services/migrations"
|
"code.gitea.io/gitea/services/migrations"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdDumpRepository represents the available dump repository sub-command.
|
// CmdDumpRepository represents the available dump repository sub-command.
|
||||||
@@ -79,16 +79,18 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDumpRepository(ctx *cli.Context) error {
|
func runDumpRepository(ctx context.Context, cmd *cli.Command) error {
|
||||||
stdCtx, cancel := installSignals()
|
setupConsoleLogger(log.INFO, log.CanColorStderr, os.Stderr)
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := initDB(stdCtx); err != nil {
|
setting.DisableLoggerInit()
|
||||||
|
setting.LoadSettings() // cannot access skip_tls_verify settings otherwise
|
||||||
|
|
||||||
|
if err := initDB(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// migrations.GiteaLocalUploader depends on git module
|
// migrations.GiteaLocalUploader depends on git module
|
||||||
if err := git.InitSimple(context.Background()); err != nil {
|
if err := git.InitSimple(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,8 +102,8 @@ func runDumpRepository(ctx *cli.Context) error {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
serviceType structs.GitServiceType
|
serviceType structs.GitServiceType
|
||||||
cloneAddr = ctx.String("clone_addr")
|
cloneAddr = cmd.String("clone_addr")
|
||||||
serviceStr = ctx.String("git_service")
|
serviceStr = cmd.String("git_service")
|
||||||
)
|
)
|
||||||
|
|
||||||
if strings.HasPrefix(strings.ToLower(cloneAddr), "https://github.com/") {
|
if strings.HasPrefix(strings.ToLower(cloneAddr), "https://github.com/") {
|
||||||
@@ -119,13 +121,13 @@ func runDumpRepository(ctx *cli.Context) error {
|
|||||||
opts := base.MigrateOptions{
|
opts := base.MigrateOptions{
|
||||||
GitServiceType: serviceType,
|
GitServiceType: serviceType,
|
||||||
CloneAddr: cloneAddr,
|
CloneAddr: cloneAddr,
|
||||||
AuthUsername: ctx.String("auth_username"),
|
AuthUsername: cmd.String("auth_username"),
|
||||||
AuthPassword: ctx.String("auth_password"),
|
AuthPassword: cmd.String("auth_password"),
|
||||||
AuthToken: ctx.String("auth_token"),
|
AuthToken: cmd.String("auth_token"),
|
||||||
RepoName: ctx.String("repo_name"),
|
RepoName: cmd.String("repo_name"),
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ctx.String("units")) == 0 {
|
if len(cmd.String("units")) == 0 {
|
||||||
opts.Wiki = true
|
opts.Wiki = true
|
||||||
opts.Issues = true
|
opts.Issues = true
|
||||||
opts.Milestones = true
|
opts.Milestones = true
|
||||||
@@ -135,8 +137,8 @@ func runDumpRepository(ctx *cli.Context) error {
|
|||||||
opts.PullRequests = true
|
opts.PullRequests = true
|
||||||
opts.ReleaseAssets = true
|
opts.ReleaseAssets = true
|
||||||
} else {
|
} else {
|
||||||
units := strings.Split(ctx.String("units"), ",")
|
units := strings.SplitSeq(cmd.String("units"), ",")
|
||||||
for _, unit := range units {
|
for unit := range units {
|
||||||
switch strings.ToLower(strings.TrimSpace(unit)) {
|
switch strings.ToLower(strings.TrimSpace(unit)) {
|
||||||
case "":
|
case "":
|
||||||
continue
|
continue
|
||||||
@@ -164,7 +166,7 @@ func runDumpRepository(ctx *cli.Context) error {
|
|||||||
|
|
||||||
// the repo_dir will be removed if error occurs in DumpRepository
|
// the repo_dir will be removed if error occurs in DumpRepository
|
||||||
// make sure the directory doesn't exist or is empty, prevent from deleting user files
|
// make sure the directory doesn't exist or is empty, prevent from deleting user files
|
||||||
repoDir := ctx.String("repo_dir")
|
repoDir := cmd.String("repo_dir")
|
||||||
if exists, err := util.IsExist(repoDir); err != nil {
|
if exists, err := util.IsExist(repoDir); err != nil {
|
||||||
return fmt.Errorf("unable to stat repo_dir %q: %w", repoDir, err)
|
return fmt.Errorf("unable to stat repo_dir %q: %w", repoDir, err)
|
||||||
} else if exists {
|
} else if exists {
|
||||||
@@ -177,9 +179,9 @@ func runDumpRepository(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := migrations.DumpRepository(
|
if err := migrations.DumpRepository(
|
||||||
context.Background(),
|
ctx,
|
||||||
repoDir,
|
repoDir,
|
||||||
ctx.String("owner_name"),
|
cmd.String("owner_name"),
|
||||||
opts,
|
opts,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
log.Fatal("Failed to dump repository: %v", err)
|
log.Fatal("Failed to dump repository: %v", err)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@@ -11,6 +12,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/assetfs"
|
"code.gitea.io/gitea/modules/assetfs"
|
||||||
|
"code.gitea.io/gitea/modules/glob"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/options"
|
"code.gitea.io/gitea/modules/options"
|
||||||
"code.gitea.io/gitea/modules/public"
|
"code.gitea.io/gitea/modules/public"
|
||||||
@@ -18,8 +20,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/templates"
|
"code.gitea.io/gitea/modules/templates"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/gobwas/glob"
|
"github.com/urfave/cli/v3"
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdEmbedded represents the available extract sub-command.
|
// CmdEmbedded represents the available extract sub-command.
|
||||||
@@ -28,7 +29,7 @@ var (
|
|||||||
Name: "embedded",
|
Name: "embedded",
|
||||||
Usage: "Extract embedded resources",
|
Usage: "Extract embedded resources",
|
||||||
Description: "A command for extracting embedded resources, like templates and images",
|
Description: "A command for extracting embedded resources, like templates and images",
|
||||||
Subcommands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
subcmdList,
|
subcmdList,
|
||||||
subcmdView,
|
subcmdView,
|
||||||
subcmdExtract,
|
subcmdExtract,
|
||||||
@@ -100,7 +101,7 @@ type assetFile struct {
|
|||||||
path string
|
path string
|
||||||
}
|
}
|
||||||
|
|
||||||
func initEmbeddedExtractor(c *cli.Context) error {
|
func initEmbeddedExtractor(c *cli.Command) error {
|
||||||
setupConsoleLogger(log.ERROR, log.CanColorStderr, os.Stderr)
|
setupConsoleLogger(log.ERROR, log.CanColorStderr, os.Stderr)
|
||||||
|
|
||||||
patterns, err := compileCollectPatterns(c.Args().Slice())
|
patterns, err := compileCollectPatterns(c.Args().Slice())
|
||||||
@@ -115,31 +116,31 @@ func initEmbeddedExtractor(c *cli.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runList(c *cli.Context) error {
|
func runList(_ context.Context, c *cli.Command) error {
|
||||||
if err := runListDo(c); err != nil {
|
if err := runListDo(c); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
_, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runView(c *cli.Context) error {
|
func runView(_ context.Context, c *cli.Command) error {
|
||||||
if err := runViewDo(c); err != nil {
|
if err := runViewDo(c); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
_, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runExtract(c *cli.Context) error {
|
func runExtract(_ context.Context, c *cli.Command) error {
|
||||||
if err := runExtractDo(c); err != nil {
|
if err := runExtractDo(c); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
_, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runListDo(c *cli.Context) error {
|
func runListDo(c *cli.Command) error {
|
||||||
if err := initEmbeddedExtractor(c); err != nil {
|
if err := initEmbeddedExtractor(c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -151,7 +152,7 @@ func runListDo(c *cli.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runViewDo(c *cli.Context) error {
|
func runViewDo(c *cli.Command) error {
|
||||||
if err := initEmbeddedExtractor(c); err != nil {
|
if err := initEmbeddedExtractor(c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -174,7 +175,7 @@ func runViewDo(c *cli.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runExtractDo(c *cli.Context) error {
|
func runExtractDo(c *cli.Command) error {
|
||||||
if err := initEmbeddedExtractor(c); err != nil {
|
if err := initEmbeddedExtractor(c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -216,7 +217,7 @@ func runExtractDo(c *cli.Context) error {
|
|||||||
for _, a := range matchedAssetFiles {
|
for _, a := range matchedAssetFiles {
|
||||||
if err := extractAsset(destdir, a, overwrite, rename); err != nil {
|
if err := extractAsset(destdir, a, overwrite, rename); err != nil {
|
||||||
// Non-fatal error
|
// Non-fatal error
|
||||||
fmt.Fprintf(os.Stderr, "%s: %v", a.path, err)
|
_, _ = fmt.Fprintf(os.Stderr, "%s: %v\n", a.path, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,7 +272,7 @@ func extractAsset(d string, a assetFile, overwrite, rename bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectAssetFilesByPattern(c *cli.Context, globs []glob.Glob, path string, layer *assetfs.Layer) {
|
func collectAssetFilesByPattern(c *cli.Command, globs []glob.Glob, path string, layer *assetfs.Layer) {
|
||||||
fs := assetfs.Layered(layer)
|
fs := assetfs.Layered(layer)
|
||||||
files, err := fs.ListAllFiles(".", true)
|
files, err := fs.ListAllFiles(".", true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -294,16 +295,14 @@ func collectAssetFilesByPattern(c *cli.Context, globs []glob.Glob, path string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func compileCollectPatterns(args []string) ([]glob.Glob, error) {
|
func compileCollectPatterns(args []string) (_ []glob.Glob, err error) {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
args = []string{"**"}
|
args = []string{"**"}
|
||||||
}
|
}
|
||||||
pat := make([]glob.Glob, len(args))
|
pat := make([]glob.Glob, len(args))
|
||||||
for i := range args {
|
for i := range args {
|
||||||
if g, err := glob.Compile(args[i], '/'); err != nil {
|
if pat[i], err = glob.Compile(args[i], '/'); err != nil {
|
||||||
return nil, fmt.Errorf("'%s': Invalid glob pattern: %w", args[i], err)
|
return nil, fmt.Errorf("invalid glob patterh %q: %w", args[i], err)
|
||||||
} else { //nolint:revive
|
|
||||||
pat[i] = g
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pat, nil
|
return pat, nil
|
||||||
|
|||||||
@@ -5,13 +5,14 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/generate"
|
"code.gitea.io/gitea/modules/generate"
|
||||||
|
|
||||||
"github.com/mattn/go-isatty"
|
"github.com/mattn/go-isatty"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -19,7 +20,7 @@ var (
|
|||||||
CmdGenerate = &cli.Command{
|
CmdGenerate = &cli.Command{
|
||||||
Name: "generate",
|
Name: "generate",
|
||||||
Usage: "Generate Gitea's secrets/keys/tokens",
|
Usage: "Generate Gitea's secrets/keys/tokens",
|
||||||
Subcommands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
subcmdSecret,
|
subcmdSecret,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -27,7 +28,7 @@ var (
|
|||||||
subcmdSecret = &cli.Command{
|
subcmdSecret = &cli.Command{
|
||||||
Name: "secret",
|
Name: "secret",
|
||||||
Usage: "Generate a secret token",
|
Usage: "Generate a secret token",
|
||||||
Subcommands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
microcmdGenerateInternalToken,
|
microcmdGenerateInternalToken,
|
||||||
microcmdGenerateLfsJwtSecret,
|
microcmdGenerateLfsJwtSecret,
|
||||||
microcmdGenerateSecretKey,
|
microcmdGenerateSecretKey,
|
||||||
@@ -54,7 +55,7 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func runGenerateInternalToken(c *cli.Context) error {
|
func runGenerateInternalToken(_ context.Context, c *cli.Command) error {
|
||||||
internalToken, err := generate.NewInternalToken()
|
internalToken, err := generate.NewInternalToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -69,7 +70,7 @@ func runGenerateInternalToken(c *cli.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runGenerateLfsJwtSecret(c *cli.Context) error {
|
func runGenerateLfsJwtSecret(_ context.Context, c *cli.Command) error {
|
||||||
_, jwtSecretBase64, err := generate.NewJwtSecretWithBase64()
|
_, jwtSecretBase64, err := generate.NewJwtSecretWithBase64()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -84,7 +85,7 @@ func runGenerateLfsJwtSecret(c *cli.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runGenerateSecretKey(c *cli.Context) error {
|
func runGenerateSecretKey(_ context.Context, c *cli.Command) error {
|
||||||
secretKey, err := generate.NewSecretKey()
|
secretKey, err := generate.NewSecretKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
32
cmd/hook.go
32
cmd/hook.go
@@ -15,16 +15,17 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/private"
|
"code.gitea.io/gitea/modules/private"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
repo_module "code.gitea.io/gitea/modules/repository"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
hookBatchSize = 30
|
hookBatchSize = 500
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -32,9 +33,10 @@ var (
|
|||||||
CmdHook = &cli.Command{
|
CmdHook = &cli.Command{
|
||||||
Name: "hook",
|
Name: "hook",
|
||||||
Usage: "(internal) Should only be called by Git",
|
Usage: "(internal) Should only be called by Git",
|
||||||
|
Hidden: true, // internal commands shouldn't be visible
|
||||||
Description: "Delegate commands to corresponding Git hooks",
|
Description: "Delegate commands to corresponding Git hooks",
|
||||||
Before: PrepareConsoleLoggerLevel(log.FATAL),
|
Before: PrepareConsoleLoggerLevel(log.FATAL),
|
||||||
Subcommands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
subcmdHookPreReceive,
|
subcmdHookPreReceive,
|
||||||
subcmdHookUpdate,
|
subcmdHookUpdate,
|
||||||
subcmdHookPostReceive,
|
subcmdHookPostReceive,
|
||||||
@@ -161,12 +163,10 @@ func (n *nilWriter) WriteString(s string) (int, error) {
|
|||||||
return len(s), nil
|
return len(s), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runHookPreReceive(c *cli.Context) error {
|
func runHookPreReceive(ctx context.Context, c *cli.Command) error {
|
||||||
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
|
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"))
|
||||||
|
|
||||||
@@ -292,7 +292,7 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
|
|
||||||
// runHookUpdate avoid to do heavy operations on update hook because it will be
|
// runHookUpdate avoid to do heavy operations on update hook because it will be
|
||||||
// invoked for every ref update which does not like pre-receive and post-receive
|
// invoked for every ref update which does not like pre-receive and post-receive
|
||||||
func runHookUpdate(c *cli.Context) error {
|
func runHookUpdate(_ context.Context, c *cli.Command) error {
|
||||||
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
|
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -309,15 +309,12 @@ func runHookUpdate(c *cli.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runHookPostReceive(c *cli.Context) error {
|
func runHookPostReceive(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"))
|
||||||
|
|
||||||
// First of all run update-server-info no matter what
|
// First of all run update-server-info no matter what
|
||||||
if _, _, err := git.NewCommand(ctx, "update-server-info").RunStdString(nil); err != nil {
|
if _, _, err := gitcmd.NewCommand("update-server-info").RunStdString(ctx, nil); err != nil {
|
||||||
return fmt.Errorf("Failed to call 'git update-server-info': %w", err)
|
return fmt.Errorf("failed to call 'git update-server-info': %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now if we're an internal don't do anything else
|
// Now if we're an internal don't do anything else
|
||||||
@@ -485,7 +482,7 @@ func hookPrintResult(output, isCreate bool, branch, url string) {
|
|||||||
func pushOptions() map[string]string {
|
func pushOptions() map[string]string {
|
||||||
opts := make(map[string]string)
|
opts := make(map[string]string)
|
||||||
if pushCount, err := strconv.Atoi(os.Getenv(private.GitPushOptionCount)); err == nil {
|
if pushCount, err := strconv.Atoi(os.Getenv(private.GitPushOptionCount)); err == nil {
|
||||||
for idx := 0; idx < pushCount; idx++ {
|
for idx := range pushCount {
|
||||||
opt := os.Getenv(fmt.Sprintf("GIT_PUSH_OPTION_%d", idx))
|
opt := os.Getenv(fmt.Sprintf("GIT_PUSH_OPTION_%d", idx))
|
||||||
kv := strings.SplitN(opt, "=", 2)
|
kv := strings.SplitN(opt, "=", 2)
|
||||||
if len(kv) == 2 {
|
if len(kv) == 2 {
|
||||||
@@ -496,10 +493,7 @@ func pushOptions() map[string]string {
|
|||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
|
|
||||||
func runHookProcReceive(c *cli.Context) error {
|
func runHookProcReceive(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"))
|
||||||
|
|
||||||
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
||||||
@@ -740,7 +734,7 @@ func readPktLine(ctx context.Context, in *bufio.Reader, requestType pktLineType)
|
|||||||
|
|
||||||
// read prefix
|
// read prefix
|
||||||
lengthBytes := make([]byte, 4)
|
lengthBytes := make([]byte, 4)
|
||||||
for i := 0; i < 4; i++ {
|
for i := range 4 {
|
||||||
lengthBytes[i], err = in.ReadByte()
|
lengthBytes[i], err = in.ReadByte()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fail(ctx, "Protocol: stdin error", "Pkt-Line: read stdin failed : %v", err)
|
return nil, fail(ctx, "Protocol: stdin error", "Pkt-Line: read stdin failed : %v", err)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -15,7 +14,7 @@ import (
|
|||||||
|
|
||||||
func TestPktLine(t *testing.T) {
|
func TestPktLine(t *testing.T) {
|
||||||
// test read
|
// test read
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
s := strings.NewReader("0000")
|
s := strings.NewReader("0000")
|
||||||
r := bufio.NewReader(s)
|
r := bufio.NewReader(s)
|
||||||
result, err := readPktLine(ctx, r, pktLineTypeFlush)
|
result, err := readPktLine(ctx, r, pktLineTypeFlush)
|
||||||
|
|||||||
11
cmd/keys.go
11
cmd/keys.go
@@ -4,6 +4,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -11,13 +12,14 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/private"
|
"code.gitea.io/gitea/modules/private"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdKeys represents the available keys sub-command
|
// CmdKeys represents the available keys sub-command
|
||||||
var CmdKeys = &cli.Command{
|
var CmdKeys = &cli.Command{
|
||||||
Name: "keys",
|
Name: "keys",
|
||||||
Usage: "(internal) Should only be called by SSH server",
|
Usage: "(internal) Should only be called by SSH server",
|
||||||
|
Hidden: true, // internal commands shouldn't be visible
|
||||||
Description: "Queries the Gitea database to get the authorized command for a given ssh key fingerprint",
|
Description: "Queries the Gitea database to get the authorized command for a given ssh key fingerprint",
|
||||||
Before: PrepareConsoleLoggerLevel(log.FATAL),
|
Before: PrepareConsoleLoggerLevel(log.FATAL),
|
||||||
Action: runKeys,
|
Action: runKeys,
|
||||||
@@ -49,7 +51,7 @@ var CmdKeys = &cli.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func runKeys(c *cli.Context) error {
|
func runKeys(ctx context.Context, c *cli.Command) error {
|
||||||
if !c.IsSet("username") {
|
if !c.IsSet("username") {
|
||||||
return errors.New("No username provided")
|
return errors.New("No username provided")
|
||||||
}
|
}
|
||||||
@@ -68,9 +70,6 @@ func runKeys(c *cli.Context) error {
|
|||||||
return errors.New("No key type and content provided")
|
return errors.New("No key type and content provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"))
|
||||||
|
|
||||||
authorizedString, extra := private.AuthorizedPublicKeyByContent(ctx, content)
|
authorizedString, extra := private.AuthorizedPublicKeyByContent(ctx, content)
|
||||||
@@ -78,6 +77,6 @@ func runKeys(c *cli.Context) error {
|
|||||||
if extra.Error != nil {
|
if extra.Error != nil {
|
||||||
return extra.Error
|
return extra.Error
|
||||||
}
|
}
|
||||||
_, _ = fmt.Fprintln(c.App.Writer, strings.TrimSpace(authorizedString.Text))
|
_, _ = fmt.Fprintln(c.Root().Writer, strings.TrimSpace(authorizedString.Text))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,24 +4,18 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/private"
|
"code.gitea.io/gitea/modules/private"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func runSendMail(c *cli.Context) error {
|
func runSendMail(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
setting.MustInstalled()
|
setting.MustInstalled()
|
||||||
|
|
||||||
if err := argsSet(c, "title"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
subject := c.String("title")
|
subject := c.String("title")
|
||||||
confirmSkiped := c.Bool("force")
|
confirmSkiped := c.Bool("force")
|
||||||
body := c.String("content")
|
body := c.String("content")
|
||||||
|
|||||||
186
cmd/main.go
186
cmd/main.go
@@ -4,36 +4,40 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// cmdHelp is our own help subcommand with more information
|
var cliHelpPrinterOld = cli.HelpPrinter
|
||||||
// Keep in mind that the "./gitea help"(subcommand) is different from "./gitea --help"(flag), the flag doesn't parse the config or output "DEFAULT CONFIGURATION:" information
|
|
||||||
func cmdHelp() *cli.Command {
|
func init() {
|
||||||
c := &cli.Command{
|
cli.HelpPrinter = cliHelpPrinterNew
|
||||||
Name: "help",
|
}
|
||||||
Aliases: []string{"h"},
|
|
||||||
Usage: "Shows a list of commands or help for one command",
|
// cliHelpPrinterNew helps to print "DEFAULT CONFIGURATION" for the following cases ( "-c" can apper in any position):
|
||||||
ArgsUsage: "[command]",
|
// * ./gitea -c /dev/null -h
|
||||||
Action: func(c *cli.Context) (err error) {
|
// * ./gitea -c help /dev/null help
|
||||||
lineage := c.Lineage() // The order is from child to parent: help, doctor, Gitea, {Command:nil}
|
// * ./gitea help -c /dev/null
|
||||||
targetCmdIdx := 0
|
// * ./gitea help -c /dev/null web
|
||||||
if c.Command.Name == "help" {
|
// * ./gitea help web -c /dev/null
|
||||||
targetCmdIdx = 1
|
// * ./gitea web help -c /dev/null
|
||||||
}
|
// * ./gitea web -h -c /dev/null
|
||||||
if lineage[targetCmdIdx+1].Command != nil {
|
func cliHelpPrinterNew(out io.Writer, templ string, data any) {
|
||||||
err = cli.ShowCommandHelp(lineage[targetCmdIdx+1], lineage[targetCmdIdx].Command.Name)
|
cmd, _ := data.(*cli.Command)
|
||||||
} else {
|
if cmd != nil {
|
||||||
err = cli.ShowAppHelp(c)
|
prepareWorkPathAndCustomConf(cmd)
|
||||||
}
|
}
|
||||||
_, _ = fmt.Fprintf(c.App.Writer, `
|
cliHelpPrinterOld(out, templ, data)
|
||||||
|
if setting.CustomConf != "" {
|
||||||
|
_, _ = fmt.Fprintf(out, `
|
||||||
DEFAULT CONFIGURATION:
|
DEFAULT CONFIGURATION:
|
||||||
AppPath: %s
|
AppPath: %s
|
||||||
WorkPath: %s
|
WorkPath: %s
|
||||||
@@ -41,94 +45,75 @@ DEFAULT CONFIGURATION:
|
|||||||
ConfigFile: %s
|
ConfigFile: %s
|
||||||
|
|
||||||
`, setting.AppPath, setting.AppWorkPath, setting.CustomPath, setting.CustomConf)
|
`, setting.AppPath, setting.AppWorkPath, setting.CustomPath, setting.CustomConf)
|
||||||
return err
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func appGlobalFlags() []cli.Flag {
|
|
||||||
return []cli.Flag{
|
|
||||||
// make the builtin flags at the top
|
|
||||||
cli.HelpFlag,
|
|
||||||
|
|
||||||
// shared configuration flags, they are for global and for each sub-command at the same time
|
|
||||||
// eg: such command is valid: "./gitea --config /tmp/app.ini web --config /tmp/app.ini", while it's discouraged indeed
|
|
||||||
// keep in mind that the short flags like "-C", "-c" and "-w" are globally polluted, they can't be used for sub-commands anymore.
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "custom-path",
|
|
||||||
Aliases: []string{"C"},
|
|
||||||
Usage: "Set custom path (defaults to '{WorkPath}/custom')",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "config",
|
|
||||||
Aliases: []string{"c"},
|
|
||||||
Value: setting.CustomConf,
|
|
||||||
Usage: "Set custom config file (defaults to '{WorkPath}/custom/conf/app.ini')",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "work-path",
|
|
||||||
Aliases: []string{"w"},
|
|
||||||
Usage: "Set Gitea's working path (defaults to the Gitea's binary directory)",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareSubcommandWithConfig(command *cli.Command, globalFlags []cli.Flag) {
|
func prepareSubcommandWithGlobalFlags(originCmd *cli.Command) {
|
||||||
command.Flags = append(append([]cli.Flag{}, globalFlags...), command.Flags...)
|
originBefore := originCmd.Before
|
||||||
command.Action = prepareWorkPathAndCustomConf(command.Action)
|
originCmd.Before = func(ctxOrig context.Context, cmd *cli.Command) (ctx context.Context, err error) {
|
||||||
command.HideHelp = true
|
ctx = ctxOrig
|
||||||
if command.Name != "help" {
|
if originBefore != nil {
|
||||||
command.Subcommands = append(command.Subcommands, cmdHelp())
|
ctx, err = originBefore(ctx, cmd)
|
||||||
}
|
if err != nil {
|
||||||
for i := range command.Subcommands {
|
return ctx, err
|
||||||
prepareSubcommandWithConfig(command.Subcommands[i], globalFlags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config
|
|
||||||
// It can't use "Before", because each level's sub-command's Before will be called one by one, so the "init" would be done multiple times
|
|
||||||
func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context) error {
|
|
||||||
return func(ctx *cli.Context) error {
|
|
||||||
var args setting.ArgWorkPathAndCustomConf
|
|
||||||
// from children to parent, check the global flags
|
|
||||||
for _, curCtx := range ctx.Lineage() {
|
|
||||||
if curCtx.IsSet("work-path") && args.WorkPath == "" {
|
|
||||||
args.WorkPath = curCtx.String("work-path")
|
|
||||||
}
|
|
||||||
if curCtx.IsSet("custom-path") && args.CustomPath == "" {
|
|
||||||
args.CustomPath = curCtx.String("custom-path")
|
|
||||||
}
|
|
||||||
if curCtx.IsSet("config") && args.CustomConf == "" {
|
|
||||||
args.CustomConf = curCtx.String("config")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
|
prepareWorkPathAndCustomConf(cmd)
|
||||||
if ctx.Bool("help") || action == nil {
|
return ctx, nil
|
||||||
// the default behavior of "urfave/cli": "nil action" means "show help"
|
|
||||||
return cmdHelp().Action(ctx)
|
|
||||||
}
|
|
||||||
return action(ctx)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// prepareWorkPathAndCustomConf tries to prepare the work path, custom path and custom config from various inputs:
|
||||||
|
// command line flags, environment variables, config file
|
||||||
|
func prepareWorkPathAndCustomConf(cmd *cli.Command) {
|
||||||
|
var args setting.ArgWorkPathAndCustomConf
|
||||||
|
if cmd.IsSet("work-path") {
|
||||||
|
args.WorkPath = cmd.String("work-path")
|
||||||
|
}
|
||||||
|
if cmd.IsSet("custom-path") {
|
||||||
|
args.CustomPath = cmd.String("custom-path")
|
||||||
|
}
|
||||||
|
if cmd.IsSet("config") {
|
||||||
|
args.CustomConf = cmd.String("config")
|
||||||
|
}
|
||||||
|
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
|
||||||
|
}
|
||||||
|
|
||||||
type AppVersion struct {
|
type AppVersion struct {
|
||||||
Version string
|
Version string
|
||||||
Extra string
|
Extra string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMainApp(appVer AppVersion) *cli.App {
|
func NewMainApp(appVer AppVersion) *cli.Command {
|
||||||
app := cli.NewApp()
|
app := &cli.Command{}
|
||||||
app.Name = "Gitea"
|
app.Name = "gitea" // must be lower-cased because it appears in the "USAGE" section like "gitea doctor [command [command options]]"
|
||||||
app.HelpName = "gitea"
|
|
||||||
app.Usage = "A painless self-hosted Git service"
|
app.Usage = "A painless self-hosted Git service"
|
||||||
app.Description = `Gitea program contains "web" and other subcommands. If no subcommand is given, it starts the web server by default. Use "web" subcommand for more web server arguments, use other subcommands for other purposes.`
|
app.Description = `Gitea program contains "web" and other subcommands. If no subcommand is given, it starts the web server by default. Use "web" subcommand for more web server arguments, use other subcommands for other purposes.`
|
||||||
app.Version = appVer.Version + appVer.Extra
|
app.Version = appVer.Version + appVer.Extra
|
||||||
app.EnableBashCompletion = true
|
app.EnableShellCompletion = true
|
||||||
|
app.Flags = []cli.Flag{
|
||||||
// these sub-commands need to use config file
|
&cli.StringFlag{
|
||||||
|
Name: "work-path",
|
||||||
|
Aliases: []string{"w"},
|
||||||
|
TakesFile: true,
|
||||||
|
Usage: "Set Gitea's working path (defaults to the Gitea's binary directory)",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "config",
|
||||||
|
Aliases: []string{"c"},
|
||||||
|
TakesFile: true,
|
||||||
|
Value: setting.CustomConf,
|
||||||
|
Usage: "Set custom config file (defaults to '{WorkPath}/custom/conf/app.ini')",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "custom-path",
|
||||||
|
Aliases: []string{"C"},
|
||||||
|
TakesFile: true,
|
||||||
|
Usage: "Set custom path (defaults to '{WorkPath}/custom')",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// these sub-commands need to use a config file
|
||||||
subCmdWithConfig := []*cli.Command{
|
subCmdWithConfig := []*cli.Command{
|
||||||
cmdHelp(), // the "help" sub-command was used to show the more information for "work path" and "custom config"
|
|
||||||
CmdWeb,
|
CmdWeb,
|
||||||
CmdServ,
|
CmdServ,
|
||||||
CmdHook,
|
CmdHook,
|
||||||
@@ -147,29 +132,30 @@ func NewMainApp(appVer AppVersion) *cli.App {
|
|||||||
|
|
||||||
// these sub-commands do not need the config file, and they do not depend on any path or environment variable.
|
// these sub-commands do not need the config file, and they do not depend on any path or environment variable.
|
||||||
subCmdStandalone := []*cli.Command{
|
subCmdStandalone := []*cli.Command{
|
||||||
CmdCert,
|
cmdCert(),
|
||||||
CmdGenerate,
|
CmdGenerate,
|
||||||
CmdDocs,
|
CmdDocs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: we should eventually drop the default command,
|
||||||
|
// but not sure whether it would break Windows users who used to double-click the EXE to run.
|
||||||
app.DefaultCommand = CmdWeb.Name
|
app.DefaultCommand = CmdWeb.Name
|
||||||
|
|
||||||
globalFlags := appGlobalFlags()
|
|
||||||
app.Flags = append(app.Flags, cli.VersionFlag)
|
|
||||||
app.Flags = append(app.Flags, globalFlags...)
|
|
||||||
app.HideHelp = true // use our own help action to show helps (with more information like default config)
|
|
||||||
app.Before = PrepareConsoleLoggerLevel(log.INFO)
|
app.Before = PrepareConsoleLoggerLevel(log.INFO)
|
||||||
for i := range subCmdWithConfig {
|
for i := range subCmdWithConfig {
|
||||||
prepareSubcommandWithConfig(subCmdWithConfig[i], globalFlags)
|
prepareSubcommandWithGlobalFlags(subCmdWithConfig[i])
|
||||||
}
|
}
|
||||||
app.Commands = append(app.Commands, subCmdWithConfig...)
|
app.Commands = append(app.Commands, subCmdWithConfig...)
|
||||||
app.Commands = append(app.Commands, subCmdStandalone...)
|
app.Commands = append(app.Commands, subCmdStandalone...)
|
||||||
|
|
||||||
|
setting.InitGiteaEnvVars()
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunMainApp(app *cli.App, args ...string) error {
|
func RunMainApp(app *cli.Command, args ...string) error {
|
||||||
err := app.Run(args)
|
ctx, cancel := installSignals()
|
||||||
|
defer cancel()
|
||||||
|
err := app.Run(ctx, args)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
157
cmd/main_test.go
157
cmd/main_test.go
@@ -4,9 +4,10 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -14,9 +15,10 @@ import (
|
|||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/test"
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
@@ -27,11 +29,11 @@ func makePathOutput(workPath, customPath, customConf string) string {
|
|||||||
return fmt.Sprintf("WorkPath=%s\nCustomPath=%s\nCustomConf=%s", workPath, customPath, customConf)
|
return fmt.Sprintf("WorkPath=%s\nCustomPath=%s\nCustomConf=%s", workPath, customPath, customConf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestApp(testCmdAction func(ctx *cli.Context) error) *cli.App {
|
func newTestApp(testCmd cli.Command) *cli.Command {
|
||||||
app := NewMainApp(AppVersion{})
|
app := NewMainApp(AppVersion{})
|
||||||
testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction}
|
testCmd.Name = util.IfZero(testCmd.Name, "test-cmd")
|
||||||
prepareSubcommandWithConfig(testCmd, appGlobalFlags())
|
prepareSubcommandWithGlobalFlags(&testCmd)
|
||||||
app.Commands = append(app.Commands, testCmd)
|
app.Commands = append(app.Commands, &testCmd)
|
||||||
app.DefaultCommand = testCmd.Name
|
app.DefaultCommand = testCmd.Name
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
@@ -42,7 +44,7 @@ type runResult struct {
|
|||||||
ExitCode int
|
ExitCode int
|
||||||
}
|
}
|
||||||
|
|
||||||
func runTestApp(app *cli.App, args ...string) (runResult, error) {
|
func runTestApp(app *cli.Command, args ...string) (runResult, error) {
|
||||||
outBuf := new(strings.Builder)
|
outBuf := new(strings.Builder)
|
||||||
errBuf := new(strings.Builder)
|
errBuf := new(strings.Builder)
|
||||||
app.Writer = outBuf
|
app.Writer = outBuf
|
||||||
@@ -65,7 +67,7 @@ func TestCliCmd(t *testing.T) {
|
|||||||
defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini")
|
defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini")
|
||||||
|
|
||||||
cli.CommandHelpTemplate = "(command help template)"
|
cli.CommandHelpTemplate = "(command help template)"
|
||||||
cli.AppHelpTemplate = "(app help template)"
|
cli.RootCommandHelpTemplate = "(app help template)"
|
||||||
cli.SubcommandHelpTemplate = "(subcommand help template)"
|
cli.SubcommandHelpTemplate = "(subcommand help template)"
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
@@ -73,12 +75,56 @@ func TestCliCmd(t *testing.T) {
|
|||||||
cmd string
|
cmd string
|
||||||
exp string
|
exp string
|
||||||
}{
|
}{
|
||||||
// main command help
|
// help commands
|
||||||
|
{
|
||||||
|
cmd: "./gitea -h",
|
||||||
|
exp: "DEFAULT CONFIGURATION:",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
cmd: "./gitea help",
|
cmd: "./gitea help",
|
||||||
exp: "DEFAULT CONFIGURATION:",
|
exp: "DEFAULT CONFIGURATION:",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
cmd: "./gitea -c /dev/null -h",
|
||||||
|
exp: "ConfigFile: /dev/null",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
cmd: "./gitea -c /dev/null help",
|
||||||
|
exp: "ConfigFile: /dev/null",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cmd: "./gitea help -c /dev/null",
|
||||||
|
exp: "ConfigFile: /dev/null",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
cmd: "./gitea -c /dev/null test-cmd -h",
|
||||||
|
exp: "ConfigFile: /dev/null",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cmd: "./gitea test-cmd -c /dev/null -h",
|
||||||
|
exp: "ConfigFile: /dev/null",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cmd: "./gitea test-cmd -h -c /dev/null",
|
||||||
|
exp: "ConfigFile: /dev/null",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
cmd: "./gitea -c /dev/null test-cmd help",
|
||||||
|
exp: "ConfigFile: /dev/null",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cmd: "./gitea test-cmd -c /dev/null help",
|
||||||
|
exp: "ConfigFile: /dev/null",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cmd: "./gitea test-cmd help -c /dev/null",
|
||||||
|
exp: "ConfigFile: /dev/null",
|
||||||
|
},
|
||||||
|
|
||||||
// parse paths
|
// parse paths
|
||||||
{
|
{
|
||||||
cmd: "./gitea test-cmd",
|
cmd: "./gitea test-cmd",
|
||||||
@@ -109,70 +155,75 @@ func TestCliCmd(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
app := newTestApp(func(ctx *cli.Context) error {
|
|
||||||
_, _ = fmt.Fprint(ctx.App.Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
var envBackup []string
|
|
||||||
for _, s := range os.Environ() {
|
|
||||||
if strings.HasPrefix(s, "GITEA_") && strings.Contains(s, "=") {
|
|
||||||
envBackup = append(envBackup, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
clearGiteaEnv := func() {
|
|
||||||
for _, s := range os.Environ() {
|
|
||||||
if strings.HasPrefix(s, "GITEA_") {
|
|
||||||
_ = os.Unsetenv(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
clearGiteaEnv()
|
|
||||||
for _, s := range envBackup {
|
|
||||||
k, v, _ := strings.Cut(s, "=")
|
|
||||||
_ = os.Setenv(k, v)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
clearGiteaEnv()
|
t.Run(c.cmd, func(t *testing.T) {
|
||||||
for k, v := range c.env {
|
app := newTestApp(cli.Command{
|
||||||
_ = os.Setenv(k, v)
|
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||||
}
|
_, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
|
||||||
args := strings.Split(c.cmd, " ") // for test only, "split" is good enough
|
return nil
|
||||||
r, err := runTestApp(app, args...)
|
},
|
||||||
assert.NoError(t, err, c.cmd)
|
})
|
||||||
assert.NotEmpty(t, c.exp, c.cmd)
|
for k, v := range c.env {
|
||||||
assert.Contains(t, r.Stdout, c.exp, c.cmd)
|
t.Setenv(k, v)
|
||||||
|
}
|
||||||
|
args := strings.Split(c.cmd, " ") // for test only, "split" is good enough
|
||||||
|
r, err := runTestApp(app, args...)
|
||||||
|
assert.NoError(t, err, c.cmd)
|
||||||
|
assert.NotEmpty(t, c.exp, c.cmd)
|
||||||
|
assert.Contains(t, r.Stdout, c.exp, c.cmd)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCliCmdError(t *testing.T) {
|
func TestCliCmdError(t *testing.T) {
|
||||||
app := newTestApp(func(ctx *cli.Context) error { return fmt.Errorf("normal error") })
|
app := newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return errors.New("normal error") }})
|
||||||
r, err := runTestApp(app, "./gitea", "test-cmd")
|
r, err := runTestApp(app, "./gitea", "test-cmd")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, 1, r.ExitCode)
|
assert.Equal(t, 1, r.ExitCode)
|
||||||
assert.Equal(t, "", r.Stdout)
|
assert.Empty(t, r.Stdout)
|
||||||
assert.Equal(t, "Command error: normal error\n", r.Stderr)
|
assert.Equal(t, "Command error: normal error\n", r.Stderr)
|
||||||
|
|
||||||
app = newTestApp(func(ctx *cli.Context) error { return cli.Exit("exit error", 2) })
|
app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return cli.Exit("exit error", 2) }})
|
||||||
r, err = runTestApp(app, "./gitea", "test-cmd")
|
r, err = runTestApp(app, "./gitea", "test-cmd")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, 2, r.ExitCode)
|
assert.Equal(t, 2, r.ExitCode)
|
||||||
assert.Equal(t, "", r.Stdout)
|
assert.Empty(t, r.Stdout)
|
||||||
assert.Equal(t, "exit error\n", r.Stderr)
|
assert.Equal(t, "exit error\n", r.Stderr)
|
||||||
|
|
||||||
app = newTestApp(func(ctx *cli.Context) error { return nil })
|
app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return nil }})
|
||||||
r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such")
|
r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, 1, r.ExitCode)
|
assert.Equal(t, 1, r.ExitCode)
|
||||||
assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stdout)
|
assert.Empty(t, r.Stdout)
|
||||||
assert.Equal(t, "", r.Stderr) // the cli package's strange behavior, the error message is not in stderr ....
|
assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stderr)
|
||||||
|
|
||||||
app = newTestApp(func(ctx *cli.Context) error { return nil })
|
app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return nil }})
|
||||||
r, err = runTestApp(app, "./gitea", "test-cmd")
|
r, err = runTestApp(app, "./gitea", "test-cmd")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called
|
assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called
|
||||||
assert.Equal(t, "", r.Stdout)
|
assert.Empty(t, r.Stdout)
|
||||||
assert.Equal(t, "", r.Stderr)
|
assert.Empty(t, r.Stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCliCmdBefore(t *testing.T) {
|
||||||
|
ctxNew := context.WithValue(context.Background(), any("key"), "value")
|
||||||
|
configValues := map[string]string{}
|
||||||
|
setting.CustomConf = "/tmp/any.ini"
|
||||||
|
var actionCtx context.Context
|
||||||
|
app := newTestApp(cli.Command{
|
||||||
|
Before: func(context.Context, *cli.Command) (context.Context, error) {
|
||||||
|
configValues["before"] = setting.CustomConf
|
||||||
|
return ctxNew, nil
|
||||||
|
},
|
||||||
|
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||||
|
configValues["action"] = setting.CustomConf
|
||||||
|
actionCtx = ctx
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
_, err := runTestApp(app, "./gitea", "--config", "/dev/null", "test-cmd")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, ctxNew, actionCtx)
|
||||||
|
assert.Equal(t, "/tmp/any.ini", configValues["before"], "BeforeFunc must be called before preparing config")
|
||||||
|
assert.Equal(t, "/dev/null", configValues["action"])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,13 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/private"
|
"code.gitea.io/gitea/modules/private"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -18,7 +19,7 @@ var (
|
|||||||
Name: "manager",
|
Name: "manager",
|
||||||
Usage: "Manage the running gitea process",
|
Usage: "Manage the running gitea process",
|
||||||
Description: "This is a command for managing the running gitea process",
|
Description: "This is a command for managing the running gitea process",
|
||||||
Subcommands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
subcmdShutdown,
|
subcmdShutdown,
|
||||||
subcmdRestart,
|
subcmdRestart,
|
||||||
subcmdReloadTemplates,
|
subcmdReloadTemplates,
|
||||||
@@ -108,46 +109,31 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func runShutdown(c *cli.Context) error {
|
func runShutdown(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"))
|
||||||
extra := private.Shutdown(ctx)
|
extra := private.Shutdown(ctx)
|
||||||
return handleCliResponseExtra(extra)
|
return handleCliResponseExtra(extra)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRestart(c *cli.Context) error {
|
func runRestart(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"))
|
||||||
extra := private.Restart(ctx)
|
extra := private.Restart(ctx)
|
||||||
return handleCliResponseExtra(extra)
|
return handleCliResponseExtra(extra)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runReloadTemplates(c *cli.Context) error {
|
func runReloadTemplates(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"))
|
||||||
extra := private.ReloadTemplates(ctx)
|
extra := private.ReloadTemplates(ctx)
|
||||||
return handleCliResponseExtra(extra)
|
return handleCliResponseExtra(extra)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runFlushQueues(c *cli.Context) error {
|
func runFlushQueues(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"))
|
||||||
extra := private.FlushQueues(ctx, c.Duration("timeout"), c.Bool("non-blocking"))
|
extra := private.FlushQueues(ctx, c.Duration("timeout"), c.Bool("non-blocking"))
|
||||||
return handleCliResponseExtra(extra)
|
return handleCliResponseExtra(extra)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runProcesses(c *cli.Context) error {
|
func runProcesses(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"))
|
||||||
extra := private.Processes(ctx, os.Stdout, c.Bool("flat"), c.Bool("no-system"), c.Bool("stacktraces"), c.Bool("json"), c.String("cancel"))
|
extra := private.Processes(ctx, os.Stdout, c.Bool("flat"), c.Bool("no-system"), c.Bool("stacktraces"), c.Bool("json"), c.String("cancel"))
|
||||||
return handleCliResponseExtra(extra)
|
return handleCliResponseExtra(extra)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@@ -11,7 +12,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/private"
|
"code.gitea.io/gitea/modules/private"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -60,7 +61,7 @@ var (
|
|||||||
subcmdLogging = &cli.Command{
|
subcmdLogging = &cli.Command{
|
||||||
Name: "logging",
|
Name: "logging",
|
||||||
Usage: "Adjust logging commands",
|
Usage: "Adjust logging commands",
|
||||||
Subcommands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
{
|
{
|
||||||
Name: "pause",
|
Name: "pause",
|
||||||
Usage: "Pause logging (Gitea will buffer logs up to a certain point and will drop them after that point)",
|
Usage: "Pause logging (Gitea will buffer logs up to a certain point and will drop them after that point)",
|
||||||
@@ -104,7 +105,7 @@ var (
|
|||||||
}, {
|
}, {
|
||||||
Name: "add",
|
Name: "add",
|
||||||
Usage: "Add a logger",
|
Usage: "Add a logger",
|
||||||
Subcommands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
{
|
{
|
||||||
Name: "file",
|
Name: "file",
|
||||||
Usage: "Add a file logger",
|
Usage: "Add a file logger",
|
||||||
@@ -118,7 +119,6 @@ var (
|
|||||||
Name: "rotate",
|
Name: "rotate",
|
||||||
Aliases: []string{"r"},
|
Aliases: []string{"r"},
|
||||||
Usage: "Rotate logs",
|
Usage: "Rotate logs",
|
||||||
Value: true,
|
|
||||||
},
|
},
|
||||||
&cli.Int64Flag{
|
&cli.Int64Flag{
|
||||||
Name: "max-size",
|
Name: "max-size",
|
||||||
@@ -129,7 +129,6 @@ var (
|
|||||||
Name: "daily",
|
Name: "daily",
|
||||||
Aliases: []string{"d"},
|
Aliases: []string{"d"},
|
||||||
Usage: "Rotate logs daily",
|
Usage: "Rotate logs daily",
|
||||||
Value: true,
|
|
||||||
},
|
},
|
||||||
&cli.IntFlag{
|
&cli.IntFlag{
|
||||||
Name: "max-days",
|
Name: "max-days",
|
||||||
@@ -140,7 +139,6 @@ var (
|
|||||||
Name: "compress",
|
Name: "compress",
|
||||||
Aliases: []string{"z"},
|
Aliases: []string{"z"},
|
||||||
Usage: "Compress rotated logs",
|
Usage: "Compress rotated logs",
|
||||||
Value: true,
|
|
||||||
},
|
},
|
||||||
&cli.IntFlag{
|
&cli.IntFlag{
|
||||||
Name: "compression-level",
|
Name: "compression-level",
|
||||||
@@ -195,10 +193,7 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func runRemoveLogger(c *cli.Context) error {
|
func runRemoveLogger(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"))
|
||||||
logger := c.String("logger")
|
logger := c.String("logger")
|
||||||
if len(logger) == 0 {
|
if len(logger) == 0 {
|
||||||
@@ -210,10 +205,7 @@ func runRemoveLogger(c *cli.Context) error {
|
|||||||
return handleCliResponseExtra(extra)
|
return handleCliResponseExtra(extra)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runAddConnLogger(c *cli.Context) error {
|
func runAddConnLogger(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"))
|
||||||
vals := map[string]any{}
|
vals := map[string]any{}
|
||||||
mode := "conn"
|
mode := "conn"
|
||||||
@@ -237,13 +229,10 @@ func runAddConnLogger(c *cli.Context) error {
|
|||||||
if c.IsSet("reconnect-on-message") {
|
if c.IsSet("reconnect-on-message") {
|
||||||
vals["reconnectOnMsg"] = c.Bool("reconnect-on-message")
|
vals["reconnectOnMsg"] = c.Bool("reconnect-on-message")
|
||||||
}
|
}
|
||||||
return commonAddLogger(c, mode, vals)
|
return commonAddLogger(ctx, c, mode, vals)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runAddFileLogger(c *cli.Context) error {
|
func runAddFileLogger(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"))
|
||||||
vals := map[string]any{}
|
vals := map[string]any{}
|
||||||
mode := "file"
|
mode := "file"
|
||||||
@@ -270,10 +259,10 @@ func runAddFileLogger(c *cli.Context) error {
|
|||||||
if c.IsSet("compression-level") {
|
if c.IsSet("compression-level") {
|
||||||
vals["compressionLevel"] = c.Int("compression-level")
|
vals["compressionLevel"] = c.Int("compression-level")
|
||||||
}
|
}
|
||||||
return commonAddLogger(c, mode, vals)
|
return commonAddLogger(ctx, c, mode, vals)
|
||||||
}
|
}
|
||||||
|
|
||||||
func commonAddLogger(c *cli.Context, mode string, vals map[string]any) error {
|
func commonAddLogger(ctx context.Context, c *cli.Command, mode string, vals map[string]any) error {
|
||||||
if len(c.String("level")) > 0 {
|
if len(c.String("level")) > 0 {
|
||||||
vals["level"] = log.LevelFromString(c.String("level")).String()
|
vals["level"] = log.LevelFromString(c.String("level")).String()
|
||||||
}
|
}
|
||||||
@@ -300,46 +289,33 @@ func commonAddLogger(c *cli.Context, mode string, vals map[string]any) error {
|
|||||||
if c.IsSet("writer") {
|
if c.IsSet("writer") {
|
||||||
writer = c.String("writer")
|
writer = c.String("writer")
|
||||||
}
|
}
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
extra := private.AddLogger(ctx, logger, writer, mode, vals)
|
extra := private.AddLogger(ctx, logger, writer, mode, vals)
|
||||||
return handleCliResponseExtra(extra)
|
return handleCliResponseExtra(extra)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPauseLogging(c *cli.Context) error {
|
func runPauseLogging(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"))
|
||||||
userMsg := private.PauseLogging(ctx)
|
userMsg := private.PauseLogging(ctx)
|
||||||
_, _ = fmt.Fprintln(os.Stdout, userMsg)
|
_, _ = fmt.Fprintln(os.Stdout, userMsg)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runResumeLogging(c *cli.Context) error {
|
func runResumeLogging(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"))
|
||||||
userMsg := private.ResumeLogging(ctx)
|
userMsg := private.ResumeLogging(ctx)
|
||||||
_, _ = fmt.Fprintln(os.Stdout, userMsg)
|
_, _ = fmt.Fprintln(os.Stdout, userMsg)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runReleaseReopenLogging(c *cli.Context) error {
|
func runReleaseReopenLogging(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"))
|
||||||
userMsg := private.ReleaseReopenLogging(ctx)
|
userMsg := private.ReleaseReopenLogging(ctx)
|
||||||
_, _ = fmt.Fprintln(os.Stdout, userMsg)
|
_, _ = fmt.Fprintln(os.Stdout, userMsg)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runSetLogSQL(c *cli.Context) error {
|
func runSetLogSQL(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"))
|
||||||
|
|
||||||
extra := private.SetLogSQL(ctx, !c.Bool("off"))
|
extra := private.SetLogSQL(ctx, !c.Bool("off"))
|
||||||
|
|||||||
@@ -7,26 +7,23 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/migrations"
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/services/versioned_migration"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdMigrate represents the available migrate sub-command.
|
// CmdMigrate represents the available migrate sub-command.
|
||||||
var CmdMigrate = &cli.Command{
|
var CmdMigrate = &cli.Command{
|
||||||
Name: "migrate",
|
Name: "migrate",
|
||||||
Usage: "Migrate the database",
|
Usage: "Migrate the database",
|
||||||
Description: "This is a command for migrating the database, so that you can run gitea admin create-user before starting the server.",
|
Description: `This is a command for migrating the database, so that you can run "gitea admin create user" before starting the server.`,
|
||||||
Action: runMigrate,
|
Action: runMigrate,
|
||||||
}
|
}
|
||||||
|
|
||||||
func runMigrate(ctx *cli.Context) error {
|
func runMigrate(ctx context.Context, c *cli.Command) error {
|
||||||
stdCtx, cancel := installSignals()
|
if err := initDB(ctx); err != nil {
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := initDB(stdCtx); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +33,7 @@ func runMigrate(ctx *cli.Context) error {
|
|||||||
log.Info("Log path: %s", setting.Log.RootPath)
|
log.Info("Log path: %s", setting.Log.RootPath)
|
||||||
log.Info("Configuration file: %s", setting.CustomConf)
|
log.Info("Configuration file: %s", setting.CustomConf)
|
||||||
|
|
||||||
if err := db.InitEngineWithMigration(context.Background(), migrations.Migrate); err != nil {
|
if err := db.InitEngineWithMigration(context.Background(), versioned_migration.Migrate); err != nil {
|
||||||
log.Fatal("Failed to initialize ORM engine: %v", err)
|
log.Fatal("Failed to initialize ORM engine: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
actions_model "code.gitea.io/gitea/models/actions"
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
"code.gitea.io/gitea/models/migrations"
|
|
||||||
packages_model "code.gitea.io/gitea/models/packages"
|
packages_model "code.gitea.io/gitea/models/packages"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
@@ -21,8 +20,9 @@ import (
|
|||||||
packages_module "code.gitea.io/gitea/modules/packages"
|
packages_module "code.gitea.io/gitea/modules/packages"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"code.gitea.io/gitea/modules/storage"
|
||||||
|
"code.gitea.io/gitea/services/versioned_migration"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdMigrateStorage represents the available migrate storage sub-command.
|
// CmdMigrateStorage represents the available migrate storage sub-command.
|
||||||
@@ -196,7 +196,7 @@ func migrateActionsLog(ctx context.Context, dstStorage storage.ObjectStorage) er
|
|||||||
|
|
||||||
func migrateActionsArtifacts(ctx context.Context, dstStorage storage.ObjectStorage) error {
|
func migrateActionsArtifacts(ctx context.Context, dstStorage storage.ObjectStorage) error {
|
||||||
return db.Iterate(ctx, nil, func(ctx context.Context, artifact *actions_model.ActionArtifact) error {
|
return db.Iterate(ctx, nil, func(ctx context.Context, artifact *actions_model.ActionArtifact) error {
|
||||||
if artifact.Status == int64(actions_model.ArtifactStatusExpired) {
|
if artifact.Status == actions_model.ArtifactStatusExpired {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,11 +213,8 @@ func migrateActionsArtifacts(ctx context.Context, dstStorage storage.ObjectStora
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func runMigrateStorage(ctx *cli.Context) error {
|
func runMigrateStorage(ctx context.Context, cmd *cli.Command) error {
|
||||||
stdCtx, cancel := installSignals()
|
if err := initDB(ctx); err != nil {
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := initDB(stdCtx); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,7 +224,7 @@ func runMigrateStorage(ctx *cli.Context) error {
|
|||||||
log.Info("Log path: %s", setting.Log.RootPath)
|
log.Info("Log path: %s", setting.Log.RootPath)
|
||||||
log.Info("Configuration file: %s", setting.CustomConf)
|
log.Info("Configuration file: %s", setting.CustomConf)
|
||||||
|
|
||||||
if err := db.InitEngineWithMigration(context.Background(), migrations.Migrate); err != nil {
|
if err := db.InitEngineWithMigration(context.Background(), versioned_migration.Migrate); err != nil {
|
||||||
log.Fatal("Failed to initialize ORM engine: %v", err)
|
log.Fatal("Failed to initialize ORM engine: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -238,51 +235,51 @@ func runMigrateStorage(ctx *cli.Context) error {
|
|||||||
|
|
||||||
var dstStorage storage.ObjectStorage
|
var dstStorage storage.ObjectStorage
|
||||||
var err error
|
var err error
|
||||||
switch strings.ToLower(ctx.String("storage")) {
|
switch strings.ToLower(cmd.String("storage")) {
|
||||||
case "":
|
case "":
|
||||||
fallthrough
|
fallthrough
|
||||||
case string(setting.LocalStorageType):
|
case string(setting.LocalStorageType):
|
||||||
p := ctx.String("path")
|
p := cmd.String("path")
|
||||||
if p == "" {
|
if p == "" {
|
||||||
log.Fatal("Path must be given when storage is local")
|
log.Fatal("Path must be given when storage is local")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
dstStorage, err = storage.NewLocalStorage(
|
dstStorage, err = storage.NewLocalStorage(
|
||||||
stdCtx,
|
ctx,
|
||||||
&setting.Storage{
|
&setting.Storage{
|
||||||
Path: p,
|
Path: p,
|
||||||
})
|
})
|
||||||
case string(setting.MinioStorageType):
|
case string(setting.MinioStorageType):
|
||||||
dstStorage, err = storage.NewMinioStorage(
|
dstStorage, err = storage.NewMinioStorage(
|
||||||
stdCtx,
|
ctx,
|
||||||
&setting.Storage{
|
&setting.Storage{
|
||||||
MinioConfig: setting.MinioStorageConfig{
|
MinioConfig: setting.MinioStorageConfig{
|
||||||
Endpoint: ctx.String("minio-endpoint"),
|
Endpoint: cmd.String("minio-endpoint"),
|
||||||
AccessKeyID: ctx.String("minio-access-key-id"),
|
AccessKeyID: cmd.String("minio-access-key-id"),
|
||||||
SecretAccessKey: ctx.String("minio-secret-access-key"),
|
SecretAccessKey: cmd.String("minio-secret-access-key"),
|
||||||
Bucket: ctx.String("minio-bucket"),
|
Bucket: cmd.String("minio-bucket"),
|
||||||
Location: ctx.String("minio-location"),
|
Location: cmd.String("minio-location"),
|
||||||
BasePath: ctx.String("minio-base-path"),
|
BasePath: cmd.String("minio-base-path"),
|
||||||
UseSSL: ctx.Bool("minio-use-ssl"),
|
UseSSL: cmd.Bool("minio-use-ssl"),
|
||||||
InsecureSkipVerify: ctx.Bool("minio-insecure-skip-verify"),
|
InsecureSkipVerify: cmd.Bool("minio-insecure-skip-verify"),
|
||||||
ChecksumAlgorithm: ctx.String("minio-checksum-algorithm"),
|
ChecksumAlgorithm: cmd.String("minio-checksum-algorithm"),
|
||||||
BucketLookUpType: ctx.String("minio-bucket-lookup-type"),
|
BucketLookUpType: cmd.String("minio-bucket-lookup-type"),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
case string(setting.AzureBlobStorageType):
|
case string(setting.AzureBlobStorageType):
|
||||||
dstStorage, err = storage.NewAzureBlobStorage(
|
dstStorage, err = storage.NewAzureBlobStorage(
|
||||||
stdCtx,
|
ctx,
|
||||||
&setting.Storage{
|
&setting.Storage{
|
||||||
AzureBlobConfig: setting.AzureBlobStorageConfig{
|
AzureBlobConfig: setting.AzureBlobStorageConfig{
|
||||||
Endpoint: ctx.String("azureblob-endpoint"),
|
Endpoint: cmd.String("azureblob-endpoint"),
|
||||||
AccountName: ctx.String("azureblob-account-name"),
|
AccountName: cmd.String("azureblob-account-name"),
|
||||||
AccountKey: ctx.String("azureblob-account-key"),
|
AccountKey: cmd.String("azureblob-account-key"),
|
||||||
Container: ctx.String("azureblob-container"),
|
Container: cmd.String("azureblob-container"),
|
||||||
BasePath: ctx.String("azureblob-base-path"),
|
BasePath: cmd.String("azureblob-base-path"),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported storage type: %s", ctx.String("storage"))
|
return fmt.Errorf("unsupported storage type: %s", cmd.String("storage"))
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -299,14 +296,14 @@ func runMigrateStorage(ctx *cli.Context) error {
|
|||||||
"actions-artifacts": migrateActionsArtifacts,
|
"actions-artifacts": migrateActionsArtifacts,
|
||||||
}
|
}
|
||||||
|
|
||||||
tp := strings.ToLower(ctx.String("type"))
|
tp := strings.ToLower(cmd.String("type"))
|
||||||
if m, ok := migratedMethods[tp]; ok {
|
if m, ok := migratedMethods[tp]; ok {
|
||||||
if err := m(stdCtx, dstStorage); err != nil {
|
if err := m(ctx, dstStorage); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Info("%s files have successfully been copied to the new storage.", tp)
|
log.Info("%s files have successfully been copied to the new storage.", tp)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("unsupported storage: %s", ctx.String("type"))
|
return fmt.Errorf("unsupported storage: %s", cmd.String("type"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,10 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
|
||||||
"code.gitea.io/gitea/models/packages"
|
"code.gitea.io/gitea/models/packages"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
@@ -31,7 +29,7 @@ func TestMigratePackages(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer buf.Close()
|
defer buf.Close()
|
||||||
|
|
||||||
v, f, err := packages_service.CreatePackageAndAddFile(db.DefaultContext, &packages_service.PackageCreationInfo{
|
v, f, err := packages_service.CreatePackageAndAddFile(t.Context(), &packages_service.PackageCreationInfo{
|
||||||
PackageInfo: packages_service.PackageInfo{
|
PackageInfo: packages_service.PackageInfo{
|
||||||
Owner: creator,
|
Owner: creator,
|
||||||
PackageType: packages.TypeGeneric,
|
PackageType: packages.TypeGeneric,
|
||||||
@@ -53,7 +51,7 @@ func TestMigratePackages(t *testing.T) {
|
|||||||
assert.NotNil(t, v)
|
assert.NotNil(t, v)
|
||||||
assert.NotNil(t, f)
|
assert.NotNil(t, f)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
p := t.TempDir()
|
p := t.TempDir()
|
||||||
|
|
||||||
@@ -70,6 +68,6 @@ func TestMigratePackages(t *testing.T) {
|
|||||||
entries, err := os.ReadDir(p)
|
entries, err := os.ReadDir(p)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, entries, 2)
|
assert.Len(t, entries, 2)
|
||||||
assert.EqualValues(t, "01", entries[0].Name())
|
assert.Equal(t, "01", entries[0].Name())
|
||||||
assert.EqualValues(t, "tmp", entries[1].Name())
|
assert.Equal(t, "tmp", entries[1].Name())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,13 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/private"
|
"code.gitea.io/gitea/modules/private"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdRestoreRepository represents the available restore a repository sub-command.
|
// CmdRestoreRepository represents the available restore a repository sub-command.
|
||||||
@@ -48,10 +49,7 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRestoreRepository(c *cli.Context) error {
|
func runRestoreRepository(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
setting.MustInstalled()
|
setting.MustInstalled()
|
||||||
var units []string
|
var units []string
|
||||||
if s := c.String("units"); s != "" {
|
if s := c.String("units"); s != "" {
|
||||||
|
|||||||
182
cmd/serv.go
182
cmd/serv.go
@@ -11,17 +11,16 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
"code.gitea.io/gitea/modules/container"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
"code.gitea.io/gitea/modules/lfstransfer"
|
"code.gitea.io/gitea/modules/lfstransfer"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
@@ -32,17 +31,8 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/services/lfs"
|
"code.gitea.io/gitea/services/lfs"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
"github.com/kballard/go-shellquote"
|
"github.com/kballard/go-shellquote"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
verbUploadPack = "git-upload-pack"
|
|
||||||
verbUploadArchive = "git-upload-archive"
|
|
||||||
verbReceivePack = "git-receive-pack"
|
|
||||||
verbLfsAuthenticate = "git-lfs-authenticate"
|
|
||||||
verbLfsTransfer = "git-lfs-transfer"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdServ represents the available serv sub-command.
|
// CmdServ represents the available serv sub-command.
|
||||||
@@ -50,6 +40,7 @@ var CmdServ = &cli.Command{
|
|||||||
Name: "serv",
|
Name: "serv",
|
||||||
Usage: "(internal) Should only be called by SSH shell",
|
Usage: "(internal) Should only be called by SSH shell",
|
||||||
Description: "Serv provides access auth for repositories",
|
Description: "Serv provides access auth for repositories",
|
||||||
|
Hidden: true, // Internal commands shouldn't be visible in help
|
||||||
Before: PrepareConsoleLoggerLevel(log.FATAL),
|
Before: PrepareConsoleLoggerLevel(log.FATAL),
|
||||||
Action: runServ,
|
Action: runServ,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
@@ -73,27 +64,11 @@ func setup(ctx context.Context, debug bool) {
|
|||||||
_ = fail(ctx, "Unable to access repository path", "Unable to access repository path %q, err: %v", setting.RepoRootPath, err)
|
_ = fail(ctx, "Unable to access repository path", "Unable to access repository path %q, err: %v", setting.RepoRootPath, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := git.InitSimple(context.Background()); err != nil {
|
if err := git.InitSimple(); err != nil {
|
||||||
_ = fail(ctx, "Failed to init git", "Failed to init git, err: %v", err)
|
_ = fail(ctx, "Failed to init git", "Failed to init git, err: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
// keep getAccessMode() in sync
|
|
||||||
allowedCommands = container.SetOf(
|
|
||||||
verbUploadPack,
|
|
||||||
verbUploadArchive,
|
|
||||||
verbReceivePack,
|
|
||||||
verbLfsAuthenticate,
|
|
||||||
verbLfsTransfer,
|
|
||||||
)
|
|
||||||
allowedCommandsLfs = container.SetOf(
|
|
||||||
verbLfsAuthenticate,
|
|
||||||
verbLfsTransfer,
|
|
||||||
)
|
|
||||||
alphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// fail prints message to stdout, it's mainly used for git serv and git hook commands.
|
// fail prints message to stdout, it's mainly used for git serv and git hook commands.
|
||||||
// The output will be passed to git client and shown to user.
|
// The output will be passed to git client and shown to user.
|
||||||
func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error {
|
func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error {
|
||||||
@@ -104,7 +79,10 @@ func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error
|
|||||||
// There appears to be a chance to cause a zombie process and failure to read the Exit status
|
// There appears to be a chance to cause a zombie process and failure to read the Exit status
|
||||||
// if nothing is outputted on stdout.
|
// if nothing is outputted on stdout.
|
||||||
_, _ = fmt.Fprintln(os.Stdout, "")
|
_, _ = fmt.Fprintln(os.Stdout, "")
|
||||||
_, _ = fmt.Fprintln(os.Stderr, "Gitea:", userMessage)
|
// add extra empty lines to separate our message from other git errors to get more attention
|
||||||
|
_, _ = fmt.Fprintln(os.Stderr, "error:")
|
||||||
|
_, _ = fmt.Fprintln(os.Stderr, "error:", userMessage)
|
||||||
|
_, _ = fmt.Fprintln(os.Stderr, "error:")
|
||||||
|
|
||||||
if logMsgFmt != "" {
|
if logMsgFmt != "" {
|
||||||
logMsg := fmt.Sprintf(logMsgFmt, args...)
|
logMsg := fmt.Sprintf(logMsgFmt, args...)
|
||||||
@@ -136,47 +114,24 @@ func handleCliResponseExtra(extra private.ResponseExtra) error {
|
|||||||
|
|
||||||
func getAccessMode(verb, lfsVerb string) perm.AccessMode {
|
func getAccessMode(verb, lfsVerb string) perm.AccessMode {
|
||||||
switch verb {
|
switch verb {
|
||||||
case verbUploadPack, verbUploadArchive:
|
case git.CmdVerbUploadPack, git.CmdVerbUploadArchive:
|
||||||
return perm.AccessModeRead
|
return perm.AccessModeRead
|
||||||
case verbReceivePack:
|
case git.CmdVerbReceivePack:
|
||||||
return perm.AccessModeWrite
|
return perm.AccessModeWrite
|
||||||
case verbLfsAuthenticate, verbLfsTransfer:
|
case git.CmdVerbLfsAuthenticate, git.CmdVerbLfsTransfer:
|
||||||
switch lfsVerb {
|
switch lfsVerb {
|
||||||
case "upload":
|
case git.CmdSubVerbLfsUpload:
|
||||||
return perm.AccessModeWrite
|
return perm.AccessModeWrite
|
||||||
case "download":
|
case git.CmdSubVerbLfsDownload:
|
||||||
return perm.AccessModeRead
|
return perm.AccessModeRead
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// should be unreachable
|
// should be unreachable
|
||||||
|
setting.PanicInDevOrTesting("unknown verb: %s %s", verb, lfsVerb)
|
||||||
return perm.AccessModeNone
|
return perm.AccessModeNone
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLFSAuthToken(ctx context.Context, lfsVerb string, results *private.ServCommandResults) (string, error) {
|
func runServ(ctx context.Context, c *cli.Command) error {
|
||||||
now := time.Now()
|
|
||||||
claims := lfs.Claims{
|
|
||||||
RegisteredClaims: jwt.RegisteredClaims{
|
|
||||||
ExpiresAt: jwt.NewNumericDate(now.Add(setting.LFS.HTTPAuthExpiry)),
|
|
||||||
NotBefore: jwt.NewNumericDate(now),
|
|
||||||
},
|
|
||||||
RepoID: results.RepoID,
|
|
||||||
Op: lfsVerb,
|
|
||||||
UserID: results.UserID,
|
|
||||||
}
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
||||||
|
|
||||||
// Sign and get the complete encoded token as a string using the secret
|
|
||||||
tokenString, err := token.SignedString(setting.LFS.JWTSecretBytes)
|
|
||||||
if err != nil {
|
|
||||||
return "", fail(ctx, "Failed to sign JWT Token", "Failed to sign JWT token: %v", err)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("Bearer %s", tokenString), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func runServ(c *cli.Context) error {
|
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// FIXME: This needs to internationalised
|
// FIXME: This needs to internationalised
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"))
|
||||||
|
|
||||||
@@ -227,41 +182,32 @@ func runServ(c *cli.Context) error {
|
|||||||
log.Debug("SSH_ORIGINAL_COMMAND: %s", os.Getenv("SSH_ORIGINAL_COMMAND"))
|
log.Debug("SSH_ORIGINAL_COMMAND: %s", os.Getenv("SSH_ORIGINAL_COMMAND"))
|
||||||
}
|
}
|
||||||
|
|
||||||
words, err := shellquote.Split(cmd)
|
sshCmdArgs, err := shellquote.Split(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fail(ctx, "Error parsing arguments", "Failed to parse arguments: %v", err)
|
return fail(ctx, "Error parsing arguments", "Failed to parse arguments: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(words) < 2 {
|
if len(sshCmdArgs) < 2 {
|
||||||
if git.DefaultFeatures().SupportProcReceive {
|
if git.DefaultFeatures().SupportProcReceive {
|
||||||
// for AGit Flow
|
// for AGit Flow
|
||||||
if cmd == "ssh_info" {
|
if cmd == "ssh_info" {
|
||||||
fmt.Print(`{"type":"gitea","version":1}`)
|
fmt.Print(`{"type":"agit","version":1}`)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd)
|
return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
verb := words[0]
|
repoPath := strings.TrimPrefix(sshCmdArgs[1], "/")
|
||||||
repoPath := strings.TrimPrefix(words[1], "/")
|
repoPathFields := strings.SplitN(repoPath, "/", 2)
|
||||||
|
if len(repoPathFields) != 2 {
|
||||||
var lfsVerb string
|
|
||||||
|
|
||||||
rr := strings.SplitN(repoPath, "/", 2)
|
|
||||||
if len(rr) != 2 {
|
|
||||||
return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath)
|
return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
username := rr[0]
|
username := repoPathFields[0]
|
||||||
reponame := strings.TrimSuffix(rr[1], ".git")
|
reponame := strings.TrimSuffix(repoPathFields[1], ".git") // “the-repo-name" or "the-repo-name.wiki"
|
||||||
|
|
||||||
// LowerCase and trim the repoPath as that's how they are stored.
|
if !repo_model.IsValidSSHAccessRepoName(reponame) {
|
||||||
// This should be done after splitting the repoPath into username and reponame
|
|
||||||
// so that username and reponame are not affected.
|
|
||||||
repoPath = strings.ToLower(strings.TrimSpace(repoPath))
|
|
||||||
|
|
||||||
if alphaDashDotPattern.MatchString(reponame) {
|
|
||||||
return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)
|
return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,22 +229,23 @@ func runServ(c *cli.Context) error {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
if allowedCommands.Contains(verb) {
|
verb, lfsVerb := sshCmdArgs[0], ""
|
||||||
if allowedCommandsLfs.Contains(verb) {
|
if !git.IsAllowedVerbForServe(verb) {
|
||||||
if !setting.LFS.StartServer {
|
|
||||||
return fail(ctx, "LFS Server is not enabled", "")
|
|
||||||
}
|
|
||||||
if verb == verbLfsTransfer && !setting.LFS.AllowPureSSH {
|
|
||||||
return fail(ctx, "LFS SSH transfer is not enabled", "")
|
|
||||||
}
|
|
||||||
if len(words) > 2 {
|
|
||||||
lfsVerb = words[2]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fail(ctx, "Unknown git command", "Unknown git command %s", verb)
|
return fail(ctx, "Unknown git command", "Unknown git command %s", verb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if git.IsAllowedVerbForServeLfs(verb) {
|
||||||
|
if !setting.LFS.StartServer {
|
||||||
|
return fail(ctx, "LFS Server is not enabled", "")
|
||||||
|
}
|
||||||
|
if verb == git.CmdVerbLfsTransfer && !setting.LFS.AllowPureSSH {
|
||||||
|
return fail(ctx, "LFS SSH transfer is not enabled", "")
|
||||||
|
}
|
||||||
|
if len(sshCmdArgs) > 2 {
|
||||||
|
lfsVerb = sshCmdArgs[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
requestedMode := getAccessMode(verb, lfsVerb)
|
requestedMode := getAccessMode(verb, lfsVerb)
|
||||||
|
|
||||||
results, extra := private.ServCommand(ctx, keyID, username, reponame, requestedMode, verb, lfsVerb)
|
results, extra := private.ServCommand(ctx, keyID, username, reponame, requestedMode, verb, lfsVerb)
|
||||||
@@ -306,9 +253,16 @@ func runServ(c *cli.Context) error {
|
|||||||
return fail(ctx, extra.UserMsg, "ServCommand failed: %s", extra.Error)
|
return fail(ctx, extra.UserMsg, "ServCommand failed: %s", extra.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// because the original repoPath maybe redirected, we need to use the returned actual repository information
|
||||||
|
if results.IsWiki {
|
||||||
|
repoPath = repo_model.RelativeWikiPath(results.OwnerName, results.RepoName)
|
||||||
|
} else {
|
||||||
|
repoPath = repo_model.RelativePath(results.OwnerName, results.RepoName)
|
||||||
|
}
|
||||||
|
|
||||||
// LFS SSH protocol
|
// LFS SSH protocol
|
||||||
if verb == verbLfsTransfer {
|
if verb == git.CmdVerbLfsTransfer {
|
||||||
token, err := getLFSAuthToken(ctx, lfsVerb, results)
|
token, err := lfs.GetLFSAuthTokenWithBearer(lfs.AuthTokenOptions{Op: lfsVerb, UserID: results.UserID, RepoID: results.RepoID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -316,10 +270,10 @@ func runServ(c *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LFS token authentication
|
// LFS token authentication
|
||||||
if verb == verbLfsAuthenticate {
|
if verb == git.CmdVerbLfsAuthenticate {
|
||||||
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
|
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
|
||||||
|
|
||||||
token, err := getLFSAuthToken(ctx, lfsVerb, results)
|
token, err := lfs.GetLFSAuthTokenWithBearer(lfs.AuthTokenOptions{Op: lfsVerb, UserID: results.UserID, RepoID: results.RepoID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -338,30 +292,30 @@ func runServ(c *cli.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var gitcmd *exec.Cmd
|
var command *exec.Cmd
|
||||||
gitBinPath := filepath.Dir(git.GitExecutable) // e.g. /usr/bin
|
gitBinPath := filepath.Dir(gitcmd.GitExecutable) // e.g. /usr/bin
|
||||||
gitBinVerb := filepath.Join(gitBinPath, verb) // e.g. /usr/bin/git-upload-pack
|
gitBinVerb := filepath.Join(gitBinPath, verb) // e.g. /usr/bin/git-upload-pack
|
||||||
if _, err := os.Stat(gitBinVerb); err != nil {
|
if _, err := os.Stat(gitBinVerb); err != nil {
|
||||||
// if the command "git-upload-pack" doesn't exist, try to split "git-upload-pack" to use the sub-command with git
|
// if the command "git-upload-pack" doesn't exist, try to split "git-upload-pack" to use the sub-command with git
|
||||||
// ps: Windows only has "git.exe" in the bin path, so Windows always uses this way
|
// ps: Windows only has "git.exe" in the bin path, so Windows always uses this way
|
||||||
verbFields := strings.SplitN(verb, "-", 2)
|
verbFields := strings.SplitN(verb, "-", 2)
|
||||||
if len(verbFields) == 2 {
|
if len(verbFields) == 2 {
|
||||||
// use git binary with the sub-command part: "C:\...\bin\git.exe", "upload-pack", ...
|
// use git binary with the sub-command part: "C:\...\bin\git.exe", "upload-pack", ...
|
||||||
gitcmd = exec.CommandContext(ctx, git.GitExecutable, verbFields[1], repoPath)
|
command = exec.CommandContext(ctx, gitcmd.GitExecutable, verbFields[1], repoPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if gitcmd == nil {
|
if command == nil {
|
||||||
// by default, use the verb (it has been checked above by allowedCommands)
|
// by default, use the verb (it has been checked above by allowedCommands)
|
||||||
gitcmd = exec.CommandContext(ctx, gitBinVerb, repoPath)
|
command = exec.CommandContext(ctx, gitBinVerb, repoPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
process.SetSysProcAttribute(gitcmd)
|
process.SetSysProcAttribute(command)
|
||||||
gitcmd.Dir = setting.RepoRootPath
|
command.Dir = setting.RepoRootPath
|
||||||
gitcmd.Stdout = os.Stdout
|
command.Stdout = os.Stdout
|
||||||
gitcmd.Stdin = os.Stdin
|
command.Stdin = os.Stdin
|
||||||
gitcmd.Stderr = os.Stderr
|
command.Stderr = os.Stderr
|
||||||
gitcmd.Env = append(gitcmd.Env, os.Environ()...)
|
command.Env = append(command.Env, os.Environ()...)
|
||||||
gitcmd.Env = append(gitcmd.Env,
|
command.Env = append(command.Env,
|
||||||
repo_module.EnvRepoIsWiki+"="+strconv.FormatBool(results.IsWiki),
|
repo_module.EnvRepoIsWiki+"="+strconv.FormatBool(results.IsWiki),
|
||||||
repo_module.EnvRepoName+"="+results.RepoName,
|
repo_module.EnvRepoName+"="+results.RepoName,
|
||||||
repo_module.EnvRepoUsername+"="+results.OwnerName,
|
repo_module.EnvRepoUsername+"="+results.OwnerName,
|
||||||
@@ -369,16 +323,16 @@ func runServ(c *cli.Context) error {
|
|||||||
repo_module.EnvPusherEmail+"="+results.UserEmail,
|
repo_module.EnvPusherEmail+"="+results.UserEmail,
|
||||||
repo_module.EnvPusherID+"="+strconv.FormatInt(results.UserID, 10),
|
repo_module.EnvPusherID+"="+strconv.FormatInt(results.UserID, 10),
|
||||||
repo_module.EnvRepoID+"="+strconv.FormatInt(results.RepoID, 10),
|
repo_module.EnvRepoID+"="+strconv.FormatInt(results.RepoID, 10),
|
||||||
repo_module.EnvPRID+"="+fmt.Sprintf("%d", 0),
|
repo_module.EnvPRID+"="+strconv.Itoa(0),
|
||||||
repo_module.EnvDeployKeyID+"="+fmt.Sprintf("%d", results.DeployKeyID),
|
repo_module.EnvDeployKeyID+"="+strconv.FormatInt(results.DeployKeyID, 10),
|
||||||
repo_module.EnvKeyID+"="+fmt.Sprintf("%d", results.KeyID),
|
repo_module.EnvKeyID+"="+strconv.FormatInt(results.KeyID, 10),
|
||||||
repo_module.EnvAppURL+"="+setting.AppURL,
|
repo_module.EnvAppURL+"="+setting.AppURL,
|
||||||
)
|
)
|
||||||
// to avoid breaking, here only use the minimal environment variables for the "gitea serv" command.
|
// to avoid breaking, here only use the minimal environment variables for the "gitea serv" command.
|
||||||
// it could be re-considered whether to use the same git.CommonGitCmdEnvs() as "git" command later.
|
// it could be re-considered whether to use the same git.CommonGitCmdEnvs() as "git" command later.
|
||||||
gitcmd.Env = append(gitcmd.Env, git.CommonCmdServEnvs()...)
|
command.Env = append(command.Env, gitcmd.CommonCmdServEnvs()...)
|
||||||
|
|
||||||
if err = gitcmd.Run(); err != nil {
|
if err = command.Run(); err != nil {
|
||||||
return fail(ctx, "Failed to execute git command", "Failed to execute git command: %v", err)
|
return fail(ctx, "Failed to execute git command", "Failed to execute git command: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
58
cmd/web.go
58
cmd/web.go
@@ -12,20 +12,23 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
_ "net/http/pprof" // Used for debugging if enabled and a web server is running
|
_ "net/http/pprof" // Used for debugging if enabled and a web server is running
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"code.gitea.io/gitea/modules/graceful"
|
||||||
|
"code.gitea.io/gitea/modules/gtprof"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/process"
|
"code.gitea.io/gitea/modules/process"
|
||||||
"code.gitea.io/gitea/modules/public"
|
"code.gitea.io/gitea/modules/public"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/routers"
|
"code.gitea.io/gitea/routers"
|
||||||
"code.gitea.io/gitea/routers/install"
|
"code.gitea.io/gitea/routers/install"
|
||||||
|
|
||||||
"github.com/felixge/fgprof"
|
"github.com/felixge/fgprof"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PIDFile could be set from build tag
|
// PIDFile could be set from build tag
|
||||||
@@ -115,21 +118,31 @@ func showWebStartupMessage(msg string) {
|
|||||||
log.Info("* CustomPath: %s", setting.CustomPath)
|
log.Info("* CustomPath: %s", setting.CustomPath)
|
||||||
log.Info("* ConfigFile: %s", setting.CustomConf)
|
log.Info("* ConfigFile: %s", setting.CustomConf)
|
||||||
log.Info("%s", msg) // show startup message
|
log.Info("%s", msg) // show startup message
|
||||||
|
|
||||||
|
if setting.CORSConfig.Enabled {
|
||||||
|
log.Info("CORS Service Enabled")
|
||||||
|
}
|
||||||
|
if setting.DefaultUILocation != time.Local {
|
||||||
|
log.Info("Default UI Location is %v", setting.DefaultUILocation.String())
|
||||||
|
}
|
||||||
|
if setting.MailService != nil {
|
||||||
|
log.Info("Mail Service Enabled: RegisterEmailConfirm=%v, Service.EnableNotifyMail=%v", setting.Service.RegisterEmailConfirm, setting.Service.EnableNotifyMail)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveInstall(ctx *cli.Context) error {
|
func serveInstall(cmd *cli.Command) error {
|
||||||
showWebStartupMessage("Prepare to run install page")
|
showWebStartupMessage("Prepare to run install page")
|
||||||
|
|
||||||
routers.InitWebInstallPage(graceful.GetManager().HammerContext())
|
routers.InitWebInstallPage(graceful.GetManager().HammerContext())
|
||||||
|
|
||||||
// Flag for port number in case first time run conflict
|
// Flag for port number in case first time run conflict
|
||||||
if ctx.IsSet("port") {
|
if cmd.IsSet("port") {
|
||||||
if err := setPort(ctx.String("port")); err != nil {
|
if err := setPort(cmd.String("port")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ctx.IsSet("install-port") {
|
if cmd.IsSet("install-port") {
|
||||||
if err := setPort(ctx.String("install-port")); err != nil {
|
if err := setPort(cmd.String("install-port")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,7 +163,7 @@ func serveInstall(ctx *cli.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveInstalled(ctx *cli.Context) error {
|
func serveInstalled(c *cli.Command) error {
|
||||||
setting.InitCfgProvider(setting.CustomConf)
|
setting.InitCfgProvider(setting.CustomConf)
|
||||||
setting.LoadCommonSettings()
|
setting.LoadCommonSettings()
|
||||||
setting.MustInstalled()
|
setting.MustInstalled()
|
||||||
@@ -200,13 +213,19 @@ func serveInstalled(ctx *cli.Context) error {
|
|||||||
log.Fatal("Can not find APP_DATA_PATH %q", setting.AppDataPath)
|
log.Fatal("Can not find APP_DATA_PATH %q", setting.AppDataPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the AppDataTempDir is fully managed by us with a safe sub-path
|
||||||
|
// so it's safe to automatically remove the outdated files
|
||||||
|
setting.AppDataTempDir("").RemoveOutdated(3 * 24 * time.Hour)
|
||||||
|
|
||||||
// Override the provided port number within the configuration
|
// Override the provided port number within the configuration
|
||||||
if ctx.IsSet("port") {
|
if c.IsSet("port") {
|
||||||
if err := setPort(ctx.String("port")); err != nil {
|
if err := setPort(c.String("port")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gtprof.EnableBuiltinTracer(util.Iif(setting.IsProd, 2000*time.Millisecond, 100*time.Millisecond))
|
||||||
|
|
||||||
// Set up Chi routes
|
// Set up Chi routes
|
||||||
webRoutes := routers.NormalRoutes()
|
webRoutes := routers.NormalRoutes()
|
||||||
err := listen(webRoutes, true)
|
err := listen(webRoutes, true)
|
||||||
@@ -217,22 +236,27 @@ func serveInstalled(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func servePprof() {
|
func servePprof() {
|
||||||
|
// FIXME: it shouldn't use the global DefaultServeMux, and it should use a proper context
|
||||||
http.DefaultServeMux.Handle("/debug/fgprof", fgprof.Handler())
|
http.DefaultServeMux.Handle("/debug/fgprof", fgprof.Handler())
|
||||||
_, _, finished := process.GetManager().AddTypedContext(context.Background(), "Web: PProf Server", process.SystemProcessType, true)
|
_, _, finished := process.GetManager().AddTypedContext(context.TODO(), "Web: PProf Server", process.SystemProcessType, true)
|
||||||
// The pprof server is for debug purpose only, it shouldn't be exposed on public network. At the moment it's not worth to introduce a configurable option for it.
|
// The pprof server is for debug purpose only, it shouldn't be exposed on public network. At the moment, it's not worth introducing a configurable option for it.
|
||||||
log.Info("Starting pprof server on localhost:6060")
|
log.Info("Starting pprof server on localhost:6060")
|
||||||
log.Info("Stopped pprof server: %v", http.ListenAndServe("localhost:6060", nil))
|
log.Info("Stopped pprof server: %v", http.ListenAndServe("localhost:6060", nil))
|
||||||
finished()
|
finished()
|
||||||
}
|
}
|
||||||
|
|
||||||
func runWeb(ctx *cli.Context) error {
|
func runWeb(ctx context.Context, cmd *cli.Command) error {
|
||||||
defer func() {
|
defer func() {
|
||||||
if panicked := recover(); panicked != nil {
|
if panicked := recover(); panicked != nil {
|
||||||
log.Fatal("PANIC: %v\n%s", panicked, log.Stack(2))
|
log.Fatal("PANIC: %v\n%s", panicked, log.Stack(2))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
managerCtx, cancel := context.WithCancel(context.Background())
|
if subCmdName, valid := isValidDefaultSubCommand(cmd); !valid {
|
||||||
|
return fmt.Errorf("unknown command: %s", subCmdName)
|
||||||
|
}
|
||||||
|
|
||||||
|
managerCtx, cancel := context.WithCancel(ctx)
|
||||||
graceful.InitManager(managerCtx)
|
graceful.InitManager(managerCtx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@@ -243,12 +267,12 @@ func runWeb(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set pid file setting
|
// Set pid file setting
|
||||||
if ctx.IsSet("pid") {
|
if cmd.IsSet("pid") {
|
||||||
createPIDFile(ctx.String("pid"))
|
createPIDFile(cmd.String("pid"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !setting.InstallLock {
|
if !setting.InstallLock {
|
||||||
if err := serveInstall(ctx); err != nil {
|
if err := serveInstall(cmd); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -259,7 +283,7 @@ func runWeb(ctx *cli.Context) error {
|
|||||||
go servePprof()
|
go servePprof()
|
||||||
}
|
}
|
||||||
|
|
||||||
return serveInstalled(ctx)
|
return serveInstalled(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setPort(port string) error {
|
func setPort(port string) error {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/process"
|
"code.gitea.io/gitea/modules/process"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
)
|
)
|
||||||
@@ -54,8 +55,6 @@ func runACME(listenAddr string, m http.Handler) error {
|
|||||||
altTLSALPNPort = p
|
altTLSALPNPort = p
|
||||||
}
|
}
|
||||||
|
|
||||||
magic := certmagic.NewDefault()
|
|
||||||
magic.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
|
|
||||||
// Try to use private CA root if provided, otherwise defaults to system's trust
|
// Try to use private CA root if provided, otherwise defaults to system's trust
|
||||||
var certPool *x509.CertPool
|
var certPool *x509.CertPool
|
||||||
if setting.AcmeCARoot != "" {
|
if setting.AcmeCARoot != "" {
|
||||||
@@ -65,8 +64,20 @@ func runACME(listenAddr string, m http.Handler) error {
|
|||||||
log.Warn("Failed to parse CA Root certificate, using default CA trust: %v", err)
|
log.Warn("Failed to parse CA Root certificate, using default CA trust: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
myACME := certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{
|
// FIXME: this path is not right, it uses "AppWorkPath" incorrectly, and writes the data into "AppWorkPath/https"
|
||||||
CA: setting.AcmeURL,
|
// Ideally it should migrate to AppDataPath write to "AppDataPath/https"
|
||||||
|
// And one more thing, no idea why we should set the global default variables here
|
||||||
|
// But it seems that the current ACME code needs these global variables to make renew work.
|
||||||
|
// Otherwise, "renew" will use incorrect storage path
|
||||||
|
oldDefaultACME := certmagic.DefaultACME
|
||||||
|
certmagic.Default.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
|
||||||
|
certmagic.DefaultACME = certmagic.ACMEIssuer{
|
||||||
|
// try to use the default values provided by DefaultACME
|
||||||
|
CA: util.IfZero(setting.AcmeURL, oldDefaultACME.CA),
|
||||||
|
TestCA: oldDefaultACME.TestCA,
|
||||||
|
Logger: oldDefaultACME.Logger,
|
||||||
|
HTTPProxy: oldDefaultACME.HTTPProxy,
|
||||||
|
|
||||||
TrustedRoots: certPool,
|
TrustedRoots: certPool,
|
||||||
Email: setting.AcmeEmail,
|
Email: setting.AcmeEmail,
|
||||||
Agreed: setting.AcmeTOS,
|
Agreed: setting.AcmeTOS,
|
||||||
@@ -75,8 +86,10 @@ func runACME(listenAddr string, m http.Handler) error {
|
|||||||
ListenHost: setting.HTTPAddr,
|
ListenHost: setting.HTTPAddr,
|
||||||
AltTLSALPNPort: altTLSALPNPort,
|
AltTLSALPNPort: altTLSALPNPort,
|
||||||
AltHTTPPort: altHTTPPort,
|
AltHTTPPort: altHTTPPort,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
magic := certmagic.NewDefault()
|
||||||
|
myACME := certmagic.NewACMEIssuer(magic, certmagic.DefaultACME)
|
||||||
magic.Issuers = []certmagic.Issuer{myACME}
|
magic.Issuers = []certmagic.Issuer{myACME}
|
||||||
|
|
||||||
// this obtains certificates or renews them if necessary
|
// this obtains certificates or renews them if necessary
|
||||||
@@ -123,7 +136,7 @@ func runACME(listenAddr string, m http.Handler) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) {
|
func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != "GET" && r.Method != "HEAD" {
|
if r.Method != http.MethodGet && r.Method != http.MethodHead {
|
||||||
http.Error(w, "Use HTTPS", http.StatusBadRequest)
|
http.Error(w, "Use HTTPS", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,12 +23,6 @@ func NoHTTPRedirector() {
|
|||||||
graceful.GetManager().InformCleanup()
|
graceful.GetManager().InformCleanup()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NoMainListener tells our cleanup routine that we will not be using a possibly provided listener
|
|
||||||
// for our main HTTP/HTTPS service
|
|
||||||
func NoMainListener() {
|
|
||||||
graceful.GetManager().InformCleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NoInstallListener tells our cleanup routine that we will not be using a possibly provided listener
|
// NoInstallListener tells our cleanup routine that we will not be using a possibly provided listener
|
||||||
// for our install HTTP/HTTPS service
|
// for our install HTTP/HTTPS service
|
||||||
func NoInstallListener() {
|
func NoInstallListener() {
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
Bash and Zsh completion
|
|
||||||
=======================
|
|
||||||
|
|
||||||
From within the gitea root run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
source contrib/autocompletion/bash_autocomplete
|
|
||||||
```
|
|
||||||
|
|
||||||
or for zsh run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
source contrib/autocompletion/zsh_autocomplete
|
|
||||||
```
|
|
||||||
|
|
||||||
These scripts will check if gitea is on the path and if so add autocompletion for `gitea`. Or if not autocompletion will work for `./gitea`.
|
|
||||||
If gitea has been installed as a different program pass in the `PROG` environment variable to set the correct program name.
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#! /bin/bash
|
|
||||||
# Heavily inspired by https://github.com/urfave/cli
|
|
||||||
|
|
||||||
_cli_bash_autocomplete() {
|
|
||||||
if [[ "${COMP_WORDS[0]}" != "source" ]]; then
|
|
||||||
local cur opts base
|
|
||||||
COMPREPLY=()
|
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
|
||||||
if [[ "$cur" == "-"* ]]; then
|
|
||||||
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion )
|
|
||||||
else
|
|
||||||
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
|
|
||||||
fi
|
|
||||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ -z "$PROG" ] && [ ! "$(command -v gitea &> /dev/null)" ] ; then
|
|
||||||
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete gitea
|
|
||||||
elif [ -z "$PROG" ]; then
|
|
||||||
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete ./gitea
|
|
||||||
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete "$PWD/gitea"
|
|
||||||
else
|
|
||||||
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete "$PROG"
|
|
||||||
unset PROG
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#compdef ${PROG:=gitea}
|
|
||||||
|
|
||||||
|
|
||||||
# Heavily inspired by https://github.com/urfave/cli
|
|
||||||
|
|
||||||
_cli_zsh_autocomplete() {
|
|
||||||
|
|
||||||
local -a opts
|
|
||||||
local cur
|
|
||||||
cur=${words[-1]}
|
|
||||||
if [[ "$cur" == "-"* ]]; then
|
|
||||||
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}")
|
|
||||||
else
|
|
||||||
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${opts[1]}" != "" ]]; then
|
|
||||||
_describe 'values' opts
|
|
||||||
else
|
|
||||||
_files
|
|
||||||
fi
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ -z $PROG ] ; then
|
|
||||||
compdef _cli_zsh_autocomplete gitea
|
|
||||||
else
|
|
||||||
compdef _cli_zsh_autocomplete $(basename $PROG)
|
|
||||||
fi
|
|
||||||
@@ -11,7 +11,7 @@ The default version will read from `docs/config.yml`. You can override this
|
|||||||
using the option `--version`.
|
using the option `--version`.
|
||||||
|
|
||||||
The upstream branches will be fetched, using the remote `origin`. This can
|
The upstream branches will be fetched, using the remote `origin`. This can
|
||||||
be overrided using `--upstream`, and fetching can be avoided using
|
be overridden using `--upstream`, and fetching can be avoided using
|
||||||
`--no-fetch`.
|
`--no-fetch`.
|
||||||
|
|
||||||
By default the branch created will be called `backport-$PR-$VERSION`. You
|
By default the branch created will be called `backport-$PR-$VERSION`. You
|
||||||
|
|||||||
@@ -1,31 +1,30 @@
|
|||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//nolint:forbidigo
|
//nolint:forbidigo // use of print functions is allowed in cli
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/google/go-github/v61/github"
|
"github.com/google/go-github/v74/github"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultVersion = "v1.18" // to backport to
|
const defaultVersion = "v1.18" // to backport to
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := cli.NewApp()
|
app := &cli.Command{}
|
||||||
app.Name = "backport"
|
app.Name = "backport"
|
||||||
app.Usage = "Backport provided PR-number on to the current or previous released version"
|
app.Usage = "Backport provided PR-number on to the current or previous released version"
|
||||||
app.Description = `Backport will look-up the PR in Gitea's git log and attempt to cherry-pick it on the current version`
|
app.Description = `Backport will look-up the PR in Gitea's git log and attempt to cherry-pick it on the current version`
|
||||||
@@ -90,7 +89,7 @@ func main() {
|
|||||||
Usage: "Set this flag to continue from a git cherry-pick that has broken",
|
Usage: "Set this flag to continue from a git cherry-pick that has broken",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cli.AppHelpTemplate = `NAME:
|
cli.RootCommandHelpTemplate = `NAME:
|
||||||
{{.Name}} - {{.Usage}}
|
{{.Name}} - {{.Usage}}
|
||||||
USAGE:
|
USAGE:
|
||||||
{{.HelpName}} {{if .VisibleFlags}}[options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
|
{{.HelpName}} {{if .VisibleFlags}}[options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
|
||||||
@@ -104,16 +103,12 @@ OPTIONS:
|
|||||||
`
|
`
|
||||||
|
|
||||||
app.Action = runBackport
|
app.Action = runBackport
|
||||||
|
if err := app.Run(context.Background(), os.Args); err != nil {
|
||||||
if err := app.Run(os.Args); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Unable to backport: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Unable to backport: %v\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runBackport(c *cli.Context) error {
|
func runBackport(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
continuing := c.Bool("continue")
|
continuing := c.Bool("continue")
|
||||||
|
|
||||||
var pr string
|
var pr string
|
||||||
@@ -158,7 +153,7 @@ func runBackport(c *cli.Context) error {
|
|||||||
|
|
||||||
args := c.Args().Slice()
|
args := c.Args().Slice()
|
||||||
if len(args) == 0 && pr == "" {
|
if len(args) == 0 && pr == "" {
|
||||||
return fmt.Errorf("no PR number provided\nProvide a PR number to backport")
|
return errors.New("no PR number provided\nProvide a PR number to backport")
|
||||||
} else if len(args) != 1 && pr == "" {
|
} else if len(args) != 1 && pr == "" {
|
||||||
return fmt.Errorf("multiple PRs provided %v\nOnly a single PR can be backported at a time", args)
|
return fmt.Errorf("multiple PRs provided %v\nOnly a single PR can be backported at a time", args)
|
||||||
}
|
}
|
||||||
@@ -342,8 +337,8 @@ func determineRemote(ctx context.Context, forkUser string) (string, string, erro
|
|||||||
fmt.Fprintf(os.Stderr, "Unable to list git remotes:\n%s\n", string(out))
|
fmt.Fprintf(os.Stderr, "Unable to list git remotes:\n%s\n", string(out))
|
||||||
return "", "", fmt.Errorf("unable to determine forked remote: %w", err)
|
return "", "", fmt.Errorf("unable to determine forked remote: %w", err)
|
||||||
}
|
}
|
||||||
lines := strings.Split(string(out), "\n")
|
lines := strings.SplitSeq(string(out), "\n")
|
||||||
for _, line := range lines {
|
for line := range lines {
|
||||||
fields := strings.Split(line, "\t")
|
fields := strings.Split(line, "\t")
|
||||||
name, remote := fields[0], fields[1]
|
name, remote := fields[0], fields[1]
|
||||||
// only look at pushers
|
// only look at pushers
|
||||||
@@ -361,12 +356,12 @@ func determineRemote(ctx context.Context, forkUser string) (string, string, erro
|
|||||||
if !strings.Contains(remote, forkUser) {
|
if !strings.Contains(remote, forkUser) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(remote, "git@github.com:") {
|
if after, ok := strings.CutPrefix(remote, "git@github.com:"); ok {
|
||||||
forkUser = strings.TrimPrefix(remote, "git@github.com:")
|
forkUser = after
|
||||||
} else if strings.HasPrefix(remote, "https://github.com/") {
|
} else if after, ok := strings.CutPrefix(remote, "https://github.com/"); ok {
|
||||||
forkUser = strings.TrimPrefix(remote, "https://github.com/")
|
forkUser = after
|
||||||
} else if strings.HasPrefix(remote, "https://www.github.com/") {
|
} else if after, ok := strings.CutPrefix(remote, "https://www.github.com/"); ok {
|
||||||
forkUser = strings.TrimPrefix(remote, "https://www.github.com/")
|
forkUser = after
|
||||||
} else if forkUser == "" {
|
} else if forkUser == "" {
|
||||||
return "", "", fmt.Errorf("unable to extract forkUser from remote %s: %s", name, remote)
|
return "", "", fmt.Errorf("unable to extract forkUser from remote %s: %s", name, remote)
|
||||||
}
|
}
|
||||||
@@ -459,25 +454,3 @@ func determineSHAforPR(ctx context.Context, prStr, accessToken string) (string,
|
|||||||
|
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func installSignals() (context.Context, context.CancelFunc) {
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
go func() {
|
|
||||||
// install notify
|
|
||||||
signalChannel := make(chan os.Signal, 1)
|
|
||||||
|
|
||||||
signal.Notify(
|
|
||||||
signalChannel,
|
|
||||||
syscall.SIGINT,
|
|
||||||
syscall.SIGTERM,
|
|
||||||
)
|
|
||||||
select {
|
|
||||||
case <-signalChannel:
|
|
||||||
case <-ctx.Done():
|
|
||||||
}
|
|
||||||
cancel()
|
|
||||||
signal.Reset()
|
|
||||||
}()
|
|
||||||
|
|
||||||
return ctx, cancel
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,16 +4,17 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := cli.NewApp()
|
app := cli.Command{}
|
||||||
app.Name = "environment-to-ini"
|
app.Name = "environment-to-ini"
|
||||||
app.Usage = "Use provided environment to update configuration ini"
|
app.Usage = "Use provided environment to update configuration ini"
|
||||||
app.Description = `As a helper to allow docker users to update the gitea configuration
|
app.Description = `As a helper to allow docker users to update the gitea configuration
|
||||||
@@ -72,13 +73,13 @@ func main() {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
app.Action = runEnvironmentToIni
|
app.Action = runEnvironmentToIni
|
||||||
err := app.Run(os.Args)
|
err := app.Run(context.Background(), os.Args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Failed to run app with %s: %v", os.Args, err)
|
log.Fatal("Failed to run app with %s: %v", os.Args, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runEnvironmentToIni(c *cli.Context) error {
|
func runEnvironmentToIni(_ context.Context, c *cli.Command) error {
|
||||||
// the config system may change the environment variables, so get a copy first, to be used later
|
// the config system may change the environment variables, so get a copy first, to be used later
|
||||||
env := append([]string{}, os.Environ()...)
|
env := append([]string{}, os.Environ()...)
|
||||||
setting.InitWorkPathAndCfgProvider(os.Getenv, setting.ArgWorkPathAndCustomConf{
|
setting.InitWorkPathAndCfgProvider(os.Getenv, setting.ArgWorkPathAndCustomConf{
|
||||||
|
|||||||
@@ -150,7 +150,7 @@
|
|||||||
|
|
||||||
<p>In general, Your Gitea Instance retains User Personal Information for as long as your account is active, or as needed to provide you service.</p>
|
<p>In general, Your Gitea Instance retains User Personal Information for as long as your account is active, or as needed to provide you service.</p>
|
||||||
|
|
||||||
<p>If you would like to cancel your account or delete your User Personal Information, you may do so in your user profile. We retain and use your information as necessary to comply with our legal obligations, resolve disputes, and enforce our agreements, but barring legal requirements, we will delete your full profile (within reason) within 90 days of your request. Feel free to contact our support to request erasure of the data we process on the bassis of consent within 30 days.</p>
|
<p>If you would like to cancel your account or delete your User Personal Information, you may do so in your user profile. We retain and use your information as necessary to comply with our legal obligations, resolve disputes, and enforce our agreements, but barring legal requirements, we will delete your full profile (within reason) within 90 days of your request. Feel free to contact our support to request erasure of the data we process on the basis of consent within 30 days.</p>
|
||||||
|
|
||||||
<p>After an account has been deleted, certain data, such as contributions to other Users' repositories and comments in others' issues, will remain. However, we will delete or de-identify your User Personal Information, including your username and email address, from the author field of issues, pull requests, and comments by associating them with a ghost user.</p>
|
<p>After an account has been deleted, certain data, such as contributions to other Users' repositories and comments in others' issues, will remain. However, we will delete or de-identify your User Personal Information, including your username and email address, from the author field of issues, pull requests, and comments by associating them with a ghost user.</p>
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ fi
|
|||||||
# confirm update
|
# confirm update
|
||||||
echo "Checking currently installed version..."
|
echo "Checking currently installed version..."
|
||||||
current=$(giteacmd --version | cut -d ' ' -f 3)
|
current=$(giteacmd --version | cut -d ' ' -f 3)
|
||||||
[[ "$current" == "$giteaversion" ]] && echo "$current is already installed, stopping." && exit 1
|
[[ "$current" == "$giteaversion" ]] && echo "$current is already installed, stopping." && exit 0
|
||||||
if [[ -z "${no_confirm:-}" ]]; then
|
if [[ -z "${no_confirm:-}" ]]; then
|
||||||
echo "Make sure to read the changelog first: https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md"
|
echo "Make sure to read the changelog first: https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md"
|
||||||
echo "Are you ready to update Gitea from ${current} to ${giteaversion}? (y/N)"
|
echo "Are you ready to update Gitea from ${current} to ${giteaversion}? (y/N)"
|
||||||
|
|||||||
@@ -59,27 +59,22 @@ RUN_USER = ; git
|
|||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;
|
;;
|
||||||
;; The protocol the server listens on. One of 'http', 'https', 'http+unix', 'fcgi' or 'fcgi+unix'. Defaults to 'http'
|
;; The protocol the server listens on. One of "http", "https", "http+unix", "fcgi" or "fcgi+unix".
|
||||||
;; Note: Value must be lowercase.
|
|
||||||
;PROTOCOL = http
|
;PROTOCOL = http
|
||||||
;;
|
;;
|
||||||
;; Expect PROXY protocol headers on connections
|
;; Set the domain for the server.
|
||||||
;USE_PROXY_PROTOCOL = false
|
|
||||||
;;
|
|
||||||
;; Use PROXY protocol in TLS Bridging mode
|
|
||||||
;PROXY_PROTOCOL_TLS_BRIDGING = false
|
|
||||||
;;
|
|
||||||
; Timeout to wait for PROXY protocol header (set to 0 to have no timeout)
|
|
||||||
;PROXY_PROTOCOL_HEADER_TIMEOUT=5s
|
|
||||||
;;
|
|
||||||
; Accept PROXY protocol headers with UNKNOWN type
|
|
||||||
;PROXY_PROTOCOL_ACCEPT_UNKNOWN=false
|
|
||||||
;;
|
|
||||||
;; Set the domain for the server
|
|
||||||
;DOMAIN = localhost
|
;DOMAIN = localhost
|
||||||
;;
|
;;
|
||||||
;; Overwrite the automatically generated public URL. Necessary for proxies and docker.
|
;; The AppURL is used to generate public URL links, defaults to "{PROTOCOL}://{DOMAIN}:{HTTP_PORT}/".
|
||||||
;ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
|
;; Most users should set it to the real website URL of their Gitea instance when there is a reverse proxy.
|
||||||
|
;ROOT_URL =
|
||||||
|
;;
|
||||||
|
;; Controls how to detect the public URL.
|
||||||
|
;; Although it defaults to "legacy" (to avoid breaking existing users), most instances should use the "auto" behavior,
|
||||||
|
;; especially when the Gitea instance needs to be accessed in a container network.
|
||||||
|
;; * legacy: detect the public URL from "Host" header if "X-Forwarded-Proto" header exists, otherwise use "ROOT_URL".
|
||||||
|
;; * auto: always use "Host" header, and also use "X-Forwarded-Proto" header if it exists. If no "Host" header, use "ROOT_URL".
|
||||||
|
;PUBLIC_URL_DETECTION = legacy
|
||||||
;;
|
;;
|
||||||
;; For development purpose only. It makes Gitea handle sub-path ("/sub-path/owner/repo/...") directly when debugging without a reverse proxy.
|
;; For development purpose only. It makes Gitea handle sub-path ("/sub-path/owner/repo/...") directly when debugging without a reverse proxy.
|
||||||
;; DO NOT USE IT IN PRODUCTION!!!
|
;; DO NOT USE IT IN PRODUCTION!!!
|
||||||
@@ -89,13 +84,25 @@ RUN_USER = ; git
|
|||||||
;STATIC_URL_PREFIX =
|
;STATIC_URL_PREFIX =
|
||||||
;;
|
;;
|
||||||
;; The address to listen on. Either a IPv4/IPv6 address or the path to a unix socket.
|
;; The address to listen on. Either a IPv4/IPv6 address or the path to a unix socket.
|
||||||
;; If PROTOCOL is set to `http+unix` or `fcgi+unix`, this should be the name of the Unix socket file to use.
|
;; If PROTOCOL is set to "http+unix" or "fcgi+unix", this should be the name of the Unix socket file to use.
|
||||||
;; Relative paths will be made absolute against the _`AppWorkPath`_.
|
;; Relative paths will be made absolute against the _`AppWorkPath`_.
|
||||||
;HTTP_ADDR = 0.0.0.0
|
;HTTP_ADDR = 0.0.0.0
|
||||||
;;
|
;;
|
||||||
;; The port to listen on. Leave empty when using a unix socket.
|
;; The port to listen on for "http" or "https" protocol. Leave empty when using a unix socket.
|
||||||
;HTTP_PORT = 3000
|
;HTTP_PORT = 3000
|
||||||
;;
|
;;
|
||||||
|
;; Expect PROXY protocol headers on connections
|
||||||
|
;USE_PROXY_PROTOCOL = false
|
||||||
|
;;
|
||||||
|
;; Use PROXY protocol in TLS Bridging mode
|
||||||
|
;PROXY_PROTOCOL_TLS_BRIDGING = false
|
||||||
|
;;
|
||||||
|
;; Timeout to wait for PROXY protocol header (set to 0 to have no timeout)
|
||||||
|
;PROXY_PROTOCOL_HEADER_TIMEOUT = 5s
|
||||||
|
;;
|
||||||
|
;; Accept PROXY protocol headers with UNKNOWN type
|
||||||
|
;PROXY_PROTOCOL_ACCEPT_UNKNOWN = false
|
||||||
|
;;
|
||||||
;; If REDIRECT_OTHER_PORT is true, and PROTOCOL is set to https an http server
|
;; If REDIRECT_OTHER_PORT is true, and PROTOCOL is set to https an http server
|
||||||
;; will be started on PORT_TO_REDIRECT and it will redirect plain, non-secure http requests to the main
|
;; will be started on PORT_TO_REDIRECT and it will redirect plain, non-secure http requests to the main
|
||||||
;; ROOT_URL. Defaults are false for REDIRECT_OTHER_PORT and 80 for
|
;; ROOT_URL. Defaults are false for REDIRECT_OTHER_PORT and 80 for
|
||||||
@@ -103,8 +110,8 @@ RUN_USER = ; git
|
|||||||
;REDIRECT_OTHER_PORT = false
|
;REDIRECT_OTHER_PORT = false
|
||||||
;PORT_TO_REDIRECT = 80
|
;PORT_TO_REDIRECT = 80
|
||||||
;;
|
;;
|
||||||
;; expect PROXY protocol header on connections to https redirector.
|
;; expect PROXY protocol header on connections to https redirector, defaults to USE_PROXY_PROTOCOL
|
||||||
;REDIRECTOR_USE_PROXY_PROTOCOL = %(USE_PROXY_PROTOCOL)s
|
;REDIRECTOR_USE_PROXY_PROTOCOL =
|
||||||
;; Minimum and maximum supported TLS versions
|
;; Minimum and maximum supported TLS versions
|
||||||
;SSL_MIN_VERSION=TLSv1.2
|
;SSL_MIN_VERSION=TLSv1.2
|
||||||
;SSL_MAX_VERSION=
|
;SSL_MAX_VERSION=
|
||||||
@@ -128,13 +135,14 @@ RUN_USER = ; git
|
|||||||
;; most cases you do not need to change the default value. Alter it only if
|
;; most cases you do not need to change the default value. Alter it only if
|
||||||
;; your SSH server node is not the same as HTTP node. For different protocol, the default
|
;; your SSH server node is not the same as HTTP node. For different protocol, the default
|
||||||
;; values are different. If `PROTOCOL` is `http+unix`, the default value is `http://unix/`.
|
;; values are different. If `PROTOCOL` is `http+unix`, the default value is `http://unix/`.
|
||||||
;; If `PROTOCOL` is `fcgi` or `fcgi+unix`, the default value is `%(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/`.
|
;; If `PROTOCOL` is `fcgi` or `fcgi+unix`, the default value is `{PROTOCOL}://{HTTP_ADDR}:{HTTP_PORT}/`.
|
||||||
;; If listen on `0.0.0.0`, the default value is `%(PROTOCOL)s://localhost:%(HTTP_PORT)s/`, Otherwise the default
|
;; If listen on `0.0.0.0`, the default value is `{PROTOCOL}://localhost:{HTTP_PORT}/`.
|
||||||
;; value is `%(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/`.
|
;; Otherwise the default value is `{PROTOCOL}://{HTTP_ADDR}:{HTTP_PORT}/`.
|
||||||
;LOCAL_ROOT_URL = %(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/
|
;; Most users don't need (and shouldn't) set this value.
|
||||||
|
;LOCAL_ROOT_URL =
|
||||||
;;
|
;;
|
||||||
;; When making local connections pass the PROXY protocol header.
|
;; When making local connections pass the PROXY protocol header, defaults to USE_PROXY_PROTOCOL
|
||||||
;LOCAL_USE_PROXY_PROTOCOL = %(USE_PROXY_PROTOCOL)s
|
;LOCAL_USE_PROXY_PROTOCOL =
|
||||||
;;
|
;;
|
||||||
;; Disable SSH feature when not available
|
;; Disable SSH feature when not available
|
||||||
;DISABLE_SSH = false
|
;DISABLE_SSH = false
|
||||||
@@ -146,13 +154,17 @@ RUN_USER = ; git
|
|||||||
;SSH_SERVER_USE_PROXY_PROTOCOL = false
|
;SSH_SERVER_USE_PROXY_PROTOCOL = false
|
||||||
;;
|
;;
|
||||||
;; Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER.
|
;; Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER.
|
||||||
;BUILTIN_SSH_SERVER_USER = %(RUN_USER)s
|
;BUILTIN_SSH_SERVER_USER =
|
||||||
;;
|
;;
|
||||||
;; Domain name to be exposed in clone URL
|
;; Domain name to be exposed in clone URL, defaults to DOMAIN or the domain part of ROOT_URL
|
||||||
;SSH_DOMAIN = %(DOMAIN)s
|
;SSH_DOMAIN =
|
||||||
;;
|
;;
|
||||||
;; SSH username displayed in clone URLs.
|
;; SSH username displayed in clone URLs. It defaults to BUILTIN_SSH_SERVER_USER or RUN_USER.
|
||||||
;SSH_USER = %(BUILTIN_SSH_SERVER_USER)s
|
;; If it is set to "(DOER_USERNAME)", it will use current signed-in user's username.
|
||||||
|
;; This option is only for some advanced users who have configured their SSH reverse-proxy
|
||||||
|
;; and need to use different usernames for git SSH clone.
|
||||||
|
;; Most users should just leave it blank.
|
||||||
|
;SSH_USER =
|
||||||
;;
|
;;
|
||||||
;; The network interface the builtin SSH server should listen on
|
;; The network interface the builtin SSH server should listen on
|
||||||
;SSH_LISTEN_HOST =
|
;SSH_LISTEN_HOST =
|
||||||
@@ -160,8 +172,8 @@ RUN_USER = ; git
|
|||||||
;; Port number to be exposed in clone URL
|
;; Port number to be exposed in clone URL
|
||||||
;SSH_PORT = 22
|
;SSH_PORT = 22
|
||||||
;;
|
;;
|
||||||
;; The port number the builtin SSH server should listen on
|
;; The port number the builtin SSH server should listen on, defaults to SSH_PORT
|
||||||
;SSH_LISTEN_PORT = %(SSH_PORT)s
|
;SSH_LISTEN_PORT =
|
||||||
;;
|
;;
|
||||||
;; Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'.
|
;; Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'.
|
||||||
;SSH_ROOT_PATH =
|
;SSH_ROOT_PATH =
|
||||||
@@ -174,30 +186,19 @@ RUN_USER = ; git
|
|||||||
;; If you intend to use the AuthorizedPrincipalsCommand functionality then you should turn this off.
|
;; If you intend to use the AuthorizedPrincipalsCommand functionality then you should turn this off.
|
||||||
;SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE = true
|
;SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE = true
|
||||||
;;
|
;;
|
||||||
;; For the built-in SSH server, choose the ciphers to support for SSH connections,
|
;; For the builtin SSH server, choose the supported ciphers/key-exchange-algorithms/MACs for SSH connections.
|
||||||
;; for system SSH this setting has no effect
|
;; The supported names are listed in https://github.com/golang/crypto/blob/master/ssh/common.go.
|
||||||
;SSH_SERVER_CIPHERS = chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com
|
;; Leave them empty to use the Golang crypto's recommended default values.
|
||||||
;;
|
;; For system SSH (non-builtin SSH server), this setting has no effect.
|
||||||
;; For the built-in SSH server, choose the key exchange algorithms to support for SSH connections,
|
;SSH_SERVER_CIPHERS =
|
||||||
;; for system SSH this setting has no effect
|
;SSH_SERVER_KEY_EXCHANGES =
|
||||||
;SSH_SERVER_KEY_EXCHANGES = curve25519-sha256, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1
|
;SSH_SERVER_MACS =
|
||||||
;;
|
|
||||||
;; For the built-in SSH server, choose the MACs to support for SSH connections,
|
|
||||||
;; for system SSH this setting has no effect
|
|
||||||
;SSH_SERVER_MACS = hmac-sha2-256-etm@openssh.com, hmac-sha2-256, hmac-sha1
|
|
||||||
;;
|
;;
|
||||||
;; For the built-in SSH server, choose the keypair to offer as the host key
|
;; For the built-in SSH server, choose the keypair to offer as the host key
|
||||||
;; The private key should be at SSH_SERVER_HOST_KEY and the public SSH_SERVER_HOST_KEY.pub
|
;; The private key should be at SSH_SERVER_HOST_KEY and the public SSH_SERVER_HOST_KEY.pub
|
||||||
;; relative paths are made absolute relative to the %(APP_DATA_PATH)s
|
;; relative paths are made absolute relative to the APP_DATA_PATH
|
||||||
;SSH_SERVER_HOST_KEYS=ssh/gitea.rsa, ssh/gogs.rsa
|
;SSH_SERVER_HOST_KEYS=ssh/gitea.rsa, ssh/gogs.rsa
|
||||||
;;
|
;;
|
||||||
;; Directory to create temporary files in when testing public keys using ssh-keygen,
|
|
||||||
;; default is the system temporary directory.
|
|
||||||
;SSH_KEY_TEST_PATH =
|
|
||||||
;;
|
|
||||||
;; Use `ssh-keygen` to parse public SSH keys. The value is passed to the shell. By default, Gitea does the parsing itself.
|
|
||||||
;SSH_KEYGEN_PATH =
|
|
||||||
;;
|
|
||||||
;; Enable SSH Authorized Key Backup when rewriting all keys, default is false
|
;; Enable SSH Authorized Key Backup when rewriting all keys, default is false
|
||||||
;SSH_AUTHORIZED_KEYS_BACKUP = false
|
;SSH_AUTHORIZED_KEYS_BACKUP = false
|
||||||
;;
|
;;
|
||||||
@@ -288,6 +289,9 @@ RUN_USER = ; git
|
|||||||
;; Default path for App data
|
;; Default path for App data
|
||||||
;APP_DATA_PATH = data ; relative paths will be made absolute with _`AppWorkPath`_
|
;APP_DATA_PATH = data ; relative paths will be made absolute with _`AppWorkPath`_
|
||||||
;;
|
;;
|
||||||
|
;; Base path for App's temp files, leave empty to use the managed tmp directory in APP_DATA_PATH
|
||||||
|
;APP_TEMP_PATH =
|
||||||
|
;;
|
||||||
;; Enable gzip compression for runtime-generated content, static resources excluded
|
;; Enable gzip compression for runtime-generated content, static resources excluded
|
||||||
;ENABLE_GZIP = false
|
;ENABLE_GZIP = false
|
||||||
;;
|
;;
|
||||||
@@ -516,6 +520,10 @@ INTERNAL_TOKEN =
|
|||||||
;;
|
;;
|
||||||
;; On user registration, record the IP address and user agent of the user to help identify potential abuse.
|
;; On user registration, record the IP address and user agent of the user to help identify potential abuse.
|
||||||
;; RECORD_USER_SIGNUP_METADATA = false
|
;; RECORD_USER_SIGNUP_METADATA = false
|
||||||
|
;;
|
||||||
|
;; Set the two-factor auth behavior.
|
||||||
|
;; Set to "enforced", to force users to enroll into Two-Factor Authentication, users without 2FA have no access to repositories via API or web.
|
||||||
|
;TWO_FACTOR_AUTH =
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
@@ -559,6 +567,11 @@ ENABLED = true
|
|||||||
;; Alternative location to specify OAuth2 authentication secret. You cannot specify both this and JWT_SECRET, and must pick one
|
;; Alternative location to specify OAuth2 authentication secret. You cannot specify both this and JWT_SECRET, and must pick one
|
||||||
;JWT_SECRET_URI = file:/etc/gitea/oauth2_jwt_secret
|
;JWT_SECRET_URI = file:/etc/gitea/oauth2_jwt_secret
|
||||||
;;
|
;;
|
||||||
|
;; The "issuer" claim identifies the principal that issued the JWT.
|
||||||
|
;; Gitea 1.25 makes it default to "ROOT_URL without the last slash" to follow the standard.
|
||||||
|
;; If you have old logins from before 1.25, you may want to set it to the old (non-standard) value "ROOT_URL with the last slash".
|
||||||
|
;JWT_CLAIM_ISSUER =
|
||||||
|
;;
|
||||||
;; Lifetime of an OAuth2 access token in seconds
|
;; Lifetime of an OAuth2 access token in seconds
|
||||||
;ACCESS_TOKEN_EXPIRATION_TIME = 3600
|
;ACCESS_TOKEN_EXPIRATION_TIME = 3600
|
||||||
;;
|
;;
|
||||||
@@ -582,7 +595,7 @@ ENABLED = true
|
|||||||
[log]
|
[log]
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Root path for the log files - defaults to %(GITEA_WORK_DIR)/log
|
;; Root path for the log files - defaults to "{AppWorkPath}/log"
|
||||||
;ROOT_PATH =
|
;ROOT_PATH =
|
||||||
;;
|
;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
@@ -682,8 +695,8 @@ LEVEL = Info
|
|||||||
;; The path of git executable. If empty, Gitea searches through the PATH environment.
|
;; The path of git executable. If empty, Gitea searches through the PATH environment.
|
||||||
;PATH =
|
;PATH =
|
||||||
;;
|
;;
|
||||||
;; The HOME directory for Git
|
;; The HOME directory for Git, defaults to "{APP_DATA_PATH}/home"
|
||||||
;HOME_PATH = %(APP_DATA_PATH)s/home
|
;HOME_PATH =
|
||||||
;;
|
;;
|
||||||
;; Disables highlight of added and removed changes
|
;; Disables highlight of added and removed changes
|
||||||
;DISABLE_DIFF_HIGHLIGHT = false
|
;DISABLE_DIFF_HIGHLIGHT = false
|
||||||
@@ -774,6 +787,9 @@ LEVEL = Info
|
|||||||
;ALLOW_ONLY_EXTERNAL_REGISTRATION = false
|
;ALLOW_ONLY_EXTERNAL_REGISTRATION = false
|
||||||
;;
|
;;
|
||||||
;; User must sign in to view anything.
|
;; User must sign in to view anything.
|
||||||
|
;; It could be set to "expensive" to block anonymous users accessing some pages which consume a lot of resources,
|
||||||
|
;; for example: block anonymous AI crawlers from accessing repo code pages.
|
||||||
|
;; The "expensive" mode is experimental and subject to change.
|
||||||
;REQUIRE_SIGNIN_VIEW = false
|
;REQUIRE_SIGNIN_VIEW = false
|
||||||
;;
|
;;
|
||||||
;; Mail notification
|
;; Mail notification
|
||||||
@@ -784,10 +800,13 @@ LEVEL = Info
|
|||||||
;; Please note that setting this to false will not disable OAuth Basic or Basic authentication using a token
|
;; Please note that setting this to false will not disable OAuth Basic or Basic authentication using a token
|
||||||
;ENABLE_BASIC_AUTHENTICATION = true
|
;ENABLE_BASIC_AUTHENTICATION = true
|
||||||
;;
|
;;
|
||||||
;; Show the password sign-in form (for password-based login), otherwise, only show OAuth2 login methods.
|
;; Show the password sign-in form (for password-based login), otherwise, only show OAuth2 or passkey login methods if they are enabled.
|
||||||
;; If you set it to false, maybe it also needs to set ENABLE_BASIC_AUTHENTICATION to false to completely disable password-based authentication.
|
;; If you set it to false, maybe it also needs to set ENABLE_BASIC_AUTHENTICATION to false to completely disable password-based authentication.
|
||||||
;ENABLE_PASSWORD_SIGNIN_FORM = true
|
;ENABLE_PASSWORD_SIGNIN_FORM = true
|
||||||
;;
|
;;
|
||||||
|
;; Allow users to sign-in with a passkey
|
||||||
|
;ENABLE_PASSKEY_AUTHENTICATION = true
|
||||||
|
;;
|
||||||
;; More detail: https://github.com/gogits/gogs/issues/165
|
;; More detail: https://github.com/gogits/gogs/issues/165
|
||||||
;ENABLE_REVERSE_PROXY_AUTHENTICATION = false
|
;ENABLE_REVERSE_PROXY_AUTHENTICATION = false
|
||||||
; Enable this to allow reverse proxy authentication for API requests, the reverse proxy is responsible for ensuring that no CSRF is possible.
|
; Enable this to allow reverse proxy authentication for API requests, the reverse proxy is responsible for ensuring that no CSRF is possible.
|
||||||
@@ -932,7 +951,29 @@ LEVEL = Info
|
|||||||
;;
|
;;
|
||||||
;; Disable the code explore page.
|
;; Disable the code explore page.
|
||||||
;DISABLE_CODE_PAGE = false
|
;DISABLE_CODE_PAGE = false
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;[qos]
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;
|
;;
|
||||||
|
;; Enable request quality of service and overload protection.
|
||||||
|
; ENABLED = false
|
||||||
|
;;
|
||||||
|
;; The maximum number of concurrent requests that the server will
|
||||||
|
;; process before enqueueing new requests. Default is "CpuNum * 4".
|
||||||
|
; MAX_INFLIGHT =
|
||||||
|
;;
|
||||||
|
;; The maximum number of requests that can be enqueued before new
|
||||||
|
;; requests will be dropped.
|
||||||
|
; MAX_WAITING = 100
|
||||||
|
;;
|
||||||
|
;; Target maximum wait time a request may be enqueued for. Requests
|
||||||
|
;; that are enqueued for less than this amount of time will not be
|
||||||
|
;; dropped. When wait times exceed this amount, a portion of requests
|
||||||
|
;; will be dropped until wait times have decreased below this amount.
|
||||||
|
; TARGET_WAIT_TIME = 250ms
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
@@ -946,8 +987,8 @@ LEVEL = Info
|
|||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;[repository]
|
;[repository]
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Root path for storing all repository data. By default, it is set to %(APP_DATA_PATH)s/gitea-repositories.
|
;; Root path for storing all repository data. By default, it is set to "{APP_DATA_PATH}/gitea-repositories".
|
||||||
;; A relative path is interpreted as _`AppWorkPath`_/%(ROOT)s
|
;; A relative path is interpreted as "{AppWorkPath}/{ROOT}" (use AppWorkPath as base path).
|
||||||
;ROOT =
|
;ROOT =
|
||||||
;;
|
;;
|
||||||
;; The script type this server supports. Usually this is `bash`, but some users report that only `sh` is available.
|
;; The script type this server supports. Usually this is `bash`, but some users report that only `sh` is available.
|
||||||
@@ -1057,15 +1098,6 @@ LEVEL = Info
|
|||||||
;; Separate extensions with a comma. To line wrap files without an extension, just put a comma
|
;; Separate extensions with a comma. To line wrap files without an extension, just put a comma
|
||||||
;LINE_WRAP_EXTENSIONS = .txt,.md,.markdown,.mdown,.mkd,.livemd,
|
;LINE_WRAP_EXTENSIONS = .txt,.md,.markdown,.mdown,.mkd,.livemd,
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;[repository.local]
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;;
|
|
||||||
;; Path for local repository copy. Defaults to `tmp/local-repo` (content gets deleted on gitea restart)
|
|
||||||
;LOCAL_COPY_PATH = tmp/local-repo
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;[repository.upload]
|
;[repository.upload]
|
||||||
@@ -1075,9 +1107,6 @@ LEVEL = Info
|
|||||||
;; Whether repository file uploads are enabled. Defaults to `true`
|
;; Whether repository file uploads are enabled. Defaults to `true`
|
||||||
;ENABLED = true
|
;ENABLED = true
|
||||||
;;
|
;;
|
||||||
;; Path for uploads. Defaults to `data/tmp/uploads` (content gets deleted on gitea restart)
|
|
||||||
;TEMP_PATH = data/tmp/uploads
|
|
||||||
;;
|
|
||||||
;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
|
;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
|
||||||
;ALLOWED_TYPES =
|
;ALLOWED_TYPES =
|
||||||
;;
|
;;
|
||||||
@@ -1120,6 +1149,9 @@ LEVEL = Info
|
|||||||
;; In default merge messages only include approvers who are official
|
;; In default merge messages only include approvers who are official
|
||||||
;DEFAULT_MERGE_MESSAGE_OFFICIAL_APPROVERS_ONLY = true
|
;DEFAULT_MERGE_MESSAGE_OFFICIAL_APPROVERS_ONLY = true
|
||||||
;;
|
;;
|
||||||
|
;; In default squash-merge messages include the commit message of all commits comprising the pull request.
|
||||||
|
;POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES = false
|
||||||
|
;;
|
||||||
;; Add co-authored-by and co-committed-by trailers if committer does not match author
|
;; Add co-authored-by and co-committed-by trailers if committer does not match author
|
||||||
;ADD_CO_COMMITTER_TRAILERS = true
|
;ADD_CO_COMMITTER_TRAILERS = true
|
||||||
;;
|
;;
|
||||||
@@ -1128,6 +1160,10 @@ LEVEL = Info
|
|||||||
;;
|
;;
|
||||||
;; Retarget child pull requests to the parent pull request branch target on merge of parent pull request. It only works on merged PRs where the head and base branch target the same repo.
|
;; Retarget child pull requests to the parent pull request branch target on merge of parent pull request. It only works on merged PRs where the head and base branch target the same repo.
|
||||||
;RETARGET_CHILDREN_ON_MERGE = true
|
;RETARGET_CHILDREN_ON_MERGE = true
|
||||||
|
;;
|
||||||
|
;; Delay mergeable check until page view or API access, for pull requests that have not been updated in the specified days when their base branches get updated.
|
||||||
|
;; Use "-1" to always check all pull requests (old behavior). Use "0" to always delay the checks.
|
||||||
|
;DELAY_CHECK_FOR_INACTIVE_DAYS = 7
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
@@ -1155,17 +1191,24 @@ LEVEL = Info
|
|||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;
|
;;
|
||||||
;; GPG key to use to sign commits, Defaults to the default - that is the value of git config --get user.signingkey
|
;; GPG or SSH key to use to sign commits, Defaults to the default - that is the value of git config --get user.signingkey
|
||||||
|
;; Depending on the value of SIGNING_FORMAT this is either:
|
||||||
|
;; - openpgp: the GPG key ID
|
||||||
|
;; - ssh: the path to the ssh public key "/path/to/key.pub": where "/path/to/key" is the private key, use ssh-keygen -t ed25519 to generate a new key pair without password
|
||||||
;; run in the context of the RUN_USER
|
;; run in the context of the RUN_USER
|
||||||
;; Switch to none to stop signing completely
|
;; Switch to none to stop signing completely
|
||||||
;SIGNING_KEY = default
|
;SIGNING_KEY = default
|
||||||
;;
|
;;
|
||||||
;; If a SIGNING_KEY ID is provided and is not set to default, use the provided Name and Email address as the signer.
|
;; If a SIGNING_KEY ID is provided and is not set to default, use the provided Name and Email address as the signer and the signing format.
|
||||||
;; These should match a publicized name and email address for the key. (When SIGNING_KEY is default these are set to
|
;; These should match a publicized name and email address for the key. (When SIGNING_KEY is default these are set to
|
||||||
;; the results of git config --get user.name and git config --get user.email respectively and can only be overridden
|
;; the results of git config --get user.name, git config --get user.email and git config --default openpgp --get gpg.format respectively and can only be overridden
|
||||||
;; by setting the SIGNING_KEY ID to the correct ID.)
|
;; by setting the SIGNING_KEY ID to the correct ID.)
|
||||||
;SIGNING_NAME =
|
;SIGNING_NAME =
|
||||||
;SIGNING_EMAIL =
|
;SIGNING_EMAIL =
|
||||||
|
;; SIGNING_FORMAT can be one of:
|
||||||
|
;; - openpgp (default): use GPG to sign commits
|
||||||
|
;; - ssh: use SSH to sign commits
|
||||||
|
;SIGNING_FORMAT = openpgp
|
||||||
;;
|
;;
|
||||||
;; Sets the default trust model for repositories. Options are: collaborator, committer, collaboratorcommitter
|
;; Sets the default trust model for repositories. Options are: collaborator, committer, collaboratorcommitter
|
||||||
;DEFAULT_TRUST_MODEL = collaborator
|
;DEFAULT_TRUST_MODEL = collaborator
|
||||||
@@ -1192,6 +1235,13 @@ LEVEL = Info
|
|||||||
;; - commitssigned: require that all the commits in the head branch are signed.
|
;; - commitssigned: require that all the commits in the head branch are signed.
|
||||||
;; - approved: only sign when merging an approved pr to a protected branch
|
;; - approved: only sign when merging an approved pr to a protected branch
|
||||||
;MERGES = pubkey, twofa, basesigned, commitssigned
|
;MERGES = pubkey, twofa, basesigned, commitssigned
|
||||||
|
;;
|
||||||
|
;; Determines which additional ssh keys are trusted for all signed commits regardless of the user
|
||||||
|
;; This is useful for ssh signing key rotation.
|
||||||
|
;; Exposes the provided SIGNING_NAME and SIGNING_EMAIL as the signer, regardless of the SIGNING_FORMAT value.
|
||||||
|
;; Multiple keys should be comma separated.
|
||||||
|
;; E.g."ssh-<algorithm> <key>". or "ssh-<algorithm> <key1>, ssh-<algorithm> <key2>".
|
||||||
|
;TRUSTED_SSH_KEYS =
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
@@ -1282,6 +1332,9 @@ LEVEL = Info
|
|||||||
;; Leave it empty to allow users to select any theme from "{CustomPath}/public/assets/css/theme-*.css"
|
;; Leave it empty to allow users to select any theme from "{CustomPath}/public/assets/css/theme-*.css"
|
||||||
;THEMES =
|
;THEMES =
|
||||||
;;
|
;;
|
||||||
|
;; The icons for file list (basic/material), this is a temporary option which will be replaced by a user setting in the future.
|
||||||
|
;FILE_ICON_THEME = material
|
||||||
|
;;
|
||||||
;; All available reactions users can choose on issues/prs and comments.
|
;; All available reactions users can choose on issues/prs and comments.
|
||||||
;; Values can be emoji alias (:smile:) or a unicode emoji.
|
;; Values can be emoji alias (:smile:) or a unicode emoji.
|
||||||
;; For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png
|
;; For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png
|
||||||
@@ -1295,6 +1348,10 @@ LEVEL = Info
|
|||||||
;; Dont mistake it for Reactions.
|
;; Dont mistake it for Reactions.
|
||||||
;CUSTOM_EMOJIS = gitea, codeberg, gitlab, git, github, gogs
|
;CUSTOM_EMOJIS = gitea, codeberg, gitlab, git, github, gogs
|
||||||
;;
|
;;
|
||||||
|
;; Comma separated list of enabled emojis, for example: smile, thumbsup, thumbsdown
|
||||||
|
;; Leave it empty to enable all emojis.
|
||||||
|
;ENABLED_EMOJIS =
|
||||||
|
;;
|
||||||
;; Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used.
|
;; Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used.
|
||||||
;DEFAULT_SHOW_FULL_NAME = false
|
;DEFAULT_SHOW_FULL_NAME = false
|
||||||
;;
|
;;
|
||||||
@@ -1339,6 +1396,9 @@ LEVEL = Info
|
|||||||
;; Number of repos that are displayed on one page
|
;; Number of repos that are displayed on one page
|
||||||
;REPO_PAGING_NUM = 15
|
;REPO_PAGING_NUM = 15
|
||||||
|
|
||||||
|
;; Number of orgs that are displayed on profile page
|
||||||
|
;ORG_PAGING_NUM = 15
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;[ui.meta]
|
;[ui.meta]
|
||||||
@@ -1392,14 +1452,14 @@ LEVEL = Info
|
|||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;
|
;;
|
||||||
;; Render soft line breaks as hard line breaks, which means a single newline character between
|
;; Customize render options for different contexts. Set to "none" to disable the defaults, or use comma separated list:
|
||||||
;; paragraphs will cause a line break and adding trailing whitespace to paragraphs is not
|
;; * short-issue-pattern: recognized "#123" issue reference and render it as a link to the issue
|
||||||
;; necessary to force a line break.
|
;; * new-line-hard-break: render soft line breaks as hard line breaks, which means a single newline character between
|
||||||
;; Render soft line breaks as hard line breaks for comments
|
;; paragraphs will cause a line break and adding trailing whitespace to paragraphs is not
|
||||||
;ENABLE_HARD_LINE_BREAK_IN_COMMENTS = true
|
;; necessary to force a line break.
|
||||||
;;
|
;RENDER_OPTIONS_COMMENT = short-issue-pattern, new-line-hard-break
|
||||||
;; Render soft line breaks as hard line breaks for markdown documents
|
;RENDER_OPTIONS_WIKI = short-issue-pattern
|
||||||
;ENABLE_HARD_LINE_BREAK_IN_DOCUMENTS = false
|
;RENDER_OPTIONS_REPO_FILE =
|
||||||
;;
|
;;
|
||||||
;; Comma separated list of custom URL-Schemes that are allowed as links when rendering Markdown
|
;; Comma separated list of custom URL-Schemes that are allowed as links when rendering Markdown
|
||||||
;; for example git,magnet,ftp (more at https://en.wikipedia.org/wiki/List_of_URI_schemes)
|
;; for example git,magnet,ftp (more at https://en.wikipedia.org/wiki/List_of_URI_schemes)
|
||||||
@@ -1413,6 +1473,11 @@ LEVEL = Info
|
|||||||
;;
|
;;
|
||||||
;; Enables math inline and block detection
|
;; Enables math inline and block detection
|
||||||
;ENABLE_MATH = true
|
;ENABLE_MATH = true
|
||||||
|
;;
|
||||||
|
;; Enable delimiters for math code block detection. Set to "none" to disable all,
|
||||||
|
;; or use comma separated list: inline-dollar, inline-parentheses, block-dollar, block-square-brackets
|
||||||
|
;; Defaults to "inline-dollar,block-dollar" to follow GitHub's behavior.
|
||||||
|
;MATH_CODE_BLOCK_DETECTION =
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
@@ -1482,6 +1547,10 @@ LEVEL = Info
|
|||||||
;REPO_INDEXER_EXCLUDE =
|
;REPO_INDEXER_EXCLUDE =
|
||||||
;;
|
;;
|
||||||
;MAX_FILE_SIZE = 1048576
|
;MAX_FILE_SIZE = 1048576
|
||||||
|
;;
|
||||||
|
;; Bleve engine has performance problems with fuzzy search, so we limit the fuzziness to 0 by default to disable it.
|
||||||
|
;; If you'd like to enable it, you can set it to a value between 0 and 2.
|
||||||
|
;TYPE_BLEVE_MAX_FUZZINESS = 0
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
@@ -1499,7 +1568,8 @@ LEVEL = Info
|
|||||||
;TYPE = persistable-channel
|
;TYPE = persistable-channel
|
||||||
;;
|
;;
|
||||||
;; data-dir for storing persistable queues and level queues, individual queues will default to `queues/common` meaning the queue is shared.
|
;; data-dir for storing persistable queues and level queues, individual queues will default to `queues/common` meaning the queue is shared.
|
||||||
;DATADIR = queues/ ; Relative paths will be made absolute against `%(APP_DATA_PATH)s`.
|
;; Relative paths will be made absolute against "APP_DATA_PATH"
|
||||||
|
;DATADIR = queues/
|
||||||
;;
|
;;
|
||||||
;; Default queue length before a channel queue will block
|
;; Default queue length before a channel queue will block
|
||||||
;LENGTH = 100000
|
;LENGTH = 100000
|
||||||
@@ -1747,6 +1817,9 @@ LEVEL = Info
|
|||||||
;;
|
;;
|
||||||
;; convert \r\n to \n for Sendmail
|
;; convert \r\n to \n for Sendmail
|
||||||
;SENDMAIL_CONVERT_CRLF = true
|
;SENDMAIL_CONVERT_CRLF = true
|
||||||
|
;;
|
||||||
|
;; convert links of attached images to inline images. Only for images hosted in this gitea instance.
|
||||||
|
;EMBED_ATTACHMENT_IMAGES = false
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
@@ -2398,6 +2471,8 @@ LEVEL = Info
|
|||||||
;DEFAULT_GIT_TREES_PER_PAGE = 1000
|
;DEFAULT_GIT_TREES_PER_PAGE = 1000
|
||||||
;; Default max size of a blob returned by the blobs API (default is 10MiB)
|
;; Default max size of a blob returned by the blobs API (default is 10MiB)
|
||||||
;DEFAULT_MAX_BLOB_SIZE = 10485760
|
;DEFAULT_MAX_BLOB_SIZE = 10485760
|
||||||
|
;; Default max combined size of all blobs returned by the files API (default is 100MiB)
|
||||||
|
;DEFAULT_MAX_RESPONSE_SIZE = 104857600
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
@@ -2438,7 +2513,7 @@ LEVEL = Info
|
|||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Set the maximum number of characters in a mermaid source. (Set to -1 to disable limits)
|
;; Set the maximum number of characters in a mermaid source. (Set to -1 to disable limits)
|
||||||
;MERMAID_MAX_SOURCE_CHARACTERS = 5000
|
;MERMAID_MAX_SOURCE_CHARACTERS = 50000
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
@@ -2470,7 +2545,19 @@ LEVEL = Info
|
|||||||
;; * sanitized: Sanitize the content and render it inside current page, default to only allow a few HTML tags and attributes. Customized sanitizer rules can be defined in [markup.sanitizer.*] .
|
;; * sanitized: Sanitize the content and render it inside current page, default to only allow a few HTML tags and attributes. Customized sanitizer rules can be defined in [markup.sanitizer.*] .
|
||||||
;; * no-sanitizer: Disable the sanitizer and render the content inside current page. It's **insecure** and may lead to XSS attack if the content contains malicious code.
|
;; * no-sanitizer: Disable the sanitizer and render the content inside current page. It's **insecure** and may lead to XSS attack if the content contains malicious code.
|
||||||
;; * iframe: Render the content in a separate standalone page and embed it into current page by iframe. The iframe is in sandbox mode with same-origin disabled, and the JS code are safely isolated from parent page.
|
;; * iframe: Render the content in a separate standalone page and embed it into current page by iframe. The iframe is in sandbox mode with same-origin disabled, and the JS code are safely isolated from parent page.
|
||||||
;RENDER_CONTENT_MODE=sanitized
|
;RENDER_CONTENT_MODE = sanitized
|
||||||
|
;; The sandbox applied to the iframe and Content-Security-Policy header when RENDER_CONTENT_MODE is `iframe`.
|
||||||
|
;; It defaults to a safe set of "allow-*" restrictions (space separated).
|
||||||
|
;; You can also set it by your requirements or use "disabled" to disable the sandbox completely.
|
||||||
|
;; When set it, make sure there is no security risk:
|
||||||
|
;; * PDF-only content: generally safe to use "disabled", and it needs to be "disabled" because PDF only renders with no sandbox.
|
||||||
|
;; * HTML content with JS: if the "RENDER_COMMAND" can guarantee there is no XSS, then it is safe, otherwise, you need to fine tune the "allow-*" restrictions.
|
||||||
|
;RENDER_CONTENT_SANDBOX =
|
||||||
|
;; Whether post-process the rendered HTML content, including:
|
||||||
|
;; resolve relative links and image sources, recognizing issue/commit references, escaping invisible characters,
|
||||||
|
;; mentioning users, rendering permlink code blocks, replacing emoji shorthands, etc.
|
||||||
|
;; By default, this is true when RENDER_CONTENT_MODE is `sanitized`, otherwise false.
|
||||||
|
;NEED_POST_PROCESS = false
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
@@ -2559,9 +2646,6 @@ LEVEL = Info
|
|||||||
;; Currently, only `minio` and `azureblob` is supported.
|
;; Currently, only `minio` and `azureblob` is supported.
|
||||||
;SERVE_DIRECT = false
|
;SERVE_DIRECT = false
|
||||||
;;
|
;;
|
||||||
;; Path for chunked uploads. Defaults to APP_DATA_PATH + `tmp/package-upload`
|
|
||||||
;CHUNKED_UPLOAD_PATH = tmp/package-upload
|
|
||||||
;;
|
|
||||||
;; Maximum count of package versions a single owner can have (`-1` means no limits)
|
;; Maximum count of package versions a single owner can have (`-1` means no limits)
|
||||||
;LIMIT_TOTAL_OWNER_COUNT = -1
|
;LIMIT_TOTAL_OWNER_COUNT = -1
|
||||||
;; Maximum size of packages a single owner can use (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
|
;; Maximum size of packages a single owner can use (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
|
||||||
|
|||||||
@@ -22,3 +22,8 @@ manifests:
|
|||||||
architecture: arm64
|
architecture: arm64
|
||||||
os: linux
|
os: linux
|
||||||
variant: v8
|
variant: v8
|
||||||
|
-
|
||||||
|
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}nightly{{/if}}-linux-riscv64-rootless
|
||||||
|
platform:
|
||||||
|
architecture: riscv64
|
||||||
|
os: linux
|
||||||
|
|||||||
@@ -22,3 +22,8 @@ manifests:
|
|||||||
architecture: arm64
|
architecture: arm64
|
||||||
os: linux
|
os: linux
|
||||||
variant: v8
|
variant: v8
|
||||||
|
-
|
||||||
|
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}nightly{{/if}}-linux-riscv64
|
||||||
|
platform:
|
||||||
|
architecture: riscv64
|
||||||
|
os: linux
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user