mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-07-01 14:40:21 +00:00
Compare commits
1160 commits
Author | SHA1 | Date | |
---|---|---|---|
|
7a8ff20bf3 | ||
|
8f28942233 | ||
|
2b5123a90f | ||
|
0730e5481f | ||
|
2f0a993a33 | ||
|
0ecb25fdcb | ||
|
6e58d285c7 | ||
|
6e66380408 | ||
|
06bff3bb7e | ||
|
a943271205 | ||
|
4927d4ee3d | ||
|
c57dea336c | ||
|
31fc02332a | ||
|
878ce241a4 | ||
|
447c5789bd | ||
|
920f6d24d2 | ||
|
2160741221 | ||
|
3feceb10c7 | ||
|
7a881e2f26 | ||
|
ad1adabcbb | ||
|
33217a3633 | ||
|
84ed8aa740 | ||
|
ba37b69252 | ||
|
b6c6981c30 | ||
|
14309837d4 | ||
|
b5e608f3e2 | ||
|
66e0988a43 | ||
|
b8e66a5552 | ||
|
a300c0b9fd | ||
|
d6e4342353 | ||
|
225a0f7026 | ||
|
6b27fa66b9 | ||
|
69d374435b | ||
|
c085d6c9ac | ||
|
3fb6e17105 | ||
|
aee161ff25 | ||
|
a2e7446fe7 | ||
|
7ad20a2730 | ||
|
184e068f37 | ||
|
414199fc66 | ||
|
aee5e1fb94 | ||
|
d3c712fe2a | ||
|
4a1f4acf76 | ||
|
30bfa13308 | ||
|
507a12bf82 | ||
|
69bd7a1f1b | ||
|
7ab27a7a7f | ||
|
2bca029f6f | ||
|
8844b6b8e5 | ||
|
9e966ac91f | ||
|
6d4554b01c | ||
|
6ed62c14d3 | ||
|
744363597d | ||
|
7a6b5b6dd9 | ||
|
7086e7a9ac | ||
|
f7d7d67238 | ||
|
0b24915271 | ||
|
43fb63a063 | ||
|
049cda526b | ||
|
39e6785da0 | ||
|
debd74e1b6 | ||
|
cf4d0e6c34 | ||
|
b58cebc2d9 | ||
|
d8ad592d4e | ||
|
b6dd1dd799 | ||
|
d7329f5dd4 | ||
|
690532efb8 | ||
|
b2c4fc9f94 | ||
|
9e6f722f94 | ||
|
25d596d387 | ||
|
1c0e9d8015 | ||
|
1efb4f1aaf | ||
|
c8e54e11d7 | ||
|
f708bacfff | ||
|
dd79f0ce2b | ||
|
c78f56e7cb | ||
|
ef27d55468 | ||
|
31ad7c9353 | ||
|
913eaffb8a | ||
|
285f66b782 | ||
|
968ba1bf84 | ||
|
42ea73d46f | ||
|
e7eca7f36c | ||
|
5fa37539de | ||
|
1e114a1225 | ||
|
1879ce8efe | ||
|
ae00a1d61b | ||
|
321561d315 | ||
|
b1e75421c1 | ||
|
e934d0a3f3 | ||
|
34987a2be7 | ||
|
fc69250f0f | ||
|
9caa3c6c5f | ||
|
15bb6b7f92 | ||
|
adc273e3a8 | ||
|
16dbc0efd3 | ||
|
3a986d282f | ||
|
3a8cea52cd | ||
|
b52264c953 | ||
|
0c55cdf6b6 | ||
|
9ea796b9ab | ||
|
877fa8cec1 | ||
|
5e157d40df | ||
|
65bb09a332 | ||
|
7380eac5a2 | ||
|
90f8239448 | ||
|
53d5e6d754 | ||
|
6cdf2cd66e | ||
|
35e051aaee | ||
|
44de50fcac | ||
|
07cc5802bf | ||
|
2fdddcb04e | ||
|
fd2f9e6842 | ||
|
a4ea74020f | ||
|
42514a4744 | ||
|
09699c1506 | ||
|
c21d271358 | ||
|
07e8684a61 | ||
|
402a85a9b6 | ||
|
563d8f1564 | ||
|
4a06153709 | ||
|
e2cf8520bc | ||
|
db99d8d09e | ||
|
6753480ab1 | ||
|
442f11995c | ||
|
fe28f128df | ||
|
90d67cf421 | ||
|
7ec014cba8 | ||
|
fcecafe823 | ||
|
2d436a5244 | ||
|
c93eb1f927 | ||
|
d3bc095d0c | ||
|
32300d5488 | ||
|
7958942e3e | ||
|
4288c214a4 | ||
|
39b93f828b | ||
|
250971b4ac | ||
|
4c085d35f8 | ||
|
e921cc8b09 | ||
|
bb78114e5e | ||
|
6965be8293 | ||
|
1ffeb2cc01 | ||
|
84c3b595c4 | ||
|
5391f43888 | ||
|
9b6e3b61cf | ||
|
b97c462d2e | ||
|
33eee199cf | ||
|
a1bec15043 | ||
|
9a1c10f92e | ||
|
1d3208898f | ||
|
8d80a9cc01 | ||
|
881cdf88bb | ||
|
d3adaf7574 | ||
|
81c960f0c2 | ||
|
580efedad4 | ||
|
e70f48bd44 | ||
|
bd6f3243ab | ||
|
b2b039b6e7 | ||
|
a8e375eb28 | ||
|
4b6ccbd631 | ||
|
4d44ae39e1 | ||
|
db83b43d8d | ||
|
6d7f5fb41e | ||
|
dc2954f8fe | ||
|
7428edacbe | ||
|
10da5e5609 | ||
|
f476ee2196 | ||
|
1242786324 | ||
|
e0bfacac0b | ||
|
b68f923592 | ||
|
82e4ccc223 | ||
|
8f2c08b8dc | ||
|
dcf1eef9e9 | ||
|
249f1fc17e | ||
|
313504739f | ||
|
7df94ff7b2 | ||
|
60adf59620 | ||
|
805e749a15 | ||
|
e4c43c0cec | ||
|
a24ca6e4b4 | ||
|
534020d0ad | ||
|
39d3e874b0 | ||
|
905a5748a8 | ||
|
2529923dea | ||
|
4dd0514022 | ||
|
d17aa98262 | ||
|
6ce9d764bc | ||
|
549fcff997 | ||
|
bc14ad7da2 | ||
|
48671975f1 | ||
|
240d958c0f | ||
|
ea07f0c0f3 | ||
|
e38e761d5b | ||
|
ac1f1a9cfb | ||
|
dbbd0de860 | ||
|
9ecef99ab9 | ||
|
374def9922 | ||
|
3bb6ed8f19 | ||
|
0ed7237b12 | ||
|
fc35915a28 | ||
|
85c054c412 | ||
|
55d8910255 | ||
|
dec17ba704 | ||
|
21151ea5ce | ||
|
baa93ec66e | ||
|
161924abf8 | ||
|
8e2747859b | ||
|
d6ab2a464f | ||
|
99d697263f | ||
|
25f3f8e1d2 | ||
|
de3a3d728d | ||
|
022eeee657 | ||
|
e78d1c8210 | ||
|
7d2a7b8559 | ||
|
4c4fe595c2 | ||
|
a9f9e7c013 | ||
|
e2278e5a38 | ||
|
ee663c5af8 | ||
|
40d678d4b9 | ||
|
d4bccedf8d | ||
|
8d5d3fc47c | ||
|
d483dc674a | ||
|
8b93f41aaa | ||
|
0dd605a8d3 | ||
|
62ec469394 | ||
|
006f488577 | ||
|
0e8d752d86 | ||
|
3ae188652a | ||
|
1b0eef4957 | ||
|
0077015755 | ||
|
765e7bd1b6 | ||
|
2b30c83a0c | ||
|
b2a3966e64 | ||
|
a5260b7f08 | ||
|
d2fce8360c | ||
|
1faab33da5 | ||
|
dda37e86bd | ||
|
de1adf224d | ||
|
6c6035bc49 | ||
|
fa2a135f68 | ||
|
112ba66637 | ||
|
5d47d2ed28 | ||
|
511718fb4f | ||
|
e2a4b47cd0 | ||
|
b416bbea54 | ||
|
7fb99379f6 | ||
|
f3294f0b2d | ||
|
a0f902f635 | ||
|
43fee56011 | ||
|
c0187aa789 | ||
|
75258cfa2a | ||
|
b6dcae9b50 | ||
|
a775c855cb | ||
|
e168c8448b | ||
|
dc56486b1f | ||
|
c1fad04473 | ||
|
0b09ef8380 | ||
|
121623582a | ||
|
c4c4b8867b | ||
|
5e9e146545 | ||
|
8deb184043 | ||
|
7c150be23d | ||
|
2ab5b585f6 | ||
|
9911450bd8 | ||
|
1ec20030af | ||
|
3a670596f5 | ||
|
09f7dbecda | ||
|
63543afa2b | ||
|
0383e2e15a | ||
|
2c9f272d42 | ||
|
e3ba342665 | ||
|
b22bea8b45 | ||
|
263d125849 | ||
|
9f69eee8f9 | ||
|
4d1f216241 | ||
|
ee02ad2630 | ||
|
a55755f9c1 | ||
|
eb010e0fca | ||
|
c56ab9f10c | ||
|
e57d0c248f | ||
|
a50b467d9f | ||
|
a696ad132c | ||
|
0234d00ace | ||
|
f4af3191ef | ||
|
4183fa9a03 | ||
|
e9ec11df4b | ||
|
b3e25cf536 | ||
|
683305e5b9 | ||
|
332705d841 | ||
|
f6573b9f3b | ||
|
0ce1f70820 | ||
|
03785a8169 | ||
|
ed9693f236 | ||
|
2a568f3b82 | ||
|
52f20da52a | ||
|
4a57f73072 | ||
|
8b03d3260d | ||
|
bf02da9fd9 | ||
|
cf03286b5b | ||
|
37d566bdb0 | ||
|
82477cb55c | ||
|
8b90eddc56 | ||
|
3ebd96ef73 | ||
|
f07456286e | ||
|
dbfc24001f | ||
|
481c7aaf19 | ||
|
5ac2c0a2ba | ||
|
aec8f4c57a | ||
|
b7b70dbaf2 | ||
|
4096d0d2d6 | ||
|
e08f2ac7f1 | ||
|
beb985062d | ||
|
c57ab693f9 | ||
|
aa4ae81fe0 | ||
|
60c1af244a | ||
|
4a1487c193 | ||
|
fbc32463fc | ||
|
b51ce74552 | ||
|
55f4ee214d | ||
|
2846aebc6a | ||
|
4a51a1f360 | ||
|
8bbac4c679 | ||
|
7ed278dec9 | ||
|
e1d8afdf32 | ||
|
d7baf25a6b | ||
|
c5d274a384 | ||
|
9cc0d4b3d1 | ||
|
ec076e66d8 | ||
|
32e64ccd34 | ||
|
a16350d9f4 | ||
|
8c81dca8b9 | ||
|
f6599099ee | ||
|
df5d656827 | ||
|
d07a9a1b56 | ||
|
386e7f8208 | ||
|
bc99bf5e8f | ||
|
b25091f757 | ||
|
bc8c3cc009 | ||
|
eb188cf83f | ||
|
61b9535039 | ||
|
a0cc05ba92 | ||
|
9e70ff57b4 | ||
|
95ad7d6201 | ||
|
31cd8c6d38 | ||
|
055fc6e0c3 | ||
|
2ce7affc9a | ||
|
00761a15d1 | ||
|
afcb7262ea | ||
|
9289e293fd | ||
|
224cb07907 | ||
|
cbcb04cf53 | ||
|
b8d7303828 | ||
|
025689312b | ||
|
7284cb5685 | ||
|
f48c6d3c04 | ||
|
c5a0825587 | ||
|
a0115cf10f | ||
|
7e489eed25 | ||
|
580b7e2671 | ||
|
05273fa8d2 | ||
|
0f6176470f | ||
|
8958dee86e | ||
|
b6072496d4 | ||
|
02c5ce77db | ||
|
7c6d5aaa56 | ||
|
8e49c07187 | ||
|
f463f97f24 | ||
|
2ca2a73311 | ||
|
06c38cd613 | ||
|
ed18a8fb46 | ||
|
58389fd357 | ||
|
bbb7f6ec6f | ||
|
dec91bd9ed | ||
|
eb67c4c109 | ||
|
358863999e | ||
|
875534e50a | ||
|
d0b7a2cccf | ||
|
01eee60fdc | ||
|
64555d6efb | ||
|
603d655ec4 | ||
|
8e0b86a5dc | ||
|
424606c8a4 | ||
|
507280b073 | ||
|
acf6c13fd9 | ||
|
fda68ddf20 | ||
|
a0c9e81611 | ||
|
78e56ee2f7 | ||
|
9a4ded7d80 | ||
|
3f673ce4d4 | ||
|
6d7b1f87e4 | ||
|
f3882d4553 | ||
|
58653d3700 | ||
|
23cc1fdbbe | ||
|
813eabc340 | ||
|
9720418bb5 | ||
|
4fe172e4b2 | ||
|
4ce232bcfe | ||
|
aa9d89c846 | ||
|
a13414341b | ||
|
0f35e87290 | ||
|
5c1c514361 | ||
|
1b0e3c717f | ||
|
c35550f324 | ||
|
f6a5b783d2 | ||
|
23360ad415 | ||
|
f711aaceb8 | ||
|
f28dbe4306 | ||
|
2accf24f72 | ||
|
2570be93a6 | ||
|
1b11ca6f36 | ||
|
6d703bb6e3 | ||
|
504bb09319 | ||
|
82dd00873d | ||
|
9144726e4f | ||
|
23b713464c | ||
|
89e4144855 | ||
|
8665bbc085 | ||
|
bc02a82338 | ||
|
442958df1d | ||
|
7861d2ffcf | ||
|
b86aefc038 | ||
|
b55c72828e | ||
|
eb85681b41 | ||
|
2a1759ba3b | ||
|
eb3feaad45 | ||
|
a783a72d6b | ||
|
cdb4682bca | ||
|
81b5c7ca6f | ||
|
1c2a29807b | ||
|
7d9d48819a | ||
|
0bfb3751fb | ||
|
911806879f | ||
|
c31be003d1 | ||
|
282125e90f | ||
|
eca57cc3e9 | ||
|
3be290457f | ||
|
bca6ae7862 | ||
|
5b6cbd8195 | ||
|
b54d210eb3 | ||
|
64cc883ac1 | ||
|
3e3a109dd2 | ||
|
51990751a6 | ||
|
f7b19964a7 | ||
|
02d9c7cd27 | ||
|
afffbe2982 | ||
|
39341df8cb | ||
|
8296a23d79 | ||
|
3272e3588a | ||
|
beddf442b1 | ||
|
08b2ec2300 | ||
|
9d984fedf3 | ||
|
e6c17f31be | ||
|
b04bb28ed1 | ||
|
cb4ef4495a | ||
|
d46cf46f6e | ||
|
577a694df6 | ||
|
6fa5705d4b | ||
|
56cc841b65 | ||
|
2e6f624d2f | ||
|
0766cb4776 | ||
|
7145665cbf | ||
|
6ad706aa88 | ||
|
c977585e4c | ||
|
a0df27c929 | ||
|
d688ca4515 | ||
|
53df0bf9a4 | ||
|
99fc04b763 | ||
|
afea42d517 | ||
|
1ec672694a | ||
|
d28a64e538 | ||
|
77b0275572 | ||
|
ba5b157f7e | ||
|
4ab40ed6af | ||
|
275d8250ea | ||
|
5e79b39654 | ||
|
738f721f3a | ||
|
49709bb270 | ||
|
ee5b102142 | ||
|
5706a2452e | ||
|
15a2338ff2 | ||
|
bd9366e7fc | ||
|
f691f03741 | ||
|
d656978818 | ||
|
18c38ed1f3 | ||
|
61885a0eaa | ||
|
6ad625b0c1 | ||
|
c497ef9e81 | ||
|
da62c2cac6 | ||
|
979cc5cd93 | ||
|
c9c8fb6501 | ||
|
d9892e57bd | ||
|
9420d3d0a5 | ||
|
c124014115 | ||
|
9eb67ba8ed | ||
|
dde7c4a770 | ||
|
8fdcde5f22 | ||
|
b296354873 | ||
|
adc2a215c1 | ||
|
6d5fc19464 | ||
|
50f8563c67 | ||
|
f68d49cb9e | ||
|
9018e5bc19 | ||
|
9d8624f341 | ||
|
839739fb71 | ||
|
4ba5c957e0 | ||
|
dbeab2a0c3 | ||
|
10c8ca62d2 | ||
|
a9faf1ff7b | ||
|
4b56c05e65 | ||
|
114cd6d4b6 | ||
|
b0dd490ae1 | ||
|
3769259c93 | ||
|
5275fbd4ea | ||
|
86039a89fc | ||
|
2348955af5 | ||
|
7baf9994bd | ||
|
facc2bb28e | ||
|
ba1e3405d6 | ||
|
63a80bf2b9 | ||
|
a23d0453a3 | ||
|
bb4e1f426f | ||
|
51caba694a | ||
|
d59495a4db | ||
|
32005ad2ab | ||
|
50798d0a63 | ||
|
aecf60f233 | ||
|
c9853e9e3a | ||
|
8df8381f51 | ||
|
bf8bdf12df | ||
|
49ea851da9 | ||
|
fed2d81c44 | ||
|
51ff4970ec | ||
|
683eb5bf78 | ||
|
cdf254816b | ||
|
adce8fa7dd | ||
|
5380f23dab | ||
|
6cac7702e9 | ||
|
85ae9d710c | ||
|
da8aa01466 | ||
|
7990bcf333 | ||
|
2457f5ff22 | ||
|
372a3dad83 | ||
|
d0a5531ebc | ||
|
e84d3a0f53 | ||
|
6d7b4c8000 | ||
|
d61f84ca7f | ||
|
d85f4f2cce | ||
|
f7df87621a | ||
|
f009c6ec4a | ||
|
89fbd4867e | ||
|
c45a1d9984 | ||
|
c531b8f020 | ||
|
513319c1ec | ||
|
587044cba1 | ||
|
3593d995a9 | ||
|
e84db8113a | ||
|
2e7ccb42ac | ||
|
dae08c5084 | ||
|
cff284fdc3 | ||
|
2d54cbc8fd | ||
|
c399b8b135 | ||
|
3700db6dd5 | ||
|
c5c0948ae5 | ||
|
bec2659bfb | ||
|
03c50c54bb | ||
|
0b73a1da00 | ||
|
5a7af0dae2 | ||
|
72ee7f3b00 | ||
|
2cd9872b10 | ||
|
e033967eee | ||
|
316682f17b | ||
|
30b438ff84 | ||
|
5032388cc7 | ||
|
9ad3f2813b | ||
|
e2aa9adad7 | ||
|
9786982c6e | ||
|
83e186c00c | ||
|
d40ff8c1c1 | ||
|
2b79c23686 | ||
|
736adbde23 | ||
|
3901ae6ab1 | ||
|
7d6d4f94ee | ||
|
a624b6a8f4 | ||
|
d5c8091e08 | ||
|
ccd87001c8 | ||
|
511148dbc3 | ||
|
5e338062c5 | ||
|
ae8dca8423 | ||
|
1132fde45f | ||
|
61787b54c3 | ||
|
67431ad107 | ||
|
2ca5405120 | ||
|
983264d873 | ||
|
7a430d6b43 | ||
|
66b7e1f34b | ||
|
5162c887df | ||
|
5507b25d65 | ||
|
d1882a3213 | ||
|
cbd772873a | ||
|
7a19d3c2be | ||
|
c7cb5f9978 | ||
|
d40c444d07 | ||
|
0dfa5d0c7d | ||
|
f8dfb5905f | ||
|
bd9719918c | ||
|
3c014a8f0e | ||
|
d53dfcbccb | ||
|
380e2879bc | ||
|
534d3ca93e | ||
|
3816db68aa | ||
|
cb46a036aa | ||
|
d733bdf1db | ||
|
168b0ea9d4 | ||
|
a479fef432 | ||
|
7c05c8faac | ||
|
54ec6daa2d | ||
|
756fc48534 | ||
|
3dc222e46c | ||
|
91a791bfed | ||
|
88e0549c33 | ||
|
474cc60451 | ||
|
69e8496c99 | ||
|
1327acccad | ||
|
8e868580e5 | ||
|
58ca578f64 | ||
|
377052c90c | ||
|
584c504e25 | ||
|
f015c00ecb | ||
|
9073ca8128 | ||
|
140b28b60d | ||
|
8fce7fbec7 | ||
|
0a52be2a04 | ||
|
49694242d4 | ||
|
da2a92fc5f | ||
|
3db9de3b53 | ||
|
eb78c6618b | ||
|
4f027fe95a | ||
|
9cef129f6b | ||
|
30982b9e7b | ||
|
9dea54a9d6 | ||
|
3de904c963 | ||
|
b2d01fdde6 | ||
|
179f9b37f5 | ||
|
ca0f2a0adf | ||
|
17106721f0 | ||
|
a2958f5a26 | ||
|
b10454a00c | ||
|
fc4458bfbb | ||
|
932201fa23 | ||
|
ed85d1f2a9 | ||
|
e4a21a1493 | ||
|
5e08b51fed | ||
|
19cd33d35b | ||
|
4c123d8e3b | ||
|
dc7f5d6b84 | ||
|
2c27a0f727 | ||
|
e39b98ea51 | ||
|
c1399947f4 | ||
|
e76e130e66 | ||
|
362d076b98 | ||
|
101efdd2e7 | ||
|
6b436955fc | ||
|
bb0e26a7b4 | ||
|
15e89bf0ef | ||
|
ee324c68cb | ||
|
3fdc3b6f82 | ||
|
a9303901cf | ||
|
9a5ef22f3a | ||
|
41e91373d3 | ||
|
670ad6e150 | ||
|
46d1a0f5c1 | ||
|
4c641b0fb2 | ||
|
8971321083 | ||
|
d04188a248 | ||
|
372b91a362 | ||
|
bfd0b645d0 | ||
|
ed5af44c06 | ||
|
735b536c04 | ||
|
011c8b23fd | ||
|
9dd47d932f | ||
|
ac08fc5b40 | ||
|
4f770a8995 | ||
|
9f0147994b | ||
|
a397602525 | ||
|
39a4f22c1c | ||
|
4e30f54295 | ||
|
a8eac67c85 | ||
|
2212923de0 | ||
|
535408143a | ||
|
7b8bcfad55 | ||
|
2024031a7a | ||
|
77a1af5ab8 | ||
|
e69be4cf55 | ||
|
fae5681858 | ||
|
12488d3f2d | ||
|
e09ea821bd | ||
|
6dad457552 | ||
|
3372db660c | ||
|
6fb069cc50 | ||
|
17db5f3793 | ||
|
b74a967c92 | ||
|
85124bcfcf | ||
|
0e5e383425 | ||
|
952dfac255 | ||
|
e932f89a13 | ||
|
dccd443e04 | ||
|
344351b712 | ||
|
0ff2c11432 | ||
|
eea9160c4c | ||
|
c0d132c79b | ||
|
a4778fc970 | ||
|
768f5a9255 | ||
|
3a68a4abac | ||
|
8a84f23eb2 | ||
|
c44e671293 | ||
|
6d93b7f78d | ||
|
819f4ee587 | ||
|
ea42b1eadb | ||
|
138c8259ff | ||
|
8d8275caa8 | ||
|
1dd7b08f63 | ||
|
0042f50b1b | ||
|
3c68399eb0 | ||
|
ec35eb2506 | ||
|
cddf608cb9 | ||
|
eaa641c21e | ||
|
b88baac7e3 | ||
|
de3e6f8a72 | ||
|
9eebadce02 | ||
|
ffd19ec853 | ||
|
75b9bd82e8 | ||
|
f7e2aa5c96 | ||
|
8c1e27cd76 | ||
|
f05b960494 | ||
|
3ba7445d81 | ||
|
c7798bc657 | ||
|
1745053229 | ||
|
dc5bc1fe5b | ||
|
e8ebb5d6e3 | ||
|
8910580d0b | ||
|
ebb12bef4e | ||
|
5a2f2229d2 | ||
|
4262e3eb1f | ||
|
392921f585 | ||
|
d81baf21e9 | ||
|
31e7aa61b1 | ||
|
06556abb6d | ||
|
1a77edcecb | ||
|
bc31f0601c | ||
|
3d0e9d399c | ||
|
37cd4108d6 | ||
|
24784b214f | ||
|
2382b74526 | ||
|
7296f11288 | ||
|
0989c4c36b | ||
|
d3a125aad0 | ||
|
39378fce09 | ||
|
a1486b0ee4 | ||
|
7104c73c96 | ||
|
486acb04fa | ||
|
df232e1a8f | ||
|
ef2fbc6093 | ||
|
b1a7db7e28 | ||
|
c58d282f3d | ||
|
94e782038d | ||
|
0bb5333d77 | ||
|
5feca875ea | ||
|
ab69057327 | ||
|
a6e0d3e37c | ||
|
90adede20b | ||
|
5509ce5557 | ||
|
067ddca6d3 | ||
|
179f85cf49 | ||
|
0dcc72dcd9 | ||
|
afa8e5ad13 | ||
|
ed292505b6 | ||
|
bb07f30b4f | ||
|
cf157ab360 | ||
|
2a75f0733d | ||
|
79d13c2686 | ||
|
fef59104bc | ||
|
c6ab10c1ba | ||
|
3d708aeee6 | ||
|
65fa66fb18 | ||
|
8cda45f750 | ||
|
9d2fabc7d3 | ||
|
d3689b5683 | ||
|
751a3da979 | ||
|
ed855e1492 | ||
|
816cd117f7 | ||
|
fad28141fa | ||
|
97f743a89e | ||
|
da0804141b | ||
|
1e4baa8439 | ||
|
1d2250ef45 | ||
|
24a06a7a88 | ||
|
b44b5fa63e | ||
|
19f3639121 | ||
|
51060d9826 | ||
|
b1b635c1d9 | ||
|
caeb95cb10 | ||
|
3705555f67 | ||
|
3f4a36d2b5 | ||
|
0b17346cff | ||
|
f359ebeea5 | ||
|
94845020e8 | ||
|
93f84db542 | ||
|
c48f85797f | ||
|
b7065b7748 | ||
|
2645c0824f | ||
|
7f01f3cb20 | ||
|
f90bf425ad | ||
|
7f4f3434ec | ||
|
5961db5aa7 | ||
|
1f24f35fe9 | ||
|
742e0c6a72 | ||
|
6cdc78ed4f | ||
|
00a5a2365e | ||
|
41e74657d7 | ||
|
0d3a759fdc | ||
|
d1d78c1b14 | ||
|
862f4ad60c | ||
|
0d10d0971e | ||
|
519169ee7b | ||
|
499497c959 | ||
|
5b26596c5c | ||
|
bb6973033c | ||
|
6f181c3815 | ||
|
fd75bf83f5 | ||
|
273bc9eb04 | ||
|
a89b5b5333 | ||
|
aa17548b53 | ||
|
df06904a9c | ||
|
1d8712a8d4 | ||
|
61b608693b | ||
|
63e52b1b48 | ||
|
59910a461d | ||
|
7579f25807 | ||
|
f66a6b12cd | ||
|
a5fa488d67 | ||
|
689fb82a70 | ||
|
f44f3cbc2a | ||
|
08175152e0 | ||
|
6f97d5d625 | ||
|
6bd9867c3e | ||
|
41dceec63d | ||
|
a5f151602b | ||
|
04f6d0568e | ||
|
c92789100c | ||
|
c9b72d5b27 | ||
|
1ee9e88809 | ||
|
039a679ccb | ||
|
5d52f88c9c | ||
|
4cdfe27c8f | ||
|
9a406757f5 | ||
|
66d252bc2b | ||
|
e2dfdcdabd | ||
|
d2701877c0 | ||
|
6fd9c065dc | ||
|
61f01f35ae | ||
|
581a2ca341 | ||
|
5423e22aeb | ||
|
1b10046e1a | ||
|
0ae59b7de7 | ||
|
5cc5c877a5 | ||
|
2d81bdbeb6 | ||
|
4d8f1e3e4b | ||
|
552cfe75f5 | ||
|
b1590068a7 | ||
|
4ef00fe6f9 | ||
|
6062ba36f2 | ||
|
226994a6df | ||
|
56c757561f | ||
|
c4b2352c6c | ||
|
b7b52fd04d | ||
|
8c3bfcac3b | ||
|
901b32ce16 | ||
|
7481624273 | ||
|
a52b610eff | ||
|
5813244ff8 | ||
|
7678386138 | ||
|
c0a2108c25 | ||
|
1698210ead | ||
|
dfcf3f116b | ||
|
2ba8946cac | ||
|
59dfab2d8b | ||
|
801c392e0c | ||
|
4cc0320ed0 | ||
|
83311c694c | ||
|
fd285bfc4e | ||
|
b69755b417 | ||
|
5e92674ec4 | ||
|
bd6864fe1f | ||
|
ff3f12a350 | ||
|
15aa35a809 | ||
|
5255fd1a6c | ||
|
5853180af8 | ||
|
49c5102b40 | ||
|
621ae93392 | ||
|
a334785ac7 | ||
|
9f3507e726 | ||
|
270a2c7fa3 | ||
|
41fd548e7a | ||
|
048af05db9 | ||
|
5bfabc8115 | ||
|
95f231612b | ||
|
f45a9836b2 | ||
|
443f7d59f9 | ||
|
46e60ce966 | ||
|
f76fe1f16d | ||
|
8f8856dcb8 | ||
|
c4d8f62673 | ||
|
8178b4e98b | ||
|
a9c97110f9 | ||
|
a9c4a25fb1 | ||
|
439d68b908 | ||
|
dac7d5e73b | ||
|
14cb862790 | ||
|
89dfabdc9f | ||
|
7ae5376573 | ||
|
955f99b6a4 | ||
|
a40284bec4 | ||
|
c92fe83c40 | ||
|
1508d13cf3 | ||
|
8770ee3a99 | ||
|
b68f772a15 | ||
|
6a5dc12732 | ||
|
8a9da6ab1b | ||
|
966b845e39 | ||
|
aa986ef1a6 | ||
|
0379739ac9 | ||
|
5be7c6cbd8 | ||
|
3d3ffaf5cb | ||
|
243fdb60d0 | ||
|
86c8949d9c | ||
|
ce4730f9d7 | ||
|
ae51a90181 | ||
|
06dfcc1a45 | ||
|
6ffa454979 | ||
|
574ff84967 | ||
|
5f2d445d00 | ||
|
d68e0d3e39 | ||
|
7ea62c5ce4 | ||
|
276ef10dd5 | ||
|
e35afe475a | ||
|
d2bb86407f | ||
|
f2e63495e7 | ||
|
244ee64c30 | ||
|
f1a92de4e6 | ||
|
4c746ec653 | ||
|
401906b88e | ||
|
dbec2ed350 | ||
|
5c8db43447 | ||
|
34e1100ae2 | ||
|
a2787bb09e | ||
|
376a2e19ea | ||
|
b2a3a0411c | ||
|
387f590d9c | ||
|
cefd786685 | ||
|
907ab8bdef | ||
|
4fd56a11c8 | ||
|
909738e6f6 | ||
|
3003812477 | ||
|
4c7bfedf55 | ||
|
dd1523c72e | ||
|
e7299eb0fb | ||
|
f7ba8f0e41 | ||
|
a975b6ab94 | ||
|
c81e5fe123 | ||
|
92a5fe04c6 | ||
|
c30d13b97c | ||
|
9f842f0dec | ||
|
a013acb632 | ||
|
d717c78ea0 | ||
|
a87d07f4d2 | ||
|
30c7a0b947 | ||
|
4e6f8dd57a | ||
|
03fe21d020 | ||
|
9a608a034d | ||
|
0858a879e5 | ||
|
2ff408c8c8 | ||
|
333f384bc8 | ||
|
fc07b95d67 | ||
|
af5d96e2cc | ||
|
011ac8df44 | ||
|
dd063409e3 | ||
|
f2feb34927 | ||
|
ed96852fdb | ||
|
d09b8ba9cf | ||
|
f809052193 | ||
|
263b55fda8 | ||
|
20b401601c | ||
|
b3626f0208 | ||
|
f9e6b3e162 | ||
|
98edc27088 | ||
|
8ac310bdfd | ||
|
46e15e57f7 | ||
|
a22c8248a3 | ||
|
6980a87a08 | ||
|
d2d14ce4bb | ||
|
a99cd47081 | ||
|
ac077302f1 | ||
|
7697254fe0 | ||
|
6a236099c8 | ||
|
adf9172888 | ||
|
83aba057c2 | ||
|
cefd085f87 | ||
|
f0899d35c8 | ||
|
2a5e37bc8b | ||
|
8ed4b77f1f | ||
|
7189da15f2 | ||
|
7973332035 | ||
|
604c21ffd7 | ||
|
63204e8edf | ||
|
946b77115e | ||
|
168c1d806b | ||
|
d8f71b513c | ||
|
16564fd9e8 | ||
|
6169090c13 | ||
|
7f9e313ec1 | ||
|
4261301dfb | ||
|
17e52c6019 | ||
|
9eaf40f7a8 | ||
|
69750e11d4 | ||
|
6449b6c01f | ||
|
07d7c54d91 | ||
|
4b0d11976f | ||
|
5bfae7241c | ||
|
a23cf58d47 | ||
|
ba8ebcbd68 | ||
|
c14c2bbf35 | ||
|
76a85d26c8 | ||
|
e507fa30df | ||
|
6208d5f5c2 | ||
|
e7bc13a0be | ||
|
81f30218cf | ||
|
15ff226a5b | ||
|
af91f4e203 | ||
|
9b2f38261b | ||
|
bc64b6f004 | ||
|
e8c769d8e6 | ||
|
68d690b6b9 | ||
|
ebe6ebe3f3 | ||
|
3f44b97b5f | ||
|
a2e0dd829c | ||
|
a2eb249766 | ||
|
339814f3bc | ||
|
959c902748 | ||
|
05a8dcf2be | ||
|
b59522f0b3 | ||
|
b544243164 | ||
|
283843677e | ||
|
cfb18e2b6c | ||
|
520a1ab586 | ||
|
3388d5fe31 | ||
|
a33fe60bbe | ||
|
da5445ac87 | ||
|
e99bf7f16d | ||
|
e1cdaf1f28 | ||
|
3bea06f906 | ||
|
e28bd3c4d6 | ||
|
93cd2b724e | ||
|
b01f3b9b05 | ||
|
6cea7d7fa3 | ||
|
13ea8296af | ||
|
c53d21965a | ||
|
6e13ac4d5f | ||
|
b9ed4c3325 | ||
|
faf1c836eb | ||
|
d5bd212d00 | ||
|
5f7a267e65 | ||
|
10067db6b0 | ||
|
11a433ed1c | ||
|
f1bf489203 | ||
|
b13ac5fcbf | ||
|
4e819f1b3c | ||
|
d490738d5b | ||
|
4a7ec0f9a8 | ||
|
9195d6a318 | ||
|
5132854603 | ||
|
4b69aa5c10 | ||
|
13b6caa608 | ||
|
1600255c26 | ||
|
a302506095 | ||
|
8ada1b93bb | ||
|
7bae89770c | ||
|
bd7db225e8 | ||
|
933e4e30d8 | ||
|
ceee649086 | ||
|
7bb043ce9b | ||
|
bc2f640a95 | ||
|
29c59c96bd | ||
|
5e7981aa07 | ||
|
2885ea8da2 | ||
|
8e3c066b27 | ||
|
e819f649aa | ||
|
c67d63d88a | ||
|
de4053db83 | ||
|
57f7253610 | ||
|
8d6f0d49c6 | ||
|
a5dc315a64 | ||
|
0569714439 | ||
|
e14f2d0c84 | ||
|
db7be1a1db | ||
|
2c81893c76 | ||
|
d071c09bf7 | ||
|
e8a022ac9a | ||
|
4ed10f782b | ||
|
3870acbdfd | ||
|
b03ecf584c | ||
|
036426e230 | ||
|
b74406d82c | ||
|
02c34f9908 | ||
|
6d61ae5cbe | ||
|
8d829a97b2 | ||
|
6723087a6e | ||
|
8d32ca32c2 | ||
|
a82cd18d9a | ||
|
b2a5b428a2 | ||
|
37f05617a5 | ||
|
4cde56906e | ||
|
81599155e8 | ||
|
471e5b1975 | ||
|
9c43fa0644 | ||
|
b7d7431d05 | ||
|
b18dcd69f2 | ||
|
92ac337263 | ||
|
2ffa9a5e6e | ||
|
dcdeb23cc3 | ||
|
0efd7130f0 | ||
|
234b57b00c | ||
|
af88ddae8c | ||
|
666654a5ef | ||
|
8a3fb4885e | ||
|
e94134def6 | ||
|
410c8b913f | ||
|
5eb0877381 | ||
|
180fb3f4d6 | ||
|
64deec434a | ||
|
dd4a1107ed | ||
|
72dcf04690 | ||
|
6836ded397 | ||
|
4f92b738b5 | ||
|
283d883e5a | ||
|
515eec3d1a | ||
|
c271779092 | ||
|
b397a102c1 | ||
|
265174bd5e | ||
|
32de0745e4 | ||
|
8bd1c7ff31 | ||
|
bbcd7d6fae | ||
|
4568ebc913 | ||
|
aa64f6515c | ||
|
df570a1d71 | ||
|
25bfd2983e | ||
|
7685a1e98e | ||
|
a87e0d0e46 | ||
|
47fd9a421f | ||
|
ab2127faef | ||
|
4500757acd |
2965 changed files with 74569 additions and 39629 deletions
224
.deadcode-out
224
.deadcode-out
|
@ -1,7 +1,7 @@
|
||||||
code.gitea.io/gitea/cmd
|
forgejo.org/cmd
|
||||||
NoMainListener
|
NoMainListener
|
||||||
|
|
||||||
code.gitea.io/gitea/cmd/forgejo
|
forgejo.org/cmd/forgejo
|
||||||
ContextSetNoInit
|
ContextSetNoInit
|
||||||
ContextSetNoExit
|
ContextSetNoExit
|
||||||
ContextSetStderr
|
ContextSetStderr
|
||||||
|
@ -9,128 +9,122 @@ code.gitea.io/gitea/cmd/forgejo
|
||||||
ContextSetStdout
|
ContextSetStdout
|
||||||
ContextSetStdin
|
ContextSetStdin
|
||||||
|
|
||||||
code.gitea.io/gitea/models
|
forgejo.org/models
|
||||||
IsErrUpdateTaskNotExist
|
|
||||||
ErrUpdateTaskNotExist.Error
|
|
||||||
ErrUpdateTaskNotExist.Unwrap
|
|
||||||
IsErrSHANotFound
|
IsErrSHANotFound
|
||||||
IsErrMergeDivergingFastForwardOnly
|
IsErrMergeDivergingFastForwardOnly
|
||||||
|
|
||||||
code.gitea.io/gitea/models/actions
|
forgejo.org/models/activities
|
||||||
ScheduleList.GetUserIDs
|
GetActivityByID
|
||||||
ScheduleList.GetRepoIDs
|
NewFederatedUserActivity
|
||||||
ScheduleList.LoadTriggerUser
|
CreateUserActivity
|
||||||
ScheduleList.LoadRepos
|
GetFollowingFeeds
|
||||||
|
FederatedUserActivity.loadActor
|
||||||
|
|
||||||
code.gitea.io/gitea/models/asymkey
|
forgejo.org/models/auth
|
||||||
ErrGPGKeyAccessDenied.Error
|
|
||||||
ErrGPGKeyAccessDenied.Unwrap
|
|
||||||
HasDeployKey
|
|
||||||
|
|
||||||
code.gitea.io/gitea/models/auth
|
|
||||||
GetSourceByName
|
|
||||||
WebAuthnCredentials
|
WebAuthnCredentials
|
||||||
|
|
||||||
code.gitea.io/gitea/models/db
|
forgejo.org/models/db
|
||||||
TruncateBeans
|
TruncateBeans
|
||||||
InTransaction
|
InTransaction
|
||||||
DumpTables
|
DumpTables
|
||||||
|
GetTableNames
|
||||||
|
|
||||||
code.gitea.io/gitea/models/dbfs
|
forgejo.org/models/dbfs
|
||||||
file.renameTo
|
file.renameTo
|
||||||
Create
|
Create
|
||||||
Rename
|
Rename
|
||||||
|
|
||||||
code.gitea.io/gitea/models/forgefed
|
forgejo.org/models/forgefed
|
||||||
GetFederationHost
|
GetFederationHost
|
||||||
|
|
||||||
code.gitea.io/gitea/models/forgejo/semver
|
forgejo.org/models/forgejo/semver
|
||||||
GetVersion
|
GetVersion
|
||||||
SetVersionString
|
SetVersionString
|
||||||
SetVersion
|
SetVersion
|
||||||
|
|
||||||
code.gitea.io/gitea/models/git
|
forgejo.org/models/git
|
||||||
RemoveDeletedBranchByID
|
RemoveDeletedBranchByID
|
||||||
|
|
||||||
code.gitea.io/gitea/models/issues
|
forgejo.org/models/issues
|
||||||
IsErrUnknownDependencyType
|
IsErrUnknownDependencyType
|
||||||
ErrNewIssueInsert.Error
|
|
||||||
IsErrIssueWasClosed
|
IsErrIssueWasClosed
|
||||||
ChangeMilestoneStatus
|
|
||||||
|
|
||||||
code.gitea.io/gitea/models/organization
|
forgejo.org/models/organization
|
||||||
GetTeamNamesByID
|
|
||||||
UpdateTeamUnits
|
|
||||||
SearchMembersOptions.ToConds
|
SearchMembersOptions.ToConds
|
||||||
UsersInTeamsCount
|
|
||||||
|
|
||||||
code.gitea.io/gitea/models/perm/access
|
forgejo.org/models/perm/access
|
||||||
GetRepoWriters
|
GetRepoWriters
|
||||||
|
|
||||||
code.gitea.io/gitea/models/project
|
forgejo.org/models/repo
|
||||||
UpdateColumnSorting
|
|
||||||
ChangeProjectStatus
|
|
||||||
|
|
||||||
code.gitea.io/gitea/models/repo
|
|
||||||
DeleteAttachmentsByIssue
|
|
||||||
FindReposMapByIDs
|
|
||||||
IsErrTopicNotExist
|
|
||||||
ErrTopicNotExist.Error
|
|
||||||
ErrTopicNotExist.Unwrap
|
|
||||||
GetTopicByName
|
|
||||||
WatchRepoMode
|
WatchRepoMode
|
||||||
|
|
||||||
code.gitea.io/gitea/models/user
|
forgejo.org/models/user
|
||||||
ErrUserInactive.Error
|
|
||||||
ErrUserInactive.Unwrap
|
|
||||||
IsErrExternalLoginUserAlreadyExist
|
IsErrExternalLoginUserAlreadyExist
|
||||||
IsErrExternalLoginUserNotExist
|
IsErrExternalLoginUserNotExist
|
||||||
NewFederatedUser
|
NewFederatedUser
|
||||||
|
NewFederatedUserFollower
|
||||||
IsErrUserSettingIsNotExist
|
IsErrUserSettingIsNotExist
|
||||||
GetUserAllSettings
|
GetUserAllSettings
|
||||||
DeleteUserSetting
|
DeleteUserSetting
|
||||||
GetUserEmailsByNames
|
GetFederatedUser
|
||||||
GetUserNamesByIDs
|
GetFederatedUserByUserID
|
||||||
|
UpdateFederatedUser
|
||||||
|
GetFollowersForUser
|
||||||
|
AddFollower
|
||||||
|
RemoveFollower
|
||||||
|
IsFollowingAp
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/activitypub
|
forgejo.org/modules/activitypub
|
||||||
NewContext
|
NewContext
|
||||||
Context.APClientFactory
|
Context.APClientFactory
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/assetfs
|
forgejo.org/modules/assetfs
|
||||||
Bindata
|
Bindata
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/auth/password/hash
|
forgejo.org/modules/auth/password/hash
|
||||||
DummyHasher.HashWithSaltBytes
|
DummyHasher.HashWithSaltBytes
|
||||||
NewDummyHasher
|
NewDummyHasher
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/auth/password/pwn
|
forgejo.org/modules/auth/password/pwn
|
||||||
WithHTTP
|
WithHTTP
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/base
|
forgejo.org/modules/base
|
||||||
SetupGiteaRoot
|
SetupGiteaRoot
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/cache
|
forgejo.org/modules/cache
|
||||||
GetInt
|
GetInt
|
||||||
WithNoCacheContext
|
WithNoCacheContext
|
||||||
RemoveContextData
|
RemoveContextData
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/charset
|
forgejo.org/modules/emoji
|
||||||
BreakWriter.Write
|
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/emoji
|
|
||||||
ReplaceCodes
|
ReplaceCodes
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/eventsource
|
forgejo.org/modules/eventsource
|
||||||
Event.String
|
Event.String
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/forgefed
|
forgejo.org/modules/forgefed
|
||||||
|
NewForgeFollowFromAp
|
||||||
|
NewForgeFollow
|
||||||
|
ForgeFollow.MarshalJSON
|
||||||
|
ForgeFollow.UnmarshalJSON
|
||||||
|
ForgeFollow.Validate
|
||||||
|
NewForgeUndoLike
|
||||||
|
ForgeUndoLike.UnmarshalJSON
|
||||||
|
ForgeUndoLike.Validate
|
||||||
|
NewForgeUserActivityFromAp
|
||||||
|
NewForgeUserActivity
|
||||||
|
ForgeUserActivity.Validate
|
||||||
|
NewPersonIDFromModel
|
||||||
GetItemByType
|
GetItemByType
|
||||||
JSONUnmarshalerFn
|
JSONUnmarshalerFn
|
||||||
NotEmpty
|
NotEmpty
|
||||||
|
NewForgeUserActivityNoteFromAp
|
||||||
|
newNote
|
||||||
|
ForgeUserActivityNote.Validate
|
||||||
ToRepository
|
ToRepository
|
||||||
OnRepository
|
OnRepository
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/git
|
forgejo.org/modules/git
|
||||||
AllowLFSFiltersArgs
|
AllowLFSFiltersArgs
|
||||||
AddChanges
|
AddChanges
|
||||||
AddChangesWithArgs
|
AddChangesWithArgs
|
||||||
|
@ -138,71 +132,57 @@ code.gitea.io/gitea/modules/git
|
||||||
CommitChangesWithArgs
|
CommitChangesWithArgs
|
||||||
SetUpdateHook
|
SetUpdateHook
|
||||||
openRepositoryWithDefaultContext
|
openRepositoryWithDefaultContext
|
||||||
IsTagExist
|
|
||||||
ToEntryMode
|
ToEntryMode
|
||||||
LimitedReaderCloser.Read
|
|
||||||
LimitedReaderCloser.Close
|
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/gitgraph
|
forgejo.org/modules/gitrepo
|
||||||
Parser.Reset
|
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/gitrepo
|
|
||||||
GetBranchCommitID
|
GetBranchCommitID
|
||||||
GetWikiDefaultBranch
|
GetWikiDefaultBranch
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/graceful
|
forgejo.org/modules/graceful
|
||||||
Manager.TerminateContext
|
Manager.TerminateContext
|
||||||
Manager.Err
|
Manager.Err
|
||||||
Manager.Value
|
Manager.Value
|
||||||
Manager.Deadline
|
Manager.Deadline
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/hcaptcha
|
forgejo.org/modules/hcaptcha
|
||||||
WithHTTP
|
WithHTTP
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/hostmatcher
|
forgejo.org/modules/hostmatcher
|
||||||
HostMatchList.AppendPattern
|
HostMatchList.AppendPattern
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/json
|
forgejo.org/modules/json
|
||||||
StdJSON.Marshal
|
StdJSON.Marshal
|
||||||
StdJSON.Unmarshal
|
StdJSON.Unmarshal
|
||||||
StdJSON.NewEncoder
|
StdJSON.NewEncoder
|
||||||
StdJSON.NewDecoder
|
StdJSON.NewDecoder
|
||||||
StdJSON.Indent
|
StdJSON.Indent
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/markup
|
forgejo.org/modules/log
|
||||||
|
NewEventWriterBuffer
|
||||||
|
|
||||||
|
forgejo.org/modules/markup
|
||||||
GetRendererByType
|
GetRendererByType
|
||||||
RenderString
|
RenderString
|
||||||
IsMarkupFile
|
IsMarkupFile
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/markup/console
|
forgejo.org/modules/markup/console
|
||||||
Render
|
Render
|
||||||
RenderString
|
RenderString
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/markup/markdown
|
forgejo.org/modules/markup/markdown
|
||||||
IsDetails
|
|
||||||
IsSummary
|
|
||||||
IsTaskCheckBoxListItem
|
|
||||||
IsIcon
|
|
||||||
RenderRawString
|
RenderRawString
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/markup/markdown/math
|
forgejo.org/modules/markup/mdstripper
|
||||||
WithInlineDollarParser
|
|
||||||
WithBlockDollarParser
|
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/markup/mdstripper
|
|
||||||
stripRenderer.AddOptions
|
stripRenderer.AddOptions
|
||||||
StripMarkdown
|
StripMarkdown
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/markup/orgmode
|
forgejo.org/modules/markup/orgmode
|
||||||
RenderString
|
RenderString
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/private
|
forgejo.org/modules/process
|
||||||
ActionsRunnerRegister
|
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/process
|
|
||||||
Manager.ExecTimeout
|
Manager.ExecTimeout
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/queue
|
forgejo.org/modules/queue
|
||||||
newBaseChannelSimple
|
newBaseChannelSimple
|
||||||
newBaseChannelUnique
|
newBaseChannelUnique
|
||||||
newBaseRedisSimple
|
newBaseRedisSimple
|
||||||
|
@ -211,89 +191,73 @@ code.gitea.io/gitea/modules/queue
|
||||||
testStateRecorder.Reset
|
testStateRecorder.Reset
|
||||||
newWorkerPoolQueueForTest
|
newWorkerPoolQueueForTest
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/queue/lqinternal
|
forgejo.org/modules/queue/lqinternal
|
||||||
QueueItemIDBytes
|
QueueItemIDBytes
|
||||||
QueueItemKeyBytes
|
QueueItemKeyBytes
|
||||||
ListLevelQueueKeys
|
ListLevelQueueKeys
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/setting
|
forgejo.org/modules/setting
|
||||||
NewConfigProviderFromData
|
NewConfigProviderFromData
|
||||||
GitConfigType.GetOption
|
GitConfigType.GetOption
|
||||||
InitLoggersForTest
|
InitLoggersForTest
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/storage
|
forgejo.org/modules/sync
|
||||||
ErrInvalidConfiguration.Error
|
|
||||||
IsErrInvalidConfiguration
|
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/structs
|
|
||||||
ParseCreateHook
|
|
||||||
ParsePushHook
|
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/sync
|
|
||||||
StatusTable.Start
|
StatusTable.Start
|
||||||
StatusTable.IsRunning
|
StatusTable.IsRunning
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/timeutil
|
forgejo.org/modules/timeutil
|
||||||
GetExecutableModTime
|
GetExecutableModTime
|
||||||
MockSet
|
MockSet
|
||||||
MockUnset
|
MockUnset
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/translation
|
forgejo.org/modules/translation
|
||||||
MockLocale.Language
|
MockLocale.Language
|
||||||
MockLocale.TrString
|
MockLocale.TrString
|
||||||
MockLocale.Tr
|
MockLocale.Tr
|
||||||
MockLocale.TrN
|
MockLocale.TrN
|
||||||
|
MockLocale.TrPluralString
|
||||||
|
MockLocale.TrPluralStringAllForms
|
||||||
MockLocale.TrSize
|
MockLocale.TrSize
|
||||||
|
MockLocale.HasKey
|
||||||
MockLocale.PrettyNumber
|
MockLocale.PrettyNumber
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/util
|
forgejo.org/modules/translation/localeiter
|
||||||
|
IterateMessagesContent
|
||||||
|
|
||||||
|
forgejo.org/modules/util
|
||||||
OptionalArg
|
OptionalArg
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/util/filebuffer
|
forgejo.org/modules/util/filebuffer
|
||||||
CreateFromReader
|
CreateFromReader
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/validation
|
forgejo.org/modules/validation
|
||||||
IsErrNotValid
|
IsErrNotValid
|
||||||
|
ValidateIDExists
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/web
|
forgejo.org/modules/web
|
||||||
RouteMock
|
RouteMock
|
||||||
RouteMockReset
|
RouteMockReset
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/web/middleware
|
forgejo.org/modules/zstd
|
||||||
DeleteLocaleCookie
|
|
||||||
|
|
||||||
code.gitea.io/gitea/modules/zstd
|
|
||||||
NewWriter
|
NewWriter
|
||||||
Writer.Write
|
Writer.Write
|
||||||
Writer.Close
|
Writer.Close
|
||||||
|
|
||||||
code.gitea.io/gitea/routers/web
|
forgejo.org/routers/web/org
|
||||||
NotFound
|
|
||||||
|
|
||||||
code.gitea.io/gitea/routers/web/org
|
|
||||||
MustEnableProjects
|
MustEnableProjects
|
||||||
|
|
||||||
code.gitea.io/gitea/services/context
|
forgejo.org/services/context
|
||||||
GetPrivateContext
|
GetPrivateContext
|
||||||
|
|
||||||
code.gitea.io/gitea/services/convert
|
forgejo.org/services/repository
|
||||||
ToSecret
|
|
||||||
|
|
||||||
code.gitea.io/gitea/services/forms
|
|
||||||
DeadlineForm.Validate
|
|
||||||
|
|
||||||
code.gitea.io/gitea/services/pull
|
|
||||||
IsCommitStatusContextSuccess
|
|
||||||
|
|
||||||
code.gitea.io/gitea/services/repository
|
|
||||||
IsErrForkAlreadyExist
|
IsErrForkAlreadyExist
|
||||||
|
|
||||||
code.gitea.io/gitea/services/repository/files
|
forgejo.org/services/repository/files
|
||||||
ContentType.String
|
ContentType.String
|
||||||
GetFileResponseFromCommit
|
|
||||||
TemporaryUploadRepository.GetLastCommit
|
|
||||||
TemporaryUploadRepository.GetLastCommitByRef
|
|
||||||
|
|
||||||
code.gitea.io/gitea/services/webhook
|
forgejo.org/services/repository/gitgraph
|
||||||
|
Parser.Reset
|
||||||
|
|
||||||
|
forgejo.org/services/webhook
|
||||||
NewNotifier
|
NewNotifier
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "Gitea DevContainer",
|
"name": "Gitea DevContainer",
|
||||||
"image": "mcr.microsoft.com/devcontainers/go:1.23-bullseye",
|
"image": "mcr.microsoft.com/devcontainers/go:1.24-bullseye",
|
||||||
"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": "22"
|
||||||
},
|
|
||||||
"ghcr.io/devcontainers/features/git-lfs:1.2.3": {},
|
|
||||||
"ghcr.io/devcontainers-contrib/features/poetry:2": {},
|
|
||||||
"ghcr.io/devcontainers/features/python:1": {
|
|
||||||
"version": "3.12"
|
|
||||||
},
|
},
|
||||||
|
"ghcr.io/devcontainers/features/git-lfs:1.2.5": {},
|
||||||
"ghcr.io/warrenbuckley/codespace-features/sqlite:1": {}
|
"ghcr.io/warrenbuckley/codespace-features/sqlite:1": {}
|
||||||
},
|
},
|
||||||
"customizations": {
|
"customizations": {
|
||||||
|
|
|
@ -34,15 +34,12 @@ _testmain.go
|
||||||
|
|
||||||
*coverage.out
|
*coverage.out
|
||||||
coverage.all
|
coverage.all
|
||||||
|
coverage/
|
||||||
cpu.out
|
cpu.out
|
||||||
|
|
||||||
/modules/migration/bindata.go
|
|
||||||
/modules/migration/bindata.go.hash
|
/modules/migration/bindata.go.hash
|
||||||
/modules/options/bindata.go
|
|
||||||
/modules/options/bindata.go.hash
|
/modules/options/bindata.go.hash
|
||||||
/modules/public/bindata.go
|
|
||||||
/modules/public/bindata.go.hash
|
/modules/public/bindata.go.hash
|
||||||
/modules/templates/bindata.go
|
|
||||||
/modules/templates/bindata.go.hash
|
/modules/templates/bindata.go.hash
|
||||||
|
|
||||||
*.db
|
*.db
|
||||||
|
|
|
@ -12,6 +12,9 @@ insert_final_newline = true
|
||||||
[{*.{go,tmpl,html},Makefile,go.mod}]
|
[{*.{go,tmpl,html},Makefile,go.mod}]
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
|
|
||||||
|
[go.*]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
[templates/custom/*.tmpl]
|
[templates/custom/*.tmpl]
|
||||||
insert_final_newline = false
|
insert_final_newline = false
|
||||||
|
|
||||||
|
@ -26,3 +29,8 @@ insert_final_newline = false
|
||||||
|
|
||||||
[options/locale/locale_*.ini]
|
[options/locale/locale_*.ini]
|
||||||
insert_final_newline = false
|
insert_final_newline = false
|
||||||
|
|
||||||
|
# Weblate JSON output defaults to four spaces
|
||||||
|
[options/locale_next/locale_*.json]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
|
|
@ -6,7 +6,7 @@ body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
**NOTE: If your issue is a security concern, please email <security@forgejo.org> (GPG: `A4676E79`) instead of opening a public issue.**
|
**NOTE: If your issue is a security concern, please email <security@forgejo.org> ([security.txt](https://forgejo.org/.well-known/security.txt)) instead of opening a public issue.**
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
|
|
|
@ -6,7 +6,7 @@ body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
**NOTE: If your issue is a security concern, please email <security@forgejo.org> (GPG: `A4676E79`) instead of opening a public issue.**
|
**NOTE: If your issue is a security concern, please email <security@forgejo.org> ([security.txt](https://forgejo.org/.well-known/security.txt)) instead of opening a public issue.**
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
|
|
2
.forgejo/testdata/build-release/Dockerfile
vendored
2
.forgejo/testdata/build-release/Dockerfile
vendored
|
@ -1,4 +1,4 @@
|
||||||
FROM data.forgejo.org/oci/alpine:3.20
|
FROM data.forgejo.org/oci/alpine:3.22
|
||||||
ARG RELEASE_VERSION=unkown
|
ARG RELEASE_VERSION=unkown
|
||||||
LABEL maintainer="contact@forgejo.org" \
|
LABEL maintainer="contact@forgejo.org" \
|
||||||
org.opencontainers.image.version="${RELEASE_VERSION}"
|
org.opencontainers.image.version="${RELEASE_VERSION}"
|
||||||
|
|
2
.forgejo/testdata/build-release/go.mod
vendored
2
.forgejo/testdata/build-release/go.mod
vendored
|
@ -1,3 +1,3 @@
|
||||||
module code.gitea.io/gitea
|
module forgejo.org
|
||||||
|
|
||||||
go 1.23.3
|
go 1.23.3
|
||||||
|
|
|
@ -18,7 +18,7 @@ runs:
|
||||||
- name: install packages
|
- name: install packages
|
||||||
run: |
|
run: |
|
||||||
apt-get update -qq
|
apt-get update -qq
|
||||||
apt-get -q install -qq -y ${PACKAGES}
|
apt-get -q install --allow-downgrades -qq -y ${PACKAGES}
|
||||||
env:
|
env:
|
||||||
PACKAGES: ${{inputs.packages}}
|
PACKAGES: ${{inputs.packages}}
|
||||||
- name: remove temporary package list to prevent using it in other steps
|
- name: remove temporary package list to prevent using it in other steps
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
#
|
||||||
|
# Install the minimal version of Git supported by Forgejo
|
||||||
|
#
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
- name: install git and git-lfs
|
||||||
|
run: |
|
||||||
|
set -x
|
||||||
|
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get -q install -y -qq curl ca-certificates
|
||||||
|
|
||||||
|
curl -sS -o /tmp/git-man.deb http://archive.ubuntu.com/ubuntu/pool/main/g/git/git-man_2.34.1-1ubuntu1_all.deb
|
||||||
|
curl -sS -o /tmp/git.deb https://archive.ubuntu.com/ubuntu/pool/main/g/git/git_2.34.1-1ubuntu1_amd64.deb
|
||||||
|
curl -sS -o /tmp/git-lfs.deb https://archive.ubuntu.com/ubuntu/pool/universe/g/git-lfs/git-lfs_3.0.2-1_amd64.deb
|
||||||
|
|
||||||
|
apt-get -q install --allow-downgrades -y -qq /tmp/git-man.deb
|
||||||
|
apt-get -q install --allow-downgrades -y -qq /tmp/git.deb
|
||||||
|
apt-get -q install --allow-downgrades -y -qq /tmp/git-lfs.deb
|
|
@ -40,14 +40,14 @@ jobs:
|
||||||
)
|
)
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: 'data.forgejo.org/oci/node:20-bookworm'
|
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||||
steps:
|
steps:
|
||||||
- name: event info
|
- name: event info
|
||||||
run: |
|
run: |
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
${{ toJSON(github) }}
|
${{ toJSON(github) }}
|
||||||
EOF
|
EOF
|
||||||
- uses: https://data.forgejo.org/actions/git-backporting@v4.8.4
|
- uses: https://data.forgejo.org/actions/git-backporting@v4.8.5
|
||||||
with:
|
with:
|
||||||
target-branch-pattern: "^backport/(?<target>(v.*))$"
|
target-branch-pattern: "^backport/(?<target>(v.*))$"
|
||||||
strategy: ort
|
strategy: ort
|
||||||
|
|
|
@ -45,7 +45,7 @@ jobs:
|
||||||
|
|
||||||
- uses: https://data.forgejo.org/actions/setup-node@v4
|
- uses: https://data.forgejo.org/actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
|
|
||||||
- uses: https://data.forgejo.org/actions/setup-go@v5
|
- uses: https://data.forgejo.org/actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
|
@ -164,7 +164,7 @@ jobs:
|
||||||
|
|
||||||
- name: build container & release
|
- name: build container & release
|
||||||
if: ${{ secrets.TOKEN != '' }}
|
if: ${{ secrets.TOKEN != '' }}
|
||||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.3.4
|
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.3.5
|
||||||
with:
|
with:
|
||||||
forgejo: "${{ env.GITHUB_SERVER_URL }}"
|
forgejo: "${{ env.GITHUB_SERVER_URL }}"
|
||||||
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
|
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
|
||||||
|
@ -183,7 +183,7 @@ jobs:
|
||||||
|
|
||||||
- name: build rootless container
|
- name: build rootless container
|
||||||
if: ${{ secrets.TOKEN != '' }}
|
if: ${{ secrets.TOKEN != '' }}
|
||||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.3.4
|
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.3.5
|
||||||
with:
|
with:
|
||||||
forgejo: "${{ env.GITHUB_SERVER_URL }}"
|
forgejo: "${{ env.GITHUB_SERVER_URL }}"
|
||||||
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
|
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
|
||||||
|
|
|
@ -35,7 +35,7 @@ jobs:
|
||||||
)
|
)
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: data.forgejo.org/oci/node:20-bookworm
|
image: data.forgejo.org/oci/node:22-bookworm
|
||||||
steps:
|
steps:
|
||||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
|
|
@ -9,7 +9,7 @@ jobs:
|
||||||
if: vars.ROLE == 'forgejo-integration'
|
if: vars.ROLE == 'forgejo-integration'
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: 'data.forgejo.org/oci/node:20-bookworm'
|
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: apt install curl jq
|
- name: apt install curl jq
|
||||||
|
|
|
@ -16,7 +16,7 @@ jobs:
|
||||||
if: vars.ROLE == 'forgejo-coding'
|
if: vars.ROLE == 'forgejo-coding'
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: 'data.forgejo.org/oci/node:20-bookworm'
|
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||||
steps:
|
steps:
|
||||||
- name: Debug output
|
- name: Debug output
|
||||||
run: |
|
run: |
|
||||||
|
@ -31,7 +31,7 @@ jobs:
|
||||||
|| contains(toJSON(github.event.pull_request.labels), 'test/manual')
|
|| contains(toJSON(github.event.pull_request.labels), 'test/manual')
|
||||||
)
|
)
|
||||||
run: |
|
run: |
|
||||||
echo "Test label must be set to either 'present', 'not-needed' or 'manual'."
|
echo "A team member must set the label to either 'present', 'not-needed' or 'manual'."
|
||||||
exit 1
|
exit 1
|
||||||
- name: Missing manual test instructions
|
- name: Missing manual test instructions
|
||||||
if: >
|
if: >
|
||||||
|
|
|
@ -11,7 +11,7 @@ jobs:
|
||||||
if: ${{ secrets.MIRROR_TOKEN != '' }}
|
if: ${{ secrets.MIRROR_TOKEN != '' }}
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: 'data.forgejo.org/oci/node:20-bookworm'
|
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||||
steps:
|
steps:
|
||||||
- name: git push {v*/,}forgejo
|
- name: git push {v*/,}forgejo
|
||||||
run: |
|
run: |
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
#
|
#
|
||||||
# See also https://forgejo.org/docs/next/contributor/release/#stable-release-process
|
# See also https://forgejo.org/docs/next/contributor/release/#stable-release-process
|
||||||
#
|
#
|
||||||
|
# TOKEN_NEXT_DIGEST is a token with write repository access to https://invisible.forgejo.org/infrastructure/next-digest issued by https://invisible.forgejo.org/forgejo-next-digest
|
||||||
|
#
|
||||||
# https://codeberg.org/forgejo-experimental/forgejo
|
# https://codeberg.org/forgejo-experimental/forgejo
|
||||||
#
|
#
|
||||||
# Copies a release from codeberg.org/forgejo-integration to codeberg.org/forgejo-experimental
|
# Copies a release from codeberg.org/forgejo-integration to codeberg.org/forgejo-experimental
|
||||||
|
@ -14,7 +16,7 @@
|
||||||
# vars.DOER: forgejo-experimental-ci
|
# vars.DOER: forgejo-experimental-ci
|
||||||
# secrets.TOKEN: <generated from codeberg.org/forgejo-experimental-ci>
|
# secrets.TOKEN: <generated from codeberg.org/forgejo-experimental-ci>
|
||||||
#
|
#
|
||||||
# http://private.forgejo.org/forgejo/forgejo
|
# http://invisible.forgejo.org/forgejo/forgejo
|
||||||
#
|
#
|
||||||
# Copies & sign a release from codeberg.org/forgejo-integration to codeberg.org/forgejo
|
# Copies & sign a release from codeberg.org/forgejo-integration to codeberg.org/forgejo
|
||||||
#
|
#
|
||||||
|
@ -42,7 +44,7 @@ jobs:
|
||||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||||
|
|
||||||
- name: copy & sign
|
- name: copy & sign
|
||||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.3.4
|
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.3.5
|
||||||
with:
|
with:
|
||||||
from-forgejo: ${{ vars.FORGEJO }}
|
from-forgejo: ${{ vars.FORGEJO }}
|
||||||
to-forgejo: ${{ vars.FORGEJO }}
|
to-forgejo: ${{ vars.FORGEJO }}
|
||||||
|
@ -61,14 +63,14 @@ jobs:
|
||||||
|
|
||||||
- name: get trigger mirror issue
|
- name: get trigger mirror issue
|
||||||
id: mirror
|
id: mirror
|
||||||
uses: https://data.forgejo.org/infrastructure/issue-action/get@v1.1.0
|
uses: https://data.forgejo.org/infrastructure/issue-action/get@v1.3.0
|
||||||
with:
|
with:
|
||||||
forgejo: https://code.forgejo.org
|
forgejo: https://code.forgejo.org
|
||||||
repository: forgejo/forgejo
|
repository: forgejo/forgejo
|
||||||
labels: mirror-trigger
|
labels: mirror-trigger
|
||||||
|
|
||||||
- name: trigger the mirror
|
- name: trigger the mirror
|
||||||
uses: https://data.forgejo.org/infrastructure/issue-action/set@v1.1.0
|
uses: https://data.forgejo.org/infrastructure/issue-action/set@v1.3.0
|
||||||
with:
|
with:
|
||||||
forgejo: https://code.forgejo.org
|
forgejo: https://code.forgejo.org
|
||||||
repository: forgejo/forgejo
|
repository: forgejo/forgejo
|
||||||
|
@ -80,7 +82,7 @@ jobs:
|
||||||
- name: upgrade v*.next.forgejo.org
|
- name: upgrade v*.next.forgejo.org
|
||||||
uses: https://data.forgejo.org/infrastructure/next-digest@v1.1.0
|
uses: https://data.forgejo.org/infrastructure/next-digest@v1.1.0
|
||||||
with:
|
with:
|
||||||
url: https://placeholder:${{ secrets.TOKEN_NEXT_DIGEST }}@code.forgejo.org/infrastructure/next-digest
|
url: https://placeholder:${{ secrets.TOKEN_NEXT_DIGEST }}@invisible.forgejo.org/infrastructure/next-digest
|
||||||
ref_name: '${{ github.ref_name }}'
|
ref_name: '${{ github.ref_name }}'
|
||||||
image: 'codeberg.org/forgejo-experimental/forgejo'
|
image: 'codeberg.org/forgejo-experimental/forgejo'
|
||||||
tag_suffix: '-rootless'
|
tag_suffix: '-rootless'
|
||||||
|
|
|
@ -4,12 +4,15 @@ on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '@daily'
|
- cron: '@daily'
|
||||||
|
|
||||||
|
env:
|
||||||
|
RNA_VERSION: v1.2.5 # renovate: datasource=gitea-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release-notes:
|
release-notes:
|
||||||
if: vars.ROLE == 'forgejo-coding'
|
if: vars.ROLE == 'forgejo-coding'
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: 'data.forgejo.org/oci/node:20-bookworm'
|
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||||
steps:
|
steps:
|
||||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||||
|
|
||||||
|
@ -29,5 +32,5 @@ jobs:
|
||||||
set -x
|
set -x
|
||||||
curl -sS $GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY/milestones?state=open | jq -r '.[] | .title' | while read forgejo version ; do
|
curl -sS $GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY/milestones?state=open | jq -r '.[] | .title' | while read forgejo version ; do
|
||||||
milestone="$forgejo $version"
|
milestone="$forgejo $version"
|
||||||
go run code.forgejo.org/forgejo/release-notes-assistant@v1.1.1 --config .release-notes-assistant.yaml --storage milestone --storage-location "$milestone" --forgejo-url $GITHUB_SERVER_URL --repository $GITHUB_REPOSITORY --token ${{ secrets.RELEASE_NOTES_ASSISTANT_TOKEN }} release $version
|
go run code.forgejo.org/forgejo/release-notes-assistant@$RNA_VERSION --config .release-notes-assistant.yaml --storage milestone --storage-location "$milestone" --forgejo-url $GITHUB_SERVER_URL --repository $GITHUB_REPOSITORY --token ${{ secrets.RELEASE_NOTES_ASSISTANT_TOKEN }} release $version
|
||||||
done
|
done
|
||||||
|
|
|
@ -7,12 +7,15 @@ on:
|
||||||
- synchronize
|
- synchronize
|
||||||
- labeled
|
- labeled
|
||||||
|
|
||||||
|
env:
|
||||||
|
RNA_VERSION: v1.2.5 # renovate: datasource=gitea-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release-notes:
|
release-notes:
|
||||||
if: ( vars.ROLE == 'forgejo-coding' ) && contains(github.event.pull_request.labels.*.name, 'worth a release-note')
|
if: ( vars.ROLE == 'forgejo-coding' ) && contains(github.event.pull_request.labels.*.name, 'worth a release-note')
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: 'data.forgejo.org/oci/node:20-bookworm'
|
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||||
steps:
|
steps:
|
||||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||||
|
|
||||||
|
@ -38,4 +41,4 @@ jobs:
|
||||||
|
|
||||||
- name: release-notes-assistant preview
|
- name: release-notes-assistant preview
|
||||||
run: |
|
run: |
|
||||||
go run code.forgejo.org/forgejo/release-notes-assistant@v1.1.1 --config .release-notes-assistant.yaml --storage pr --storage-location ${{ github.event.pull_request.number }} --forgejo-url $GITHUB_SERVER_URL --repository $GITHUB_REPOSITORY --token ${{ secrets.RELEASE_NOTES_ASSISTANT_TOKEN }} preview ${{ github.event.pull_request.number }}
|
go run code.forgejo.org/forgejo/release-notes-assistant@$RNA_VERSION --config .release-notes-assistant.yaml --storage pr --storage-location ${{ github.event.pull_request.number }} --forgejo-url $GITHUB_SERVER_URL --repository $GITHUB_REPOSITORY --token ${{ secrets.RELEASE_NOTES_ASSISTANT_TOKEN }} preview ${{ github.event.pull_request.number }}
|
||||||
|
|
|
@ -18,6 +18,9 @@ on:
|
||||||
env:
|
env:
|
||||||
RENOVATE_DRY_RUN: ${{ (github.event_name != 'schedule' && github.ref_name != github.event.repository.default_branch) && 'full' || '' }}
|
RENOVATE_DRY_RUN: ${{ (github.event_name != 'schedule' && github.ref_name != github.event.repository.default_branch) && 'full' || '' }}
|
||||||
RENOVATE_REPOSITORIES: ${{ github.repository }}
|
RENOVATE_REPOSITORIES: ${{ github.repository }}
|
||||||
|
# fix because 10.0.0-58-7e1df53+gitea-1.22.0 < 10.0.0 for semver
|
||||||
|
# and codeberg api returns such versions from `git describe --tags`
|
||||||
|
# RENOVATE_X_PLATFORM_VERSION: 10.0.0+gitea-1.22.0 currently not needed
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
renovate:
|
renovate:
|
||||||
|
@ -25,7 +28,7 @@ jobs:
|
||||||
|
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: data.forgejo.org/forgejo-contrib/renovate:39.69.2
|
image: data.forgejo.org/renovate/renovate:41.17.2
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Load renovate repo cache
|
- name: Load renovate repo cache
|
||||||
|
|
65
.forgejo/workflows/testing-integration.yml
Normal file
65
.forgejo/workflows/testing-integration.yml
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
#
|
||||||
|
# Additional integration tests designed to run once a day when
|
||||||
|
# `mirror.yml` pushes to https://codeberg.org/forgejo-integration/forgejo
|
||||||
|
# and send a notification via email should they fail.
|
||||||
|
#
|
||||||
|
# For debug purposes:
|
||||||
|
#
|
||||||
|
# - uncomment [on].pull_request
|
||||||
|
# - swap 'forgejo-integration' and 'forgejo-coding'
|
||||||
|
# - open a pull request at https://codeberg.org/forgejo/forgejo and fix things
|
||||||
|
# - swap 'forgejo-integration' and 'forgejo-coding'
|
||||||
|
# - comment [on].pull_request
|
||||||
|
#
|
||||||
|
|
||||||
|
name: testing-integration
|
||||||
|
|
||||||
|
on:
|
||||||
|
# pull_request:
|
||||||
|
push:
|
||||||
|
tags: 'v[0-9]+.[0-9]+.*'
|
||||||
|
branches:
|
||||||
|
- 'forgejo'
|
||||||
|
- 'v*/forgejo'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-unit:
|
||||||
|
# if: vars.ROLE == 'forgejo-coding'
|
||||||
|
if: vars.ROLE == 'forgejo-integration'
|
||||||
|
runs-on: docker
|
||||||
|
container:
|
||||||
|
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||||
|
options: --tmpfs /tmp:exec,noatime
|
||||||
|
steps:
|
||||||
|
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||||
|
- uses: ./.forgejo/workflows-composite/setup-env
|
||||||
|
- name: install git 2.34.1 and git-lfs 3.0.2
|
||||||
|
uses: ./.forgejo/workflows-composite/install-minimum-git-version
|
||||||
|
- uses: ./.forgejo/workflows-composite/build-backend
|
||||||
|
- run: |
|
||||||
|
su forgejo -c 'make test-backend test-check'
|
||||||
|
timeout-minutes: 120
|
||||||
|
env:
|
||||||
|
RACE_ENABLED: 'true'
|
||||||
|
TAGS: bindata
|
||||||
|
test-sqlite:
|
||||||
|
# if: vars.ROLE == 'forgejo-coding'
|
||||||
|
if: vars.ROLE == 'forgejo-integration'
|
||||||
|
runs-on: docker
|
||||||
|
container:
|
||||||
|
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||||
|
options: --tmpfs /tmp:exec,noatime
|
||||||
|
steps:
|
||||||
|
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||||
|
- uses: ./.forgejo/workflows-composite/setup-env
|
||||||
|
- name: install git 2.34.1 and git-lfs 3.0.2
|
||||||
|
uses: ./.forgejo/workflows-composite/install-minimum-git-version
|
||||||
|
- uses: ./.forgejo/workflows-composite/build-backend
|
||||||
|
- run: |
|
||||||
|
su forgejo -c 'make test-sqlite-migration test-sqlite'
|
||||||
|
timeout-minutes: 120
|
||||||
|
env:
|
||||||
|
TAGS: sqlite sqlite_unlock_notify
|
||||||
|
RACE_ENABLED: true
|
||||||
|
TEST_TAGS: sqlite sqlite_unlock_notify
|
||||||
|
USE_REPO_TEST_DIR: 1
|
|
@ -13,7 +13,7 @@ jobs:
|
||||||
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing'
|
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing'
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: 'data.forgejo.org/oci/node:20-bookworm'
|
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||||
options: --tmpfs /tmp:exec,noatime
|
options: --tmpfs /tmp:exec,noatime
|
||||||
steps:
|
steps:
|
||||||
- name: event info
|
- name: event info
|
||||||
|
@ -24,13 +24,13 @@ jobs:
|
||||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||||
- uses: ./.forgejo/workflows-composite/setup-env
|
- uses: ./.forgejo/workflows-composite/setup-env
|
||||||
- run: su forgejo -c 'make deps-backend deps-tools'
|
- run: su forgejo -c 'make deps-backend deps-tools'
|
||||||
- run: su forgejo -c 'make --always-make -j$(nproc) lint-backend tidy-check swagger-check fmt-check swagger-validate' # ensure the "go-licenses" make target runs
|
- run: su forgejo -c 'make --always-make -j$(nproc) lint-backend tidy-check swagger-check lint-swagger fmt-check swagger-validate' # ensure the "go-licenses" make target runs
|
||||||
- uses: ./.forgejo/workflows-composite/build-backend
|
- uses: ./.forgejo/workflows-composite/build-backend
|
||||||
frontend-checks:
|
frontend-checks:
|
||||||
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing'
|
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing'
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: 'data.forgejo.org/oci/node:20-bookworm'
|
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||||
options: --tmpfs /tmp:exec,noatime
|
options: --tmpfs /tmp:exec,noatime
|
||||||
steps:
|
steps:
|
||||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||||
|
@ -55,7 +55,7 @@ jobs:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
needs: [backend-checks, frontend-checks]
|
needs: [backend-checks, frontend-checks]
|
||||||
container:
|
container:
|
||||||
image: 'data.forgejo.org/oci/node:20-bookworm'
|
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||||
options: --tmpfs /tmp:exec,noatime
|
options: --tmpfs /tmp:exec,noatime
|
||||||
services:
|
services:
|
||||||
elasticsearch:
|
elasticsearch:
|
||||||
|
@ -91,6 +91,7 @@ jobs:
|
||||||
RACE_ENABLED: 'true'
|
RACE_ENABLED: 'true'
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
TEST_ELASTICSEARCH_URL: http://elasticsearch:9200
|
TEST_ELASTICSEARCH_URL: http://elasticsearch:9200
|
||||||
|
TEST_MINIO_ENDPOINT: minio:9000
|
||||||
test-e2e:
|
test-e2e:
|
||||||
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing'
|
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing'
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
|
@ -114,9 +115,14 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
su forgejo -c 'make deps-frontend frontend'
|
su forgejo -c 'make deps-frontend frontend'
|
||||||
- uses: ./.forgejo/workflows-composite/build-backend
|
- uses: ./.forgejo/workflows-composite/build-backend
|
||||||
|
- name: Decide to run all tests
|
||||||
|
id: run-all
|
||||||
|
if: contains(github.event.pull_request.labels.*.name, 'run-all-playwright-tests') || contains(github.event.pull_request.title, 'playwright')
|
||||||
|
run: |
|
||||||
|
echo "all=1" >> "$GITHUB_OUTPUT"
|
||||||
- name: Get changed files
|
- name: Get changed files
|
||||||
id: changed-files
|
id: changed-files
|
||||||
uses: https://data.forgejo.org/tj-actions/changed-files@v45
|
uses: https://data.forgejo.org/tj-actions/changed-files@v46
|
||||||
with:
|
with:
|
||||||
separator: '\n'
|
separator: '\n'
|
||||||
- run: |
|
- run: |
|
||||||
|
@ -126,6 +132,7 @@ jobs:
|
||||||
USE_REPO_TEST_DIR: 1
|
USE_REPO_TEST_DIR: 1
|
||||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||||
CHANGED_FILES: ${{steps.changed-files.outputs.all_changed_files}}
|
CHANGED_FILES: ${{steps.changed-files.outputs.all_changed_files}}
|
||||||
|
RUN_ALL: ${{steps.run-all.all}}
|
||||||
- name: Upload test artifacts on failure
|
- name: Upload test artifacts on failure
|
||||||
if: failure()
|
if: failure()
|
||||||
uses: https://data.forgejo.org/forgejo/upload-artifact@v4
|
uses: https://data.forgejo.org/forgejo/upload-artifact@v4
|
||||||
|
@ -138,7 +145,7 @@ jobs:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
needs: [backend-checks, frontend-checks, test-unit]
|
needs: [backend-checks, frontend-checks, test-unit]
|
||||||
container:
|
container:
|
||||||
image: 'data.forgejo.org/oci/node:20-bookworm'
|
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||||
options: --tmpfs /tmp:exec,noatime
|
options: --tmpfs /tmp:exec,noatime
|
||||||
name: ${{ format('test-remote-cacher ({0})', matrix.cacher.name) }}
|
name: ${{ format('test-remote-cacher ({0})', matrix.cacher.name) }}
|
||||||
strategy:
|
strategy:
|
||||||
|
@ -180,7 +187,7 @@ jobs:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
needs: [backend-checks, frontend-checks]
|
needs: [backend-checks, frontend-checks]
|
||||||
container:
|
container:
|
||||||
image: 'data.forgejo.org/oci/node:20-bookworm'
|
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||||
options: --tmpfs /tmp:exec,noatime
|
options: --tmpfs /tmp:exec,noatime
|
||||||
services:
|
services:
|
||||||
mysql:
|
mysql:
|
||||||
|
@ -211,7 +218,7 @@ jobs:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
needs: [backend-checks, frontend-checks]
|
needs: [backend-checks, frontend-checks]
|
||||||
container:
|
container:
|
||||||
image: 'data.forgejo.org/oci/node:20-bookworm'
|
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||||
options: --tmpfs /tmp:exec,noatime
|
options: --tmpfs /tmp:exec,noatime
|
||||||
services:
|
services:
|
||||||
minio:
|
minio:
|
||||||
|
@ -223,7 +230,7 @@ jobs:
|
||||||
ldap:
|
ldap:
|
||||||
image: data.forgejo.org/oci/test-openldap:latest
|
image: data.forgejo.org/oci/test-openldap:latest
|
||||||
pgsql:
|
pgsql:
|
||||||
image: data.forgejo.org/oci/bitnami/postgresql:15
|
image: data.forgejo.org/oci/bitnami/postgresql:16
|
||||||
env:
|
env:
|
||||||
POSTGRESQL_DATABASE: test
|
POSTGRESQL_DATABASE: test
|
||||||
POSTGRESQL_PASSWORD: postgres
|
POSTGRESQL_PASSWORD: postgres
|
||||||
|
@ -250,7 +257,7 @@ jobs:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
needs: [backend-checks, frontend-checks]
|
needs: [backend-checks, frontend-checks]
|
||||||
container:
|
container:
|
||||||
image: 'data.forgejo.org/oci/node:20-bookworm'
|
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||||
options: --tmpfs /tmp:exec,noatime
|
options: --tmpfs /tmp:exec,noatime
|
||||||
steps:
|
steps:
|
||||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||||
|
@ -278,7 +285,7 @@ jobs:
|
||||||
- test-remote-cacher
|
- test-remote-cacher
|
||||||
- test-unit
|
- test-unit
|
||||||
container:
|
container:
|
||||||
image: 'data.forgejo.org/oci/node:20-bookworm'
|
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||||
options: --tmpfs /tmp:exec,noatime
|
options: --tmpfs /tmp:exec,noatime
|
||||||
steps:
|
steps:
|
||||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -37,6 +37,7 @@ _testmain.go
|
||||||
|
|
||||||
*coverage.out
|
*coverage.out
|
||||||
coverage.all
|
coverage.all
|
||||||
|
coverage/
|
||||||
cpu.out
|
cpu.out
|
||||||
|
|
||||||
/modules/migration/bindata.go
|
/modules/migration/bindata.go
|
||||||
|
@ -56,6 +57,7 @@ cpu.out
|
||||||
/gitea-vet
|
/gitea-vet
|
||||||
/debug
|
/debug
|
||||||
/integrations.test
|
/integrations.test
|
||||||
|
/forgejo
|
||||||
|
|
||||||
/bin
|
/bin
|
||||||
/dist
|
/dist
|
||||||
|
|
271
.golangci.yml
271
.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,142 +11,155 @@ linters:
|
||||||
- errcheck
|
- errcheck
|
||||||
- forbidigo
|
- forbidigo
|
||||||
- gocritic
|
- gocritic
|
||||||
- gofmt
|
|
||||||
- gofumpt
|
|
||||||
- gosimple
|
|
||||||
- govet
|
- govet
|
||||||
- ineffassign
|
- ineffassign
|
||||||
- nakedret
|
- nakedret
|
||||||
- nolintlint
|
- nolintlint
|
||||||
- revive
|
- revive
|
||||||
- staticcheck
|
- staticcheck
|
||||||
- stylecheck
|
|
||||||
- tenv
|
|
||||||
- testifylint
|
- testifylint
|
||||||
- typecheck
|
|
||||||
- unconvert
|
- unconvert
|
||||||
- unused
|
|
||||||
- unparam
|
- unparam
|
||||||
|
- unused
|
||||||
|
- 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
|
||||||
stylecheck:
|
desc: use os or io instead
|
||||||
checks: ["all", "-ST1005", "-ST1003"]
|
- pkg: golang.org/x/exp
|
||||||
nakedret:
|
desc: it's experimental and unreliable
|
||||||
max-func-lines: 0
|
- pkg: forgejo.org/modules/git/internal
|
||||||
gocritic:
|
desc: do not use the internal package, use AddXxx function instead
|
||||||
disabled-checks:
|
- pkg: gopkg.in/ini.v1
|
||||||
- ifElseChain
|
desc: do not use the ini package, use gitea's config system instead
|
||||||
revive:
|
- pkg: github.com/minio/sha256-simd
|
||||||
severity: error
|
desc: use crypto/sha256 instead, see https://codeberg.org/forgejo/forgejo/pulls/1528
|
||||||
|
gocritic:
|
||||||
|
disabled-checks:
|
||||||
|
- ifElseChain
|
||||||
|
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
|
||||||
|
- name: redefines-builtin-id
|
||||||
|
disabled: true
|
||||||
|
staticcheck:
|
||||||
|
checks:
|
||||||
|
- all
|
||||||
|
testifylint:
|
||||||
|
disable:
|
||||||
|
- go-require
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
presets:
|
||||||
|
- comments
|
||||||
|
- common-false-positives
|
||||||
|
- legacy
|
||||||
|
- std-error-handling
|
||||||
rules:
|
rules:
|
||||||
- name: atomic
|
- linters:
|
||||||
- name: bare-return
|
- nolintlint
|
||||||
- name: blank-imports
|
path: models/db/sql_postgres_with_schema.go
|
||||||
- name: constant-logical-expr
|
- linters:
|
||||||
- name: context-as-argument
|
- dupl
|
||||||
- name: context-keys-type
|
- errcheck
|
||||||
- name: dot-imports
|
- gocyclo
|
||||||
- name: duplicated-imports
|
- gosec
|
||||||
- name: empty-lines
|
- staticcheck
|
||||||
- name: error-naming
|
- unparam
|
||||||
- name: error-return
|
path: _test\.go
|
||||||
- name: error-strings
|
- linters:
|
||||||
- name: errorf
|
- dupl
|
||||||
- name: exported
|
- errcheck
|
||||||
- name: identical-branches
|
- gocyclo
|
||||||
- name: if-return
|
- gosec
|
||||||
- name: increment-decrement
|
path: models/migrations/v
|
||||||
- name: indent-error-flow
|
- linters:
|
||||||
- name: modifies-value-receiver
|
- forbidigo
|
||||||
- name: package-comments
|
path: cmd
|
||||||
- name: range
|
- linters:
|
||||||
- name: receiver-naming
|
- dupl
|
||||||
- name: redefines-builtin-id
|
text: (?i)webhook
|
||||||
- name: string-of-int
|
- linters:
|
||||||
- name: superfluous-else
|
- gocritic
|
||||||
- name: time-naming
|
text: (?i)`ID' should not be capitalized
|
||||||
- name: unconditional-recursion
|
- linters:
|
||||||
- name: unexported-return
|
- deadcode
|
||||||
- name: unreachable-code
|
- unused
|
||||||
- name: var-declaration
|
text: (?i)swagger
|
||||||
- name: var-naming
|
- linters:
|
||||||
- name: redefines-builtin-id
|
- staticcheck
|
||||||
disabled: true
|
text: (?i)argument x is overwritten before first use
|
||||||
gofumpt:
|
- linters:
|
||||||
extra-rules: true
|
- gocritic
|
||||||
depguard:
|
text: '(?i)commentFormatting: put a space between `//` and comment text'
|
||||||
rules:
|
- linters:
|
||||||
main:
|
- gocritic
|
||||||
deny:
|
text: '(?i)exitAfterDefer:'
|
||||||
- pkg: encoding/json
|
- linters:
|
||||||
desc: use gitea's modules/json instead of encoding/json
|
- staticcheck
|
||||||
- pkg: github.com/unknwon/com
|
text: "(ST1005|ST1003|QF1001):"
|
||||||
desc: use gitea's util and replacements
|
paths:
|
||||||
- pkg: io/ioutil
|
- node_modules
|
||||||
desc: use os or io instead
|
- public
|
||||||
- pkg: golang.org/x/exp
|
- web_src
|
||||||
desc: it's experimental and unreliable
|
- third_party$
|
||||||
- pkg: code.gitea.io/gitea/modules/git/internal
|
- builtin$
|
||||||
desc: do not use the internal package, use AddXxx function instead
|
- examples$
|
||||||
- pkg: gopkg.in/ini.v1
|
|
||||||
desc: do not use the ini package, use gitea's config system instead
|
|
||||||
- pkg: github.com/minio/sha256-simd
|
|
||||||
desc: use crypto/sha256 instead, see https://codeberg.org/forgejo/forgejo/pulls/1528
|
|
||||||
testifylint:
|
|
||||||
disable:
|
|
||||||
- go-require
|
|
||||||
|
|
||||||
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: models/db/sql_postgres_with_schema.go
|
- gofumpt
|
||||||
linters:
|
settings:
|
||||||
- nolintlint
|
gofumpt:
|
||||||
- path: _test\.go
|
extra-rules: true
|
||||||
linters:
|
exclusions:
|
||||||
- gocyclo
|
generated: lax
|
||||||
- errcheck
|
paths:
|
||||||
- dupl
|
- node_modules
|
||||||
- gosec
|
- public
|
||||||
- unparam
|
- web_src
|
||||||
- staticcheck
|
- third_party$
|
||||||
- path: models/migrations/v
|
- builtin$
|
||||||
linters:
|
- examples$
|
||||||
- gocyclo
|
|
||||||
- errcheck
|
|
||||||
- dupl
|
|
||||||
- gosec
|
|
||||||
- path: cmd
|
|
||||||
linters:
|
|
||||||
- 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
|
|
||||||
|
|
17
BSDmakefile
17
BSDmakefile
|
@ -36,10 +36,6 @@ GARGS = "--no-print-directory"
|
||||||
JARG = -j$(.MAKE.JOBS)
|
JARG = -j$(.MAKE.JOBS)
|
||||||
.endif
|
.endif
|
||||||
|
|
||||||
# bmake prefers out-of-source builds and tries to cd into ./obj (among others)
|
|
||||||
# where possible. GNU Make doesn't, so override that value.
|
|
||||||
.OBJDIR: ./
|
|
||||||
|
|
||||||
# The GNU convention is to use the lowercased `prefix` variable/macro to
|
# The GNU convention is to use the lowercased `prefix` variable/macro to
|
||||||
# specify the installation directory. Humor them.
|
# specify the installation directory. Humor them.
|
||||||
GPREFIX =
|
GPREFIX =
|
||||||
|
@ -48,11 +44,12 @@ GPREFIX =
|
||||||
.endif
|
.endif
|
||||||
|
|
||||||
.BEGIN: .SILENT
|
.BEGIN: .SILENT
|
||||||
which $(GMAKE) || (printf "Error: GNU Make is required!\n\n" 1>&2 && false)
|
which $(GMAKE) >/dev/null || (printf "Error: GNU Make is required!\n\n" 1>&2 && false)
|
||||||
|
|
||||||
.PHONY: FRC
|
.PHONY: EMPTY
|
||||||
$(.TARGETS): FRC
|
EMPTY: .SILENT
|
||||||
$(GMAKE) $(GPREFIX) $(GARGS) $(.TARGETS:S,.DONE,,) $(JARG)
|
$(GMAKE) $(GPREFIX) $(GARGS) $(JARG)
|
||||||
|
|
||||||
.DONE .DEFAULT: .SILENT
|
.PHONY: $(.TARGETS)
|
||||||
$(GMAKE) $(GPREFIX) $(GARGS) $(.TARGETS:S,.DONE,,) $(JARG)
|
$(.TARGETS): .SILENT
|
||||||
|
$(GMAKE) $(GPREFIX) $(GARGS) $(JARG) $@
|
||||||
|
|
10
CODEOWNERS
10
CODEOWNERS
|
@ -9,10 +9,11 @@
|
||||||
# Files related to frontend development.
|
# Files related to frontend development.
|
||||||
|
|
||||||
# Javascript and CSS code.
|
# Javascript and CSS code.
|
||||||
web_src/.* @caesar @crystal @gusted
|
web_src/.* @beowulf @gusted
|
||||||
|
web_src/css/.* @0ko
|
||||||
|
|
||||||
# HTML templates used by the backend.
|
# HTML templates used by the backend.
|
||||||
templates/.* @caesar @crystal @gusted
|
templates/.* @beowulf @gusted
|
||||||
## the issue sidebar was touched by fnetx
|
## the issue sidebar was touched by fnetx
|
||||||
templates/repo/issue/view_content/sidebar.* @fnetx
|
templates/repo/issue/view_content/sidebar.* @fnetx
|
||||||
|
|
||||||
|
@ -33,8 +34,9 @@ models/.* @gusted
|
||||||
# for code that lives in here.
|
# for code that lives in here.
|
||||||
routers/.* @gusted
|
routers/.* @gusted
|
||||||
|
|
||||||
# Let new strings be checked by the translation team.
|
# Let locale changes be checked by the translation team.
|
||||||
options/locale/locale_en-US.ini @0ko
|
options/locale/.* @0ko
|
||||||
|
options/locale_next/.* @0ko
|
||||||
|
|
||||||
# Personal interest
|
# Personal interest
|
||||||
.*/webhook.* @oliverpool
|
.*/webhook.* @oliverpool
|
||||||
|
|
28
Dockerfile
28
Dockerfile
|
@ -1,9 +1,9 @@
|
||||||
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx
|
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.23-alpine3.20 as build-env
|
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.24-alpine3.22 AS build-env
|
||||||
|
|
||||||
ARG GOPROXY
|
ARG GOPROXY
|
||||||
ENV GOPROXY=${GOPROXY:-direct}
|
ENV GOPROXY=${GOPROXY:-https://proxy.golang.org,direct}
|
||||||
|
|
||||||
ARG RELEASE_VERSION
|
ARG RELEASE_VERSION
|
||||||
ARG TAGS="sqlite sqlite_unlock_notify"
|
ARG TAGS="sqlite sqlite_unlock_notify"
|
||||||
|
@ -30,13 +30,13 @@ RUN cp /*-alpine-linux-musl*/lib/ld-musl-*.so.1 /lib || true
|
||||||
|
|
||||||
RUN apk --no-cache add build-base git nodejs npm
|
RUN apk --no-cache add build-base git nodejs npm
|
||||||
|
|
||||||
COPY . ${GOPATH}/src/code.gitea.io/gitea
|
COPY . ${GOPATH}/src/forgejo.org
|
||||||
WORKDIR ${GOPATH}/src/code.gitea.io/gitea
|
WORKDIR ${GOPATH}/src/forgejo.org
|
||||||
|
|
||||||
RUN make clean
|
RUN make clean-no-bindata
|
||||||
RUN make frontend
|
RUN make frontend
|
||||||
RUN go build contrib/environment-to-ini/environment-to-ini.go && xx-verify environment-to-ini
|
RUN go build contrib/environment-to-ini/environment-to-ini.go && xx-verify environment-to-ini
|
||||||
RUN LDFLAGS="-buildid=" make RELEASE_VERSION=$RELEASE_VERSION GOFLAGS="-trimpath" go-check generate-backend static-executable && xx-verify gitea
|
RUN LDFLAGS="-buildid=" make FORGEJO_GENERATE_SKIP_HASH=true RELEASE_VERSION=$RELEASE_VERSION GOFLAGS="-trimpath" go-check generate-backend static-executable && xx-verify gitea
|
||||||
|
|
||||||
# Copy local files
|
# Copy local files
|
||||||
COPY docker/root /tmp/local
|
COPY docker/root /tmp/local
|
||||||
|
@ -47,11 +47,11 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
|
||||||
/tmp/local/etc/s6/gitea/* \
|
/tmp/local/etc/s6/gitea/* \
|
||||||
/tmp/local/etc/s6/openssh/* \
|
/tmp/local/etc/s6/openssh/* \
|
||||||
/tmp/local/etc/s6/.s6-svscan/* \
|
/tmp/local/etc/s6/.s6-svscan/* \
|
||||||
/go/src/code.gitea.io/gitea/gitea \
|
/go/src/forgejo.org/gitea \
|
||||||
/go/src/code.gitea.io/gitea/environment-to-ini
|
/go/src/forgejo.org/environment-to-ini
|
||||||
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
|
RUN chmod 644 /go/src/forgejo.org/contrib/autocompletion/bash_autocomplete
|
||||||
|
|
||||||
FROM data.forgejo.org/oci/alpine:3.20
|
FROM data.forgejo.org/oci/alpine:3.22
|
||||||
ARG RELEASE_VERSION
|
ARG RELEASE_VERSION
|
||||||
LABEL maintainer="contact@forgejo.org" \
|
LABEL maintainer="contact@forgejo.org" \
|
||||||
org.opencontainers.image.authors="Forgejo" \
|
org.opencontainers.image.authors="Forgejo" \
|
||||||
|
@ -98,11 +98,11 @@ 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 /
|
||||||
RUN cd /usr/local/bin ; ln -s gitea forgejo
|
RUN cd /usr/local/bin ; ln -s gitea forgejo
|
||||||
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
|
COPY --from=build-env /go/src/forgejo.org/gitea /app/gitea/gitea
|
||||||
RUN ln -s /app/gitea/gitea /app/gitea/forgejo-cli
|
RUN ln -s /app/gitea/gitea /app/gitea/forgejo-cli
|
||||||
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/forgejo.org/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
|
COPY --from=build-env /go/src/forgejo.org/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx
|
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.23-alpine3.20 as build-env
|
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.24-alpine3.22 AS build-env
|
||||||
|
|
||||||
ARG GOPROXY
|
ARG GOPROXY
|
||||||
ENV GOPROXY=${GOPROXY:-direct}
|
ENV GOPROXY=${GOPROXY:-https://proxy.golang.org,direct}
|
||||||
|
|
||||||
ARG RELEASE_VERSION
|
ARG RELEASE_VERSION
|
||||||
ARG TAGS="sqlite sqlite_unlock_notify"
|
ARG TAGS="sqlite sqlite_unlock_notify"
|
||||||
|
@ -30,13 +30,13 @@ RUN cp /*-alpine-linux-musl*/lib/ld-musl-*.so.1 /lib || true
|
||||||
|
|
||||||
RUN apk --no-cache add build-base git nodejs npm
|
RUN apk --no-cache add build-base git nodejs npm
|
||||||
|
|
||||||
COPY . ${GOPATH}/src/code.gitea.io/gitea
|
COPY . ${GOPATH}/src/forgejo.org
|
||||||
WORKDIR ${GOPATH}/src/code.gitea.io/gitea
|
WORKDIR ${GOPATH}/src/forgejo.org
|
||||||
|
|
||||||
RUN make clean
|
RUN make clean-no-bindata
|
||||||
RUN make frontend
|
RUN make frontend
|
||||||
RUN go build contrib/environment-to-ini/environment-to-ini.go && xx-verify environment-to-ini
|
RUN go build contrib/environment-to-ini/environment-to-ini.go && xx-verify environment-to-ini
|
||||||
RUN make RELEASE_VERSION=$RELEASE_VERSION go-check generate-backend static-executable && xx-verify gitea
|
RUN make FORGEJO_GENERATE_SKIP_HASH=true RELEASE_VERSION=$RELEASE_VERSION go-check generate-backend static-executable && xx-verify gitea
|
||||||
|
|
||||||
# Copy local files
|
# Copy local files
|
||||||
COPY docker/rootless /tmp/local
|
COPY docker/rootless /tmp/local
|
||||||
|
@ -45,11 +45,11 @@ COPY docker/rootless /tmp/local
|
||||||
RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
|
RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
|
||||||
/tmp/local/usr/local/bin/docker-setup.sh \
|
/tmp/local/usr/local/bin/docker-setup.sh \
|
||||||
/tmp/local/usr/local/bin/gitea \
|
/tmp/local/usr/local/bin/gitea \
|
||||||
/go/src/code.gitea.io/gitea/gitea \
|
/go/src/forgejo.org/gitea \
|
||||||
/go/src/code.gitea.io/gitea/environment-to-ini
|
/go/src/forgejo.org/environment-to-ini
|
||||||
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
|
RUN chmod 644 /go/src/forgejo.org/contrib/autocompletion/bash_autocomplete
|
||||||
|
|
||||||
FROM data.forgejo.org/oci/alpine:3.20
|
FROM data.forgejo.org/oci/alpine:3.22
|
||||||
ARG RELEASE_VERSION
|
ARG RELEASE_VERSION
|
||||||
LABEL maintainer="contact@forgejo.org" \
|
LABEL maintainer="contact@forgejo.org" \
|
||||||
org.opencontainers.image.authors="Forgejo" \
|
org.opencontainers.image.authors="Forgejo" \
|
||||||
|
@ -91,10 +91,10 @@ RUN chown git:git /var/lib/gitea /etc/gitea
|
||||||
|
|
||||||
COPY --from=build-env /tmp/local /
|
COPY --from=build-env /tmp/local /
|
||||||
RUN cd /usr/local/bin ; ln -s gitea forgejo
|
RUN cd /usr/local/bin ; ln -s gitea forgejo
|
||||||
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/forgejo.org/gitea /app/gitea/gitea
|
||||||
RUN ln -s /app/gitea/gitea /app/gitea/forgejo-cli
|
RUN ln -s /app/gitea/gitea /app/gitea/forgejo-cli
|
||||||
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/forgejo.org/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
|
COPY --from=build-env /go/src/forgejo.org/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh
|
||||||
|
|
||||||
#git:git
|
#git:git
|
||||||
USER 1000:1000
|
USER 1000:1000
|
||||||
|
|
236
Makefile
236
Makefile
|
@ -16,7 +16,7 @@ else
|
||||||
|
|
||||||
DIST := dist
|
DIST := dist
|
||||||
DIST_DIRS := $(DIST)/binaries $(DIST)/release
|
DIST_DIRS := $(DIST)/binaries $(DIST)/release
|
||||||
IMPORT := code.gitea.io/gitea
|
IMPORT := forgejo.org
|
||||||
|
|
||||||
GO ?= $(shell go env GOROOT)/bin/go
|
GO ?= $(shell go env GOROOT)/bin/go
|
||||||
SHASUM ?= shasum -a 256
|
SHASUM ?= shasum -a 256
|
||||||
|
@ -37,19 +37,17 @@ endif
|
||||||
XGO_VERSION := go-1.21.x
|
XGO_VERSION := go-1.21.x
|
||||||
|
|
||||||
AIR_PACKAGE ?= github.com/air-verse/air@v1 # renovate: datasource=go
|
AIR_PACKAGE ?= github.com/air-verse/air@v1 # renovate: datasource=go
|
||||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.0.3 # renovate: datasource=go
|
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.3.0 # renovate: datasource=go
|
||||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0 # renovate: datasource=go
|
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.8.0 # renovate: datasource=go
|
||||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2 # renovate: datasource=go
|
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.6 # renovate: datasource=go
|
||||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11 # renovate: datasource=go
|
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11 # renovate: datasource=go
|
||||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.6.0 # renovate: datasource=go
|
|
||||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0 # renovate: datasource=go
|
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0 # renovate: datasource=go
|
||||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
||||||
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 # renovate: datasource=go
|
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 # renovate: datasource=go
|
||||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go
|
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go
|
||||||
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.28.0 # renovate: datasource=go
|
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.34.0 # renovate: datasource=go
|
||||||
GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.4.0 # renovate: datasource=go
|
GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.5.2 # renovate: datasource=go
|
||||||
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.17.0 # renovate: datasource=go
|
RENOVATE_NPM_PACKAGE ?= renovate@41.17.2 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
|
||||||
RENOVATE_NPM_PACKAGE ?= renovate@39.82.1 # renovate: datasource=docker packageName=code.forgejo.org/forgejo-contrib/renovate
|
|
||||||
|
|
||||||
# https://github.com/disposable-email-domains/disposable-email-domains/commits/main/
|
# https://github.com/disposable-email-domains/disposable-email-domains/commits/main/
|
||||||
DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ...
|
DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ...
|
||||||
|
@ -59,20 +57,8 @@ 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
|
||||||
|
|
||||||
ifeq ($(GOOS),windows)
|
GOFLAGS := -v
|
||||||
IS_WINDOWS := yes
|
EXECUTABLE ?= gitea
|
||||||
else ifeq ($(patsubst Windows%,Windows,$(OS)),Windows)
|
|
||||||
ifeq ($(GOOS),)
|
|
||||||
IS_WINDOWS := yes
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
ifeq ($(IS_WINDOWS),yes)
|
|
||||||
GOFLAGS := -v -buildmode=exe
|
|
||||||
EXECUTABLE ?= gitea.exe
|
|
||||||
else
|
|
||||||
GOFLAGS := -v
|
|
||||||
EXECUTABLE ?= gitea
|
|
||||||
endif
|
|
||||||
|
|
||||||
ifeq ($(shell sed --version 2>/dev/null | grep -q GNU && echo gnu),gnu)
|
ifeq ($(shell sed --version 2>/dev/null | grep -q GNU && echo gnu),gnu)
|
||||||
SED_INPLACE := sed -i
|
SED_INPLACE := sed -i
|
||||||
|
@ -104,38 +90,36 @@ else
|
||||||
FORGEJO_VERSION_API ?= $(GITEA_VERSION)+${GITEA_COMPATIBILITY}
|
FORGEJO_VERSION_API ?= $(GITEA_VERSION)+${GITEA_COMPATIBILITY}
|
||||||
else
|
else
|
||||||
# drop the "g" prefix prepended by git describe to the commit hash
|
# drop the "g" prefix prepended by git describe to the commit hash
|
||||||
FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always | sed 's/^v//' | sed 's/\-g/-/')+${GITEA_COMPATIBILITY}
|
FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always 2>/dev/null | sed 's/^v//' | sed 's/\-g/-/')
|
||||||
|
ifneq ($(FORGEJO_VERSION),)
|
||||||
|
ifeq ($(findstring $(GITEA_COMPATIBILITY),$(FORGEJO_VERSION)),)
|
||||||
|
FORGEJO_VERSION := $(FORGEJO_VERSION)+$(GITEA_COMPATIBILITY)
|
||||||
|
endif
|
||||||
|
endif
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
FORGEJO_VERSION_MAJOR=$(shell echo $(FORGEJO_VERSION) | sed -e 's/\..*//')
|
FORGEJO_VERSION_MAJOR=$(shell echo $(FORGEJO_VERSION) | sed -e 's/\..*//')
|
||||||
FORGEJO_VERSION_MINOR=$(shell echo $(FORGEJO_VERSION) | sed -E -e 's/^([0-9]+\.[0-9]+).*/\1/')
|
FORGEJO_VERSION_MINOR=$(shell echo $(FORGEJO_VERSION) | sed -E -e 's/^([0-9]+\.[0-9]+).*/\1/')
|
||||||
|
|
||||||
show-version-full:
|
|
||||||
@echo ${FORGEJO_VERSION}
|
|
||||||
|
|
||||||
show-version-major:
|
|
||||||
@echo ${FORGEJO_VERSION_MAJOR}
|
|
||||||
|
|
||||||
show-version-minor:
|
|
||||||
@echo ${FORGEJO_VERSION_MINOR}
|
|
||||||
|
|
||||||
RELEASE_VERSION ?= ${FORGEJO_VERSION}
|
RELEASE_VERSION ?= ${FORGEJO_VERSION}
|
||||||
VERSION ?= ${RELEASE_VERSION}
|
VERSION ?= ${RELEASE_VERSION}
|
||||||
|
|
||||||
FORGEJO_VERSION_API ?= ${FORGEJO_VERSION}
|
FORGEJO_VERSION_API ?= ${FORGEJO_VERSION}
|
||||||
|
|
||||||
show-version-api:
|
# Strip binaries by default to reduce size, allow overriding for debugging
|
||||||
@echo ${FORGEJO_VERSION_API}
|
STRIP ?= 1
|
||||||
|
ifeq ($(STRIP),1)
|
||||||
|
LDFLAGS := $(LDFLAGS) -s -w
|
||||||
|
endif
|
||||||
LDFLAGS := $(LDFLAGS) -X "main.ReleaseVersion=$(RELEASE_VERSION)" -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(FORGEJO_VERSION)" -X "main.Tags=$(TAGS)" -X "main.ForgejoVersion=$(FORGEJO_VERSION_API)"
|
LDFLAGS := $(LDFLAGS) -X "main.ReleaseVersion=$(RELEASE_VERSION)" -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(FORGEJO_VERSION)" -X "main.Tags=$(TAGS)" -X "main.ForgejoVersion=$(FORGEJO_VERSION_API)"
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
ifeq ($(HAS_GO), yes)
|
ifeq ($(HAS_GO), yes)
|
||||||
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) $(shell $(GO) list code.gitea.io/gitea/models/forgejo_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 ./...))
|
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list forgejo.org/models/migrations/...) $(shell $(GO) list forgejo.org/models/forgejo_migrations/...) forgejo.org/tests/integration/migration-test forgejo.org/tests forgejo.org/tests/integration forgejo.org/tests/e2e,$(shell $(GO) list ./...))
|
||||||
endif
|
endif
|
||||||
REMOTE_CACHER_MODULES ?= cache nosql session queue
|
REMOTE_CACHER_MODULES ?= cache nosql session queue
|
||||||
GO_TEST_REMOTE_CACHER_PACKAGES ?= $(addprefix code.gitea.io/gitea/modules/,$(REMOTE_CACHER_MODULES))
|
GO_TEST_REMOTE_CACHER_PACKAGES ?= $(addprefix forgejo.org/modules/,$(REMOTE_CACHER_MODULES))
|
||||||
|
|
||||||
FOMANTIC_WORK_DIR := web_src/fomantic
|
FOMANTIC_WORK_DIR := web_src/fomantic
|
||||||
|
|
||||||
|
@ -144,7 +128,7 @@ WEBPACK_CONFIGS := webpack.config.js tailwind.config.js
|
||||||
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 := modules/migration/bindata.go modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go
|
||||||
BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST))
|
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
|
||||||
|
@ -168,7 +152,7 @@ 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
|
||||||
|
|
||||||
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) docs/content templates options/locale/locale_en-US.ini .github $(wildcard *.go *.js *.ts *.vue *.md *.yml *.yaml *.toml)
|
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) docs/content templates options/locale/locale_en-US.ini .github $(wildcard *.go *.js *.ts *.vue *.md *.yml *.yaml)
|
||||||
|
|
||||||
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" ! -path modules/options/bindata.go ! -path modules/public/bindata.go ! -path modules/templates/bindata.go)
|
||||||
|
@ -176,7 +160,7 @@ GO_SOURCES += $(GENERATED_GO_DEST)
|
||||||
GO_SOURCES_NO_BINDATA := $(GO_SOURCES)
|
GO_SOURCES_NO_BINDATA := $(GO_SOURCES)
|
||||||
|
|
||||||
ifeq ($(HAS_GO), yes)
|
ifeq ($(HAS_GO), yes)
|
||||||
MIGRATION_PACKAGES := $(shell $(GO) list code.gitea.io/gitea/models/migrations/... code.gitea.io/gitea/models/forgejo_migrations/...)
|
MIGRATION_PACKAGES := $(shell $(GO) list forgejo.org/models/migrations/... forgejo.org/models/forgejo_migrations/...)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(filter $(TAGS_SPLIT),bindata),bindata)
|
ifeq ($(filter $(TAGS_SPLIT),bindata),bindata)
|
||||||
|
@ -215,7 +199,7 @@ all: build
|
||||||
.PHONY: help
|
.PHONY: help
|
||||||
help:
|
help:
|
||||||
@echo "Make Routines:"
|
@echo "Make Routines:"
|
||||||
@echo " - \"\" equivalent to \"build\""
|
@echo " - \"\" equivalent to \"build\""
|
||||||
@echo " - build build everything"
|
@echo " - build build everything"
|
||||||
@echo " - frontend build frontend files"
|
@echo " - frontend build frontend files"
|
||||||
@echo " - backend build backend files"
|
@echo " - backend build backend files"
|
||||||
|
@ -228,31 +212,22 @@ help:
|
||||||
@echo " - deps-frontend install frontend dependencies"
|
@echo " - deps-frontend install frontend dependencies"
|
||||||
@echo " - deps-backend install backend dependencies"
|
@echo " - deps-backend install backend dependencies"
|
||||||
@echo " - deps-tools install tool dependencies"
|
@echo " - deps-tools install tool dependencies"
|
||||||
@echo " - deps-py install python dependencies"
|
|
||||||
@echo " - lint lint everything"
|
@echo " - lint lint everything"
|
||||||
@echo " - lint-fix lint everything and fix issues"
|
@echo " - lint-fix lint everything and fix issues"
|
||||||
@echo " - lint-frontend lint frontend files"
|
@echo " - lint-frontend lint frontend files"
|
||||||
@echo " - lint-frontend-fix lint frontend files and fix issues"
|
@echo " - lint-frontend-fix lint frontend files and fix issues"
|
||||||
@echo " - lint-backend lint backend files"
|
@echo " - lint-backend lint backend files"
|
||||||
@echo " - lint-backend-fix lint backend files and fix issues"
|
@echo " - lint-backend-fix lint backend files and fix issues"
|
||||||
@echo " - lint-codespell lint typos"
|
|
||||||
@echo " - lint-codespell-fix lint typos and fix them automatically"
|
|
||||||
@echo " - lint-codespell-fix-i lint typos and fix them interactively"
|
|
||||||
@echo " - lint-go lint go files"
|
@echo " - lint-go lint go files"
|
||||||
@echo " - lint-go-fix lint go files and fix issues"
|
@echo " - lint-go-fix lint go files and fix issues"
|
||||||
@echo " - lint-go-vet lint go files with vet"
|
@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 lint js files"
|
||||||
@echo " - lint-js-fix lint js files and fix issues"
|
@echo " - lint-js-fix lint js files and fix issues"
|
||||||
@echo " - lint-css lint css files"
|
@echo " - lint-css lint css files"
|
||||||
@echo " - lint-css-fix lint css files and fix issues"
|
@echo " - lint-css-fix lint css files and fix issues"
|
||||||
@echo " - lint-md lint markdown files"
|
@echo " - lint-md lint markdown files"
|
||||||
@echo " - lint-swagger lint swagger files"
|
@echo " - lint-swagger lint swagger files"
|
||||||
@echo " - lint-templates lint template files"
|
|
||||||
@echo " - lint-renovate lint renovate files"
|
@echo " - lint-renovate lint renovate 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 run various consistency checks"
|
||||||
@echo " - checks-frontend check frontend files"
|
@echo " - checks-frontend check frontend files"
|
||||||
@echo " - checks-backend check backend files"
|
@echo " - checks-backend check backend files"
|
||||||
|
@ -283,6 +258,30 @@ help:
|
||||||
@echo " - test-sqlite[\#TestSpecificName] run integration test for sqlite"
|
@echo " - test-sqlite[\#TestSpecificName] run integration test for sqlite"
|
||||||
@echo " - reproduce-build\#version build a reproducible binary for the specified release version"
|
@echo " - reproduce-build\#version build a reproducible binary for the specified release version"
|
||||||
|
|
||||||
|
.PHONY: verify-version
|
||||||
|
verify-version:
|
||||||
|
ifeq ($(FORGEJO_VERSION),)
|
||||||
|
@echo "Error: Could not determine FORGEJO_VERSION; version file $(STORED_VERSION_FILE) not present and no suitable git tag found"
|
||||||
|
@echo 'In most cases this likely means you forgot to fetch git tags, you can fix this by executing `git fetch --tags`. If this is not possible and this is part of a custom build process, then you can set a specific version by writing it to $(STORED_VERSION_FILE) (This must be a semver compatible version).'
|
||||||
|
@false
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: show-version-full
|
||||||
|
show-version-full: verify-version
|
||||||
|
@echo ${FORGEJO_VERSION}
|
||||||
|
|
||||||
|
.PHONY: show-version-major
|
||||||
|
show-version-major: verify-version
|
||||||
|
@echo ${FORGEJO_VERSION_MAJOR}
|
||||||
|
|
||||||
|
.PHONY: show-version-minor
|
||||||
|
show-version-minor: verify-version
|
||||||
|
@echo ${FORGEJO_VERSION_MINOR}
|
||||||
|
|
||||||
|
.PHONY: show-version-api
|
||||||
|
show-version-api: verify-version
|
||||||
|
@echo ${FORGEJO_VERSION_API}
|
||||||
|
|
||||||
###
|
###
|
||||||
# Check system and environment requirements
|
# Check system and environment requirements
|
||||||
###
|
###
|
||||||
|
@ -324,8 +323,12 @@ clean-all: clean
|
||||||
rm -rf $(WEBPACK_DEST_ENTRIES) node_modules
|
rm -rf $(WEBPACK_DEST_ENTRIES) node_modules
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean: clean-no-bindata
|
||||||
rm -rf $(EXECUTABLE) $(DIST) $(BINDATA_DEST) $(BINDATA_HASH) \
|
rm -rf $(BINDATA_DEST) $(BINDATA_HASH)
|
||||||
|
|
||||||
|
.PHONY: clean-no-bindata
|
||||||
|
clean-no-bindata:
|
||||||
|
rm -rf $(EXECUTABLE) $(DIST) \
|
||||||
integrations*.test \
|
integrations*.test \
|
||||||
e2e*.test \
|
e2e*.test \
|
||||||
tests/integration/gitea-integration-* \
|
tests/integration/gitea-integration-* \
|
||||||
|
@ -408,10 +411,10 @@ checks-frontend: lockfile-check svg-check
|
||||||
checks-backend: tidy-check swagger-check fmt-check swagger-validate security-check
|
checks-backend: tidy-check swagger-check fmt-check swagger-validate security-check
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint: lint-frontend lint-backend lint-spell
|
lint: lint-frontend lint-backend
|
||||||
|
|
||||||
.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
|
||||||
|
|
||||||
.PHONY: lint-frontend
|
.PHONY: lint-frontend
|
||||||
lint-frontend: lint-js lint-css
|
lint-frontend: lint-js lint-css
|
||||||
|
@ -420,23 +423,11 @@ lint-frontend: lint-js lint-css
|
||||||
lint-frontend-fix: lint-js-fix lint-css-fix
|
lint-frontend-fix: lint-js-fix lint-css-fix
|
||||||
|
|
||||||
.PHONY: lint-backend
|
.PHONY: lint-backend
|
||||||
lint-backend: lint-go lint-go-vet lint-editorconfig lint-renovate lint-locale lint-disposable-emails
|
lint-backend: lint-go lint-go-vet lint-editorconfig lint-renovate lint-locale lint-locale-usage lint-disposable-emails
|
||||||
|
|
||||||
.PHONY: lint-backend-fix
|
.PHONY: lint-backend-fix
|
||||||
lint-backend-fix: lint-go-fix lint-go-vet lint-editorconfig lint-disposable-emails-fix
|
lint-backend-fix: lint-go-fix lint-go-vet lint-editorconfig lint-disposable-emails-fix
|
||||||
|
|
||||||
.PHONY: lint-codespell
|
|
||||||
lint-codespell:
|
|
||||||
codespell
|
|
||||||
|
|
||||||
.PHONY: lint-codespell-fix
|
|
||||||
lint-codespell-fix:
|
|
||||||
codespell -w
|
|
||||||
|
|
||||||
.PHONY: lint-codespell-fix-i
|
|
||||||
lint-codespell-fix-i:
|
|
||||||
codespell -w -i 3 -C 2
|
|
||||||
|
|
||||||
.PHONY: lint-js
|
.PHONY: lint-js
|
||||||
lint-js: node_modules
|
lint-js: node_modules
|
||||||
npx eslint --color --max-warnings=0
|
npx eslint --color --max-warnings=0
|
||||||
|
@ -459,27 +450,23 @@ lint-swagger: node_modules
|
||||||
|
|
||||||
.PHONY: lint-renovate
|
.PHONY: lint-renovate
|
||||||
lint-renovate: node_modules
|
lint-renovate: node_modules
|
||||||
npx --yes --package $(RENOVATE_NPM_PACKAGE) -- renovate-config-validator --strict > .lint-renovate 2>&1 || true
|
npx --yes --package $(RENOVATE_NPM_PACKAGE) -- renovate-config-validator > .lint-renovate 2>&1 || true
|
||||||
@if grep --quiet --extended-regexp -e '^( WARN:|ERROR:)' .lint-renovate ; then cat .lint-renovate ; rm .lint-renovate ; exit 1 ; fi
|
@if grep --quiet --extended-regexp -e '^( ERROR:)' .lint-renovate ; then cat .lint-renovate ; rm .lint-renovate ; exit 1 ; fi
|
||||||
@rm .lint-renovate
|
@rm .lint-renovate
|
||||||
|
|
||||||
.PHONY: lint-locale
|
.PHONY: lint-locale
|
||||||
lint-locale:
|
lint-locale:
|
||||||
$(GO) run build/lint-locale.go
|
$(GO) run build/lint-locale/lint-locale.go
|
||||||
|
|
||||||
|
.PHONY: lint-locale-usage
|
||||||
|
lint-locale-usage:
|
||||||
|
$(GO) run build/lint-locale-usage/lint-locale-usage.go
|
||||||
|
|
||||||
.PHONY: lint-md
|
.PHONY: lint-md
|
||||||
lint-md: node_modules
|
lint-md: node_modules
|
||||||
npx markdownlint docs *.md
|
npx markdownlint docs *.md
|
||||||
|
|
||||||
.PHONY: lint-spell
|
RUN_DEADCODE = $(GO) run $(DEADCODE_PACKAGE) -generated=false -f='{{println .Path}}{{range .Funcs}}{{printf "\t%s\n" .Name}}{{end}}{{println}}' -test forgejo.org
|
||||||
lint-spell: lint-codespell
|
|
||||||
@go run $(MISSPELL_PACKAGE) -error $(SPELLCHECK_FILES)
|
|
||||||
|
|
||||||
.PHONY: lint-spell-fix
|
|
||||||
lint-spell-fix: lint-codespell-fix
|
|
||||||
@go run $(MISSPELL_PACKAGE) -w $(SPELLCHECK_FILES)
|
|
||||||
|
|
||||||
RUN_DEADCODE = $(GO) run $(DEADCODE_PACKAGE) -generated=false -f='{{println .Path}}{{range .Funcs}}{{printf "\t%s\n" .Name}}{{end}}{{println}}' -test code.gitea.io/gitea
|
|
||||||
|
|
||||||
.PHONY: lint-go
|
.PHONY: lint-go
|
||||||
lint-go:
|
lint-go:
|
||||||
|
@ -493,23 +480,11 @@ lint-go-fix:
|
||||||
$(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GOLANGCI_LINT_ARGS) --fix
|
$(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GOLANGCI_LINT_ARGS) --fix
|
||||||
$(RUN_DEADCODE) > .deadcode-out
|
$(RUN_DEADCODE) > .deadcode-out
|
||||||
|
|
||||||
# workaround step for the lint-go-windows CI task because 'go run' can not
|
|
||||||
# have distinct GOOS/GOARCH for its build and run steps
|
|
||||||
.PHONY: lint-go-windows
|
|
||||||
lint-go-windows:
|
|
||||||
@GOOS= GOARCH= $(GO) install $(GOLANGCI_LINT_PACKAGE)
|
|
||||||
golangci-lint run
|
|
||||||
|
|
||||||
.PHONY: lint-go-vet
|
.PHONY: lint-go-vet
|
||||||
lint-go-vet:
|
lint-go-vet:
|
||||||
@echo "Running go vet..."
|
@echo "Running go vet..."
|
||||||
@$(GO) vet ./...
|
@$(GO) vet ./...
|
||||||
|
|
||||||
.PHONY: lint-go-gopls
|
|
||||||
lint-go-gopls:
|
|
||||||
@echo "Running gopls check..."
|
|
||||||
@GO=$(GO) GOPLS_PACKAGE=$(GOPLS_PACKAGE) tools/lint-go-gopls.sh $(GO_SOURCES_NO_BINDATA)
|
|
||||||
|
|
||||||
.PHONY: lint-editorconfig
|
.PHONY: lint-editorconfig
|
||||||
lint-editorconfig:
|
lint-editorconfig:
|
||||||
$(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) templates .forgejo/workflows
|
$(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) templates .forgejo/workflows
|
||||||
|
@ -522,18 +497,9 @@ lint-disposable-emails:
|
||||||
lint-disposable-emails-fix:
|
lint-disposable-emails-fix:
|
||||||
$(GO) run build/generate-disposable-email.go -r $(DISPOSABLE_EMAILS_SHA)
|
$(GO) run build/generate-disposable-email.go -r $(DISPOSABLE_EMAILS_SHA)
|
||||||
|
|
||||||
.PHONY: lint-templates
|
|
||||||
lint-templates: .venv node_modules
|
|
||||||
@node tools/lint-templates-svg.js
|
|
||||||
@poetry run djlint $(shell find templates -type f -iname '*.tmpl')
|
|
||||||
|
|
||||||
.PHONY: lint-yaml
|
|
||||||
lint-yaml: .venv
|
|
||||||
@poetry run yamllint .
|
|
||||||
|
|
||||||
.PHONY: security-check
|
.PHONY: security-check
|
||||||
security-check:
|
security-check:
|
||||||
go run $(GOVULNCHECK_PACKAGE) ./...
|
go run $(GOVULNCHECK_PACKAGE) -show color ./...
|
||||||
|
|
||||||
###
|
###
|
||||||
# Development and testing targets
|
# Development and testing targets
|
||||||
|
@ -587,7 +553,7 @@ test-check:
|
||||||
|
|
||||||
.PHONY: test\#%
|
.PHONY: test\#%
|
||||||
test\#%:
|
test\#%:
|
||||||
@echo "Running go test with -tags '$(TEST_TAGS)'..."
|
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
|
||||||
@$(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES)
|
@$(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES)
|
||||||
|
|
||||||
.PHONY: coverage
|
.PHONY: coverage
|
||||||
|
@ -620,7 +586,7 @@ tidy-check: tidy
|
||||||
go-licenses: $(GO_LICENSE_FILE)
|
go-licenses: $(GO_LICENSE_FILE)
|
||||||
|
|
||||||
$(GO_LICENSE_FILE): go.mod go.sum
|
$(GO_LICENSE_FILE): go.mod go.sum
|
||||||
-$(GO) run $(GO_LICENSES_PACKAGE) save . --force --ignore code.gitea.io/gitea --save_path=$(GO_LICENSE_TMP_DIR) 2>/dev/null
|
-$(GO) run $(GO_LICENSES_PACKAGE) save . --force --ignore forgejo.org --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)
|
||||||
|
|
||||||
|
@ -750,33 +716,33 @@ integration-test-coverage-sqlite: integrations.cover.sqlite.test generate-ini-sq
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.cover.sqlite.test -test.coverprofile=integration.coverage.out
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.cover.sqlite.test -test.coverprofile=integration.coverage.out
|
||||||
|
|
||||||
integrations.mysql.test: git-check $(GO_SOURCES)
|
integrations.mysql.test: git-check $(GO_SOURCES)
|
||||||
$(GOTEST) $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.mysql.test
|
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration -o integrations.mysql.test
|
||||||
|
|
||||||
integrations.pgsql.test: git-check $(GO_SOURCES)
|
integrations.pgsql.test: git-check $(GO_SOURCES)
|
||||||
$(GOTEST) $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.pgsql.test
|
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration -o integrations.pgsql.test
|
||||||
|
|
||||||
integrations.sqlite.test: git-check $(GO_SOURCES)
|
integrations.sqlite.test: git-check $(GO_SOURCES)
|
||||||
$(GOTEST) $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.sqlite.test -tags '$(TEST_TAGS)'
|
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration -o integrations.sqlite.test -tags '$(TEST_TAGS)'
|
||||||
|
|
||||||
integrations.cover.test: git-check $(GO_SOURCES)
|
integrations.cover.test: git-check $(GO_SOURCES)
|
||||||
$(GOTEST) $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -coverpkg $(shell echo $(GO_TEST_PACKAGES) | tr ' ' ',') -o integrations.cover.test
|
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration -coverpkg $(shell echo $(GO_TEST_PACKAGES) | tr ' ' ',') -o integrations.cover.test
|
||||||
|
|
||||||
integrations.cover.sqlite.test: git-check $(GO_SOURCES)
|
integrations.cover.sqlite.test: git-check $(GO_SOURCES)
|
||||||
$(GOTEST) $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -coverpkg $(shell echo $(GO_TEST_PACKAGES) | tr ' ' ',') -o integrations.cover.sqlite.test -tags '$(TEST_TAGS)'
|
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration -coverpkg $(shell echo $(GO_TEST_PACKAGES) | tr ' ' ',') -o integrations.cover.sqlite.test -tags '$(TEST_TAGS)'
|
||||||
|
|
||||||
.PHONY: migrations.mysql.test
|
.PHONY: migrations.mysql.test
|
||||||
migrations.mysql.test: $(GO_SOURCES) generate-ini-mysql
|
migrations.mysql.test: $(GO_SOURCES) generate-ini-mysql
|
||||||
$(GOTEST) $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.mysql.test
|
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration/migration-test -o migrations.mysql.test
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GOTESTCOMPILEDRUNPREFIX) ./migrations.mysql.test $(GOTESTCOMPILEDRUNSUFFIX)
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GOTESTCOMPILEDRUNPREFIX) ./migrations.mysql.test $(GOTESTCOMPILEDRUNSUFFIX)
|
||||||
|
|
||||||
.PHONY: migrations.pgsql.test
|
.PHONY: migrations.pgsql.test
|
||||||
migrations.pgsql.test: $(GO_SOURCES) generate-ini-pgsql
|
migrations.pgsql.test: $(GO_SOURCES) generate-ini-pgsql
|
||||||
$(GOTEST) $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.pgsql.test
|
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration/migration-test -o migrations.pgsql.test
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTESTCOMPILEDRUNPREFIX) ./migrations.pgsql.test $(GOTESTCOMPILEDRUNSUFFIX)
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTESTCOMPILEDRUNPREFIX) ./migrations.pgsql.test $(GOTESTCOMPILEDRUNSUFFIX)
|
||||||
|
|
||||||
.PHONY: migrations.sqlite.test
|
.PHONY: migrations.sqlite.test
|
||||||
migrations.sqlite.test: $(GO_SOURCES) generate-ini-sqlite
|
migrations.sqlite.test: $(GO_SOURCES) generate-ini-sqlite
|
||||||
$(GOTEST) $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.sqlite.test -tags '$(TEST_TAGS)'
|
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration/migration-test -o migrations.sqlite.test -tags '$(TEST_TAGS)'
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTESTCOMPILEDRUNPREFIX) ./migrations.sqlite.test $(GOTESTCOMPILEDRUNSUFFIX)
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTESTCOMPILEDRUNPREFIX) ./migrations.sqlite.test $(GOTESTCOMPILEDRUNSUFFIX)
|
||||||
|
|
||||||
.PHONY: migrations.individual.mysql.test
|
.PHONY: migrations.individual.mysql.test
|
||||||
|
@ -787,7 +753,7 @@ migrations.individual.mysql.test: $(GO_SOURCES)
|
||||||
|
|
||||||
.PHONY: migrations.individual.sqlite.test\#%
|
.PHONY: migrations.individual.sqlite.test\#%
|
||||||
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
|
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/migrations/$*
|
||||||
|
|
||||||
.PHONY: migrations.individual.pgsql.test
|
.PHONY: migrations.individual.pgsql.test
|
||||||
migrations.individual.pgsql.test: $(GO_SOURCES)
|
migrations.individual.pgsql.test: $(GO_SOURCES)
|
||||||
|
@ -797,7 +763,7 @@ migrations.individual.pgsql.test: $(GO_SOURCES)
|
||||||
|
|
||||||
.PHONY: migrations.individual.pgsql.test\#%
|
.PHONY: migrations.individual.pgsql.test\#%
|
||||||
migrations.individual.pgsql.test\#%: $(GO_SOURCES) generate-ini-pgsql
|
migrations.individual.pgsql.test\#%: $(GO_SOURCES) generate-ini-pgsql
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/migrations/$*
|
||||||
|
|
||||||
.PHONY: migrations.individual.sqlite.test
|
.PHONY: migrations.individual.sqlite.test
|
||||||
migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite
|
migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite
|
||||||
|
@ -807,16 +773,16 @@ migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite
|
||||||
|
|
||||||
.PHONY: migrations.individual.sqlite.test\#%
|
.PHONY: migrations.individual.sqlite.test\#%
|
||||||
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
|
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/migrations/$*
|
||||||
|
|
||||||
e2e.mysql.test: $(GO_SOURCES)
|
e2e.mysql.test: $(GO_SOURCES)
|
||||||
$(GOTEST) $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.mysql.test
|
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/e2e -o e2e.mysql.test
|
||||||
|
|
||||||
e2e.pgsql.test: $(GO_SOURCES)
|
e2e.pgsql.test: $(GO_SOURCES)
|
||||||
$(GOTEST) $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.pgsql.test
|
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/e2e -o e2e.pgsql.test
|
||||||
|
|
||||||
e2e.sqlite.test: $(GO_SOURCES)
|
e2e.sqlite.test: $(GO_SOURCES)
|
||||||
$(GOTEST) $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.sqlite.test -tags '$(TEST_TAGS)'
|
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/e2e -o e2e.sqlite.test -tags '$(TEST_TAGS)'
|
||||||
|
|
||||||
.PHONY: check
|
.PHONY: check
|
||||||
check: test
|
check: test
|
||||||
|
@ -826,8 +792,8 @@ check: test
|
||||||
###
|
###
|
||||||
|
|
||||||
.PHONY: install $(TAGS_PREREQ)
|
.PHONY: install $(TAGS_PREREQ)
|
||||||
install: $(wildcard *.go)
|
install: $(wildcard *.go) | verify-version
|
||||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) install -v -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)'
|
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) install -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)'
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build: frontend backend
|
build: frontend backend
|
||||||
|
@ -854,14 +820,14 @@ generate-go: $(TAGS_PREREQ)
|
||||||
merge-locales:
|
merge-locales:
|
||||||
@echo "NOT NEEDED: THIS IS A NOOP AS OF Forgejo 7.0 BUT KEPT FOR BACKWARD COMPATIBILITY"
|
@echo "NOT NEEDED: THIS IS A NOOP AS OF Forgejo 7.0 BUT KEPT FOR BACKWARD COMPATIBILITY"
|
||||||
|
|
||||||
$(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ)
|
$(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ) | verify-version
|
||||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
|
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $@
|
||||||
|
|
||||||
forgejo: $(EXECUTABLE)
|
forgejo: $(EXECUTABLE)
|
||||||
ln -f $(EXECUTABLE) forgejo
|
ln -f $(EXECUTABLE) forgejo
|
||||||
|
|
||||||
static-executable: $(GO_SOURCES) $(TAGS_PREREQ)
|
static-executable: $(GO_SOURCES) $(TAGS_PREREQ) | verify-version
|
||||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags 'netgo osusergo $(TAGS)' -ldflags '-s -w -linkmode external -extldflags "-static" $(LDFLAGS)' -o $(EXECUTABLE)
|
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -o $(EXECUTABLE)
|
||||||
|
|
||||||
.PHONY: release
|
.PHONY: release
|
||||||
release: frontend generate release-linux release-copy release-compress vendor release-sources release-check
|
release: frontend generate release-linux release-copy release-compress vendor release-sources release-check
|
||||||
|
@ -872,23 +838,19 @@ sources-tarbal: frontend generate vendor release-sources release-check
|
||||||
$(DIST_DIRS):
|
$(DIST_DIRS):
|
||||||
mkdir -p $(DIST_DIRS)
|
mkdir -p $(DIST_DIRS)
|
||||||
|
|
||||||
.PHONY: release-windows
|
|
||||||
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) .
|
|
||||||
|
|
||||||
.PHONY: release-linux
|
.PHONY: release-linux
|
||||||
release-linux: | $(DIST_DIRS)
|
release-linux: | $(DIST_DIRS) verify-version
|
||||||
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 forgejo-$(VERSION) .
|
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 forgejo-$(VERSION) .
|
||||||
ifeq ($(CI),true)
|
ifeq ($(CI),true)
|
||||||
cp /build/* $(DIST)/binaries
|
cp /build/* $(DIST)/binaries
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: release-darwin
|
.PHONY: release-darwin
|
||||||
release-darwin: | $(DIST_DIRS)
|
release-darwin: | $(DIST_DIRS) verify-version
|
||||||
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 '$(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) verify-version
|
||||||
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 '$(LDFLAGS)' -targets 'freebsd/amd64' -out gitea-$(VERSION) .
|
||||||
|
|
||||||
.PHONY: release-copy
|
.PHONY: release-copy
|
||||||
|
@ -942,10 +904,7 @@ reproduce-build\#%:
|
||||||
###
|
###
|
||||||
|
|
||||||
.PHONY: deps
|
.PHONY: deps
|
||||||
deps: deps-frontend deps-backend deps-tools deps-py
|
deps: deps-frontend deps-backend deps-tools
|
||||||
|
|
||||||
.PHONY: deps-py
|
|
||||||
deps-py: .venv
|
|
||||||
|
|
||||||
.PHONY: deps-frontend
|
.PHONY: deps-frontend
|
||||||
deps-frontend: node_modules
|
deps-frontend: node_modules
|
||||||
|
@ -961,13 +920,11 @@ deps-tools:
|
||||||
$(GO) install $(GOFUMPT_PACKAGE)
|
$(GO) install $(GOFUMPT_PACKAGE)
|
||||||
$(GO) install $(GOLANGCI_LINT_PACKAGE)
|
$(GO) install $(GOLANGCI_LINT_PACKAGE)
|
||||||
$(GO) install $(GXZ_PACKAGE)
|
$(GO) install $(GXZ_PACKAGE)
|
||||||
$(GO) install $(MISSPELL_PACKAGE)
|
|
||||||
$(GO) install $(SWAGGER_PACKAGE)
|
$(GO) install $(SWAGGER_PACKAGE)
|
||||||
$(GO) install $(XGO_PACKAGE)
|
$(GO) install $(XGO_PACKAGE)
|
||||||
$(GO) install $(GO_LICENSES_PACKAGE)
|
$(GO) install $(GO_LICENSES_PACKAGE)
|
||||||
$(GO) install $(GOVULNCHECK_PACKAGE)
|
$(GO) install $(GOVULNCHECK_PACKAGE)
|
||||||
$(GO) install $(GOMOCK_PACKAGE)
|
$(GO) install $(GOMOCK_PACKAGE)
|
||||||
$(GO) install $(GOPLS_PACKAGE)
|
|
||||||
|
|
||||||
node_modules: package-lock.json
|
node_modules: package-lock.json
|
||||||
npm install --no-save
|
npm install --no-save
|
||||||
|
@ -1027,12 +984,11 @@ generate-gitignore:
|
||||||
|
|
||||||
.PHONY: generate-gomock
|
.PHONY: generate-gomock
|
||||||
generate-gomock:
|
generate-gomock:
|
||||||
$(GO) run $(GOMOCK_PACKAGE) -package mock -destination ./modules/queue/mock/redisuniversalclient.go code.gitea.io/gitea/modules/nosql RedisClient
|
$(GO) run $(GOMOCK_PACKAGE) -package mock -destination ./modules/queue/mock/redisuniversalclient.go forgejo.org/modules/nosql RedisClient
|
||||||
|
|
||||||
.PHONY: generate-images
|
.PHONY: generate-images
|
||||||
generate-images: | node_modules
|
generate-images: | node_modules
|
||||||
npm install --no-save fabric@6 imagemin-zopfli@7
|
node tools/generate-images.js
|
||||||
node tools/generate-images.js $(TAGS)
|
|
||||||
|
|
||||||
.PHONY: generate-manpage
|
.PHONY: generate-manpage
|
||||||
generate-manpage:
|
generate-manpage:
|
||||||
|
|
|
@ -15,11 +15,6 @@ Our promise: **Independent Free/Libre Software forever!**
|
||||||
|
|
||||||
## What does Forgejo offer?
|
## What does Forgejo offer?
|
||||||
|
|
||||||
<!-- If you want to know what Forgejo is like,
|
|
||||||
you can check out public instances,
|
|
||||||
e.g. [Codeberg.org](https://codeberg.org).
|
|
||||||
-->
|
|
||||||
|
|
||||||
If you like any of the following, Forgejo is literally meant for you:
|
If you like any of the following, Forgejo is literally meant for you:
|
||||||
|
|
||||||
- Lightweight: Forgejo can easily be hosted on nearly **every machine**.
|
- Lightweight: Forgejo can easily be hosted on nearly **every machine**.
|
||||||
|
|
|
@ -1590,7 +1590,7 @@ this situation, [follow the instructions in the companion blog post](https://for
|
||||||
|
|
||||||
The most prominent ones are described here, others can be found in the list of commits included in the release as described above.
|
The most prominent ones are described here, others can be found in the list of commits included in the release as described above.
|
||||||
|
|
||||||
* [Fix links to pull request reviews sent via mail](https://codeberg.org/forgejo/forgejo/commit/88e179d5ef8ee41f71d068195685ff098b38ca31). The pull request link was correct but it did not go the the review and stayed at the beginning of the page
|
* [Fix links to pull request reviews sent via mail](https://codeberg.org/forgejo/forgejo/commit/88e179d5ef8ee41f71d068195685ff098b38ca31). The pull request link was correct but it did not go the review and stayed at the beginning of the page
|
||||||
* [Recognize OGG as an audio format](https://codeberg.org/forgejo/forgejo/commit/622ec5c79f299c32ac2667a1aa7b4bf5d7c2d6cf)
|
* [Recognize OGG as an audio format](https://codeberg.org/forgejo/forgejo/commit/622ec5c79f299c32ac2667a1aa7b4bf5d7c2d6cf)
|
||||||
* [Consistently show the last time a cron job was run in the admin panel](https://codeberg.org/forgejo/forgejo/commit/5f769ef20)
|
* [Consistently show the last time a cron job was run in the admin panel](https://codeberg.org/forgejo/forgejo/commit/5f769ef20)
|
||||||
* [Fix NuGet registry v2 & v3 API search endpoints](https://codeberg.org/forgejo/forgejo/commit/471138829b0c24fe8c621dbb866ae8bb45ebc674)
|
* [Fix NuGet registry v2 & v3 API search endpoints](https://codeberg.org/forgejo/forgejo/commit/471138829b0c24fe8c621dbb866ae8bb45ebc674)
|
||||||
|
@ -1609,7 +1609,7 @@ this situation, [follow the instructions in the companion blog post](https://for
|
||||||
* [Fix pull request check list when there are more than 30](https://codeberg.org/forgejo/forgejo/commit/e226b9646)
|
* [Fix pull request check list when there are more than 30](https://codeberg.org/forgejo/forgejo/commit/e226b9646)
|
||||||
* [Fix attachment clipboard copy on insecure origin](https://codeberg.org/forgejo/forgejo/commit/12ac84c26)
|
* [Fix attachment clipboard copy on insecure origin](https://codeberg.org/forgejo/forgejo/commit/12ac84c26)
|
||||||
* [Fix the profile README rendering](https://codeberg.org/forgejo/forgejo/commit/84c3b60a4) that [was inconsistent with other markdown files renderings](https://codeberg.org/forgejo/forgejo/issues/833)
|
* [Fix the profile README rendering](https://codeberg.org/forgejo/forgejo/commit/84c3b60a4) that [was inconsistent with other markdown files renderings](https://codeberg.org/forgejo/forgejo/issues/833)
|
||||||
* [Fix API leaking the user email when the caller is not authentified](https://codeberg.org/forgejo/forgejo/commit/d89003cc1)
|
* [Fix API leaking the user email when the caller is not authenticated](https://codeberg.org/forgejo/forgejo/commit/d89003cc1)
|
||||||
|
|
||||||
## 1.20.2-0
|
## 1.20.2-0
|
||||||
|
|
||||||
|
@ -1667,7 +1667,7 @@ $ git -C forgejo log --oneline --no-merges origin/v1.19/forgejo..origin/v1.20/fo
|
||||||
The semantic version was updated to `5.0.0+0-gitea-1.20.1` because it contains breaking changes.
|
The semantic version was updated to `5.0.0+0-gitea-1.20.1` because it contains breaking changes.
|
||||||
- **Breaking:**
|
- **Breaking:**
|
||||||
- [Scoped access tokens](https://codeberg.org/forgejo/forgejo/commit/18de83b2a3fc120922096b7348d6375094ae1532) or (Personal Access Tokens), were refactored and although existing tokens are still valid, they may have a different scope than before. To ensure that no tokens have a larger scope than expected they must be removed and recreated.
|
- [Scoped access tokens](https://codeberg.org/forgejo/forgejo/commit/18de83b2a3fc120922096b7348d6375094ae1532) or (Personal Access Tokens), were refactored and although existing tokens are still valid, they may have a different scope than before. To ensure that no tokens have a larger scope than expected they must be removed and recreated.
|
||||||
- If your `app.ini` has one of the the following `[indexer].ISSUE_INDEXER_QUEUE_TYPE`, `[indexer].ISSUE_INDEXER_QUEUE_BATCH_NUMBER`, `[indexer].`, `[indexer].ISSUE_INDEXER_QUEUE_DIR`, `[indexer].ISSUE_INDEXER_QUEUE_CONN_STR`, `[indexer].UPDATE_BUFFER_LEN`, `[mailer].SEND_BUFFER_LEN`, `[repository].PULL_REQUEST_QUEUE_LENGTH` or `[repository].MIRROR_QUEUE_LENGTH`, Forgejo will abort immediately. Unless you know exactly what you're doing, you must comment them out so the default values are used.
|
- If your `app.ini` has one of the following `[indexer].ISSUE_INDEXER_QUEUE_TYPE`, `[indexer].ISSUE_INDEXER_QUEUE_BATCH_NUMBER`, `[indexer].`, `[indexer].ISSUE_INDEXER_QUEUE_DIR`, `[indexer].ISSUE_INDEXER_QUEUE_CONN_STR`, `[indexer].UPDATE_BUFFER_LEN`, `[mailer].SEND_BUFFER_LEN`, `[repository].PULL_REQUEST_QUEUE_LENGTH` or `[repository].MIRROR_QUEUE_LENGTH`, Forgejo will abort immediately. Unless you know exactly what you're doing, you must comment them out so the default values are used.
|
||||||
- The `-p` option of `environment-to-ini` is [no longer supported](https://codeberg.org/forgejo/forgejo/commit/fa0b5b14c2faa6a5f76bb2e7bc9241a5e4354189)
|
- The `-p` option of `environment-to-ini` is [no longer supported](https://codeberg.org/forgejo/forgejo/commit/fa0b5b14c2faa6a5f76bb2e7bc9241a5e4354189)
|
||||||
- The ".png" suffix for [user and organizations is now reserved](https://codeberg.org/forgejo/forgejo/commit/2b91841cd3e1213ff3e4ed4209d6a4be89c2fa79)
|
- The ".png" suffix for [user and organizations is now reserved](https://codeberg.org/forgejo/forgejo/commit/2b91841cd3e1213ff3e4ed4209d6a4be89c2fa79)
|
||||||
- The section `[git.reflog]` is [now obsolete and its keys have been moved](https://codeberg.org/forgejo/forgejo/commit/2f149c5c9db97f20fbbc65e32d1f3133048b11a2) to the following replacements:
|
- The section `[git.reflog]` is [now obsolete and its keys have been moved](https://codeberg.org/forgejo/forgejo/commit/2f149c5c9db97f20fbbc65e32d1f3133048b11a2) to the following replacements:
|
||||||
|
@ -1761,7 +1761,7 @@ $ git -C forgejo log --oneline --no-merges origin/v1.19/forgejo..origin/v1.20/fo
|
||||||
- [The repository migration can be canceled](https://codeberg.org/forgejo/forgejo/commit/f6e029e6c7849d4361abf7f1d749b5d528364ac4)
|
- [The repository migration can be canceled](https://codeberg.org/forgejo/forgejo/commit/f6e029e6c7849d4361abf7f1d749b5d528364ac4)
|
||||||
- [Add button on the diff header to copy the file name](https://codeberg.org/forgejo/forgejo/commit/c5ede35124c8d5280219c24049bb0ad7da9f02ed)
|
- [Add button on the diff header to copy the file name](https://codeberg.org/forgejo/forgejo/commit/c5ede35124c8d5280219c24049bb0ad7da9f02ed)
|
||||||
- [Add --quiet option to the dump CLI](https://codeberg.org/forgejo/forgejo/commit/cb1536471bcef4d78a3fe5cbd738b9f60fabbcc2)
|
- [Add --quiet option to the dump CLI](https://codeberg.org/forgejo/forgejo/commit/cb1536471bcef4d78a3fe5cbd738b9f60fabbcc2)
|
||||||
- [Support searching for an issue with its number in the the list of issues](https://codeberg.org/forgejo/forgejo/commit/1144b1d129de530b2c07dfdfaf55de383cd82212)
|
- [Support searching for an issue with its number in the list of issues](https://codeberg.org/forgejo/forgejo/commit/1144b1d129de530b2c07dfdfaf55de383cd82212)
|
||||||
- [Improve the list of notifications](https://codeberg.org/forgejo/forgejo/commit/f7ede92f82f7f3ec7bb31a1249f9524e5b728f34)
|
- [Improve the list of notifications](https://codeberg.org/forgejo/forgejo/commit/f7ede92f82f7f3ec7bb31a1249f9524e5b728f34)
|
||||||
- [When editing a file in the web UI, allow for a preview whenever possible](https://codeberg.org/forgejo/forgejo/commit/ac64c8297444ade63a2a364c4afb7e6c1de5a75f)
|
- [When editing a file in the web UI, allow for a preview whenever possible](https://codeberg.org/forgejo/forgejo/commit/ac64c8297444ade63a2a364c4afb7e6c1de5a75f)
|
||||||
- [Make release download URLs human readable](https://codeberg.org/forgejo/forgejo/commit/42919ccb7cd32ab67d0878baf2bac6cd007899a8)
|
- [Make release download URLs human readable](https://codeberg.org/forgejo/forgejo/commit/42919ccb7cd32ab67d0878baf2bac6cd007899a8)
|
||||||
|
@ -1798,7 +1798,7 @@ $ git -C forgejo log --oneline --no-merges origin/v1.19/forgejo..origin/v1.20/fo
|
||||||
- [Add API for gitignore templates](https://codeberg.org/forgejo/forgejo/commit/36a5d4c2f3b5670e5e921034cd5d25817534a6d4)
|
- [Add API for gitignore templates](https://codeberg.org/forgejo/forgejo/commit/36a5d4c2f3b5670e5e921034cd5d25817534a6d4)
|
||||||
- [Add API to upuload a file to an empty repository](https://codeberg.org/forgejo/forgejo/commit/cf465b472166ccf6d3e001e3043e4bf43e16e6b3)
|
- [Add API to upuload a file to an empty repository](https://codeberg.org/forgejo/forgejo/commit/cf465b472166ccf6d3e001e3043e4bf43e16e6b3)
|
||||||
- [Allow for --not when listing the commits of a repo](https://codeberg.org/forgejo/forgejo/commit/f766b002938b5c81e343c81fda3c0669fa09809f)
|
- [Allow for --not when listing the commits of a repo](https://codeberg.org/forgejo/forgejo/commit/f766b002938b5c81e343c81fda3c0669fa09809f)
|
||||||
- [Add `files` and `verification` parameters to improve performances when listing the commits of a a repo](https://codeberg.org/forgejo/forgejo/commit/1dd83dbb917d55bd253001646d6743f247a4d98b)
|
- [Add `files` and `verification` parameters to improve performances when listing the commits of a repo](https://codeberg.org/forgejo/forgejo/commit/1dd83dbb917d55bd253001646d6743f247a4d98b)
|
||||||
- [Allow for listing a single commit in a repository](https://codeberg.org/forgejo/forgejo/commit/5930ab5fdf7a970fcca3cd50b44cf1cacb615a54)
|
- [Allow for listing a single commit in a repository](https://codeberg.org/forgejo/forgejo/commit/5930ab5fdf7a970fcca3cd50b44cf1cacb615a54)
|
||||||
- [Create a branch directly from commit on the create branch API](https://codeberg.org/forgejo/forgejo/commit/cd9a13ebb47d32f46b38439a524e3b2e0c619490)
|
- [Create a branch directly from commit on the create branch API](https://codeberg.org/forgejo/forgejo/commit/cd9a13ebb47d32f46b38439a524e3b2e0c619490)
|
||||||
- [Add API for Label templates](https://codeberg.org/forgejo/forgejo/commit/25dc1556cd70b567a4920beb002a0addfbfd6ef2)
|
- [Add API for Label templates](https://codeberg.org/forgejo/forgejo/commit/25dc1556cd70b567a4920beb002a0addfbfd6ef2)
|
||||||
|
|
117
assets/go-licenses.json
generated
117
assets/go-licenses.json
generated
File diff suppressed because one or more lines are too long
14
build.go
14
build.go
|
@ -1,14 +0,0 @@
|
||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
//go:build vendor
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
// Libraries that are included to vendor utilities used during build.
|
|
||||||
// These libraries will not be included in a normal compilation.
|
|
||||||
|
|
||||||
import (
|
|
||||||
// for embed
|
|
||||||
_ "github.com/shurcooL/vfsgen"
|
|
||||||
)
|
|
|
@ -12,8 +12,8 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/container"
|
"forgejo.org/modules/container"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
|
@ -15,7 +15,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/build/codeformat"
|
"forgejo.org/build/codeformat"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Windows has a limitation for command line arguments, the size can not exceed 32KB.
|
// Windows has a limitation for command line arguments, the size can not exceed 32KB.
|
||||||
|
|
|
@ -13,8 +13,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var importPackageGroupOrders = map[string]int{
|
var importPackageGroupOrders = map[string]int{
|
||||||
"": 1, // internal
|
"": 1, // internal
|
||||||
"code.gitea.io/gitea/": 2,
|
"forgejo.org/": 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
var errInvalidCommentBetweenImports = errors.New("comments between imported packages are invalid, please move comments to the end of the package line")
|
var errInvalidCommentBetweenImports = errors.New("comments between imported packages are invalid, please move comments to the end of the package line")
|
||||||
|
|
|
@ -58,8 +58,8 @@ import (
|
||||||
|
|
||||||
"code.gitea.io/other/package"
|
"code.gitea.io/other/package"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"forgejo.org/modules/util"
|
||||||
|
|
||||||
"xorm.io/the/package"
|
"xorm.io/the/package"
|
||||||
|
|
||||||
|
@ -82,8 +82,8 @@ import (
|
||||||
_ "image/jpeg" // for processing jpeg images
|
_ "image/jpeg" // for processing jpeg images
|
||||||
_ "image/png" // for processing png images
|
_ "image/png" // for processing png images
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"forgejo.org/modules/util"
|
||||||
|
|
||||||
"code.gitea.io/other/package"
|
"code.gitea.io/other/package"
|
||||||
"github.com/issue9/identicon"
|
"github.com/issue9/identicon"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
//go:build ignore
|
//go:build ignore
|
||||||
|
|
||||||
|
@ -7,30 +8,40 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha1"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
"github.com/shurcooL/vfsgen"
|
"github.com/klauspost/compress/zstd"
|
||||||
)
|
)
|
||||||
|
|
||||||
func needsUpdate(dir, filename string) (bool, []byte) {
|
func fileExists(filename string) bool {
|
||||||
needRegen := false
|
|
||||||
_, err := os.Stat(filename)
|
_, err := os.Stat(filename)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
needRegen = true
|
return true
|
||||||
}
|
}
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func needsUpdate(dir, filename string) (bool, []byte) {
|
||||||
|
needRegen := !fileExists(filename)
|
||||||
|
|
||||||
oldHash, err := os.ReadFile(filename + ".hash")
|
oldHash, err := os.ReadFile(filename + ".hash")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
oldHash = []byte{}
|
oldHash = []byte{}
|
||||||
}
|
}
|
||||||
|
|
||||||
hasher := sha1.New()
|
hasher := sha256.New()
|
||||||
|
|
||||||
err = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
|
err = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -51,7 +62,7 @@ func needsUpdate(dir, filename string) (bool, []byte) {
|
||||||
|
|
||||||
newHash := hasher.Sum([]byte{})
|
newHash := hasher.Sum([]byte{})
|
||||||
|
|
||||||
if bytes.Compare(oldHash, newHash) != 0 {
|
if !bytes.Equal(oldHash, newHash) {
|
||||||
return true, newHash
|
return true, newHash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,24 +80,280 @@ func main() {
|
||||||
useGlobalModTime, _ = strconv.ParseBool(os.Args[4])
|
useGlobalModTime, _ = strconv.ParseBool(os.Args[4])
|
||||||
}
|
}
|
||||||
|
|
||||||
update, newHash := needsUpdate(dir, filename)
|
if os.Getenv("FORGEJO_GENERATE_SKIP_HASH") == "true" && fileExists(filename) {
|
||||||
|
fmt.Printf("bindata %s already exists and FORGEJO_GENERATE_SKIP_HASH=true\n", packageName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
update, newHash := needsUpdate(dir, filename)
|
||||||
if !update {
|
if !update {
|
||||||
fmt.Printf("bindata for %s already up-to-date\n", packageName)
|
fmt.Printf("bindata %s already exists and the checksum is a match\n", packageName)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("generating bindata for %s\n", packageName)
|
fmt.Printf("generating bindata for %s\n", packageName)
|
||||||
var fsTemplates http.FileSystem = http.Dir(dir)
|
|
||||||
err := vfsgen.Generate(fsTemplates, vfsgen.Options{
|
root, err := os.OpenRoot(dir)
|
||||||
PackageName: packageName,
|
|
||||||
BuildTags: "bindata",
|
|
||||||
VariableName: "Assets",
|
|
||||||
Filename: filename,
|
|
||||||
UseGlobalModTime: useGlobalModTime,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("%v\n", err)
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
if err := generate(root.FS(), packageName, useGlobalModTime, out); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
_ = os.WriteFile(filename+".hash", newHash, 0o666)
|
_ = os.WriteFile(filename+".hash", newHash, 0o666)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type file struct {
|
||||||
|
Path string
|
||||||
|
Name string
|
||||||
|
UncompressedSize int
|
||||||
|
CompressedData []byte
|
||||||
|
UncompressedData []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type direntry struct {
|
||||||
|
Name string
|
||||||
|
IsDir bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func generate(fsRoot fs.FS, packageName string, globalTime bool, output io.Writer) error {
|
||||||
|
enc, err := zstd.NewWriter(nil, zstd.WithEncoderLevel(zstd.SpeedBestCompression))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
files := []file{}
|
||||||
|
|
||||||
|
dirs := map[string][]direntry{}
|
||||||
|
|
||||||
|
if err := fs.WalkDir(fsRoot, ".", func(filePath string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.IsDir() {
|
||||||
|
entries, err := fs.ReadDir(fsRoot, filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dirEntries := make([]direntry, 0, len(entries))
|
||||||
|
for _, entry := range entries {
|
||||||
|
dirEntries = append(dirEntries, direntry{Name: entry.Name(), IsDir: entry.IsDir()})
|
||||||
|
}
|
||||||
|
dirs[filePath] = dirEntries
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
src, err := fs.ReadFile(fsRoot, filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dst := enc.EncodeAll(src, nil)
|
||||||
|
if len(dst) < len(src) {
|
||||||
|
files = append(files, file{
|
||||||
|
Path: filePath,
|
||||||
|
Name: path.Base(filePath),
|
||||||
|
UncompressedSize: len(src),
|
||||||
|
CompressedData: dst,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
files = append(files, file{
|
||||||
|
Path: filePath,
|
||||||
|
Name: path.Base(filePath),
|
||||||
|
UncompressedData: src,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return generatedTmpl.Execute(output, map[string]any{
|
||||||
|
"Packagename": packageName,
|
||||||
|
"GlobalTime": globalTime,
|
||||||
|
"Files": files,
|
||||||
|
"Dirs": dirs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var generatedTmpl = template.Must(template.New("").Parse(`// Code generated by efs-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
//go:build bindata
|
||||||
|
|
||||||
|
package {{.Packagename}}
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"time"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
|
||||||
|
"github.com/klauspost/compress/zstd"
|
||||||
|
)
|
||||||
|
|
||||||
|
type normalFile struct {
|
||||||
|
name string
|
||||||
|
content []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type compressedFile struct {
|
||||||
|
name string
|
||||||
|
uncompressedSize int64
|
||||||
|
data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var files = map[string]any{
|
||||||
|
{{- range .Files}}
|
||||||
|
"{{.Path}}": {{if .CompressedData}}compressedFile{"{{.Name}}", {{.UncompressedSize}}, []byte({{printf "%+q" .CompressedData}})}{{else}}normalFile{"{{.Name}}", []byte({{printf "%+q" .UncompressedData}})}{{end}},
|
||||||
|
{{- end}}
|
||||||
|
}
|
||||||
|
|
||||||
|
var dirs = map[string][]fs.DirEntry{
|
||||||
|
{{- range $key, $entry := .Dirs}}
|
||||||
|
"{{$key}}": {
|
||||||
|
{{- range $entry}}
|
||||||
|
direntry{"{{.Name}}", {{.IsDir}}},
|
||||||
|
{{- end}}
|
||||||
|
},
|
||||||
|
{{- end}}
|
||||||
|
}
|
||||||
|
|
||||||
|
type assets struct{}
|
||||||
|
|
||||||
|
var Assets = assets{}
|
||||||
|
|
||||||
|
func (a assets) Open(name string) (fs.File, error) {
|
||||||
|
f, ok := files[name]
|
||||||
|
if !ok {
|
||||||
|
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch f := f.(type) {
|
||||||
|
case normalFile:
|
||||||
|
return file{name: f.name, size: int64(len(f.content)), data: bytes.NewReader(f.content)}, nil
|
||||||
|
case compressedFile:
|
||||||
|
r, _ := zstd.NewReader(bytes.NewReader(f.data))
|
||||||
|
return &compressFile{name: f.name, size: f.uncompressedSize, data: r, content: f.data}, nil
|
||||||
|
default:
|
||||||
|
panic("unknown file type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a assets) ReadDir(name string) ([]fs.DirEntry, error) {
|
||||||
|
d, ok := dirs[name]
|
||||||
|
if !ok {
|
||||||
|
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type file struct {
|
||||||
|
name string
|
||||||
|
size int64
|
||||||
|
data io.ReadSeeker
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ io.ReadSeeker = (*file)(nil)
|
||||||
|
|
||||||
|
func (f file) Stat() (fs.FileInfo, error) {
|
||||||
|
return fileinfo{name: f.name, size: f.size}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f file) Read(p []byte) (int, error) {
|
||||||
|
return f.data.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f file) Seek(offset int64, whence int) (int64, error) {
|
||||||
|
return f.data.Seek(offset, whence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f file) Close() error { return nil }
|
||||||
|
|
||||||
|
type compressFile struct {
|
||||||
|
name string
|
||||||
|
size int64
|
||||||
|
data *zstd.Decoder
|
||||||
|
content []byte
|
||||||
|
zstdPos int64
|
||||||
|
seekPos int64
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ io.ReadSeeker = (*compressFile)(nil)
|
||||||
|
|
||||||
|
func (f *compressFile) Stat() (fs.FileInfo, error) {
|
||||||
|
return fileinfo{name: f.name, size: f.size}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *compressFile) Read(p []byte) (int, error) {
|
||||||
|
if f.zstdPos > f.seekPos {
|
||||||
|
if err := f.data.Reset(bytes.NewReader(f.content)); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
f.zstdPos = 0
|
||||||
|
}
|
||||||
|
if f.zstdPos < f.seekPos {
|
||||||
|
if _, err := io.CopyN(io.Discard, f.data, f.seekPos - f.zstdPos); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
f.zstdPos = f.seekPos
|
||||||
|
}
|
||||||
|
n, err := f.data.Read(p)
|
||||||
|
f.zstdPos += int64(n)
|
||||||
|
f.seekPos = f.zstdPos
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *compressFile) Seek(offset int64, whence int) (int64, error) {
|
||||||
|
switch whence {
|
||||||
|
case io.SeekStart:
|
||||||
|
f.seekPos = 0 + offset
|
||||||
|
case io.SeekCurrent:
|
||||||
|
f.seekPos += offset
|
||||||
|
case io.SeekEnd:
|
||||||
|
f.seekPos = f.size + offset
|
||||||
|
}
|
||||||
|
return f.seekPos, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *compressFile) Close() error {
|
||||||
|
f.data.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *compressFile) ZstdBytes() []byte { return f.content }
|
||||||
|
|
||||||
|
type fileinfo struct {
|
||||||
|
name string
|
||||||
|
size int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fileinfo) Name() string { return f.name }
|
||||||
|
func (f fileinfo) Size() int64 { return f.size }
|
||||||
|
func (f fileinfo) Mode() fs.FileMode { return 0o444 }
|
||||||
|
func (f fileinfo) ModTime() time.Time { return {{if .GlobalTime}}GlobalModTime(f.name){{else}}time.Unix(0, 0){{end}} }
|
||||||
|
func (f fileinfo) IsDir() bool { return false }
|
||||||
|
func (f fileinfo) Sys() any { return nil }
|
||||||
|
|
||||||
|
type direntry struct {
|
||||||
|
name string
|
||||||
|
isDir bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d direntry) Name() string { return d.name }
|
||||||
|
func (d direntry) IsDir() bool { return d.isDir }
|
||||||
|
func (d direntry) Type() fs.FileMode {
|
||||||
|
if d.isDir {
|
||||||
|
return 0o755 | fs.ModeDir
|
||||||
|
}
|
||||||
|
return 0o444
|
||||||
|
}
|
||||||
|
func (direntry) Info() (fs.FileInfo, error) { return nil, fs.ErrNotExist }
|
||||||
|
`))
|
||||||
|
|
|
@ -20,7 +20,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/json"
|
"forgejo.org/modules/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -15,7 +15,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/util"
|
"forgejo.org/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
|
@ -16,7 +16,7 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/container"
|
"forgejo.org/modules/container"
|
||||||
)
|
)
|
||||||
|
|
||||||
// regexp is based on go-license, excluding README and NOTICE
|
// regexp is based on go-license, excluding README and NOTICE
|
||||||
|
@ -102,9 +102,9 @@ func main() {
|
||||||
pkgName := path.Dir(pkgPath)
|
pkgName := path.Dir(pkgPath)
|
||||||
|
|
||||||
// There might be a bug somewhere in go-licenses that sometimes interprets the
|
// There might be a bug somewhere in go-licenses that sometimes interprets the
|
||||||
// root package as "." and sometimes as "code.gitea.io/gitea". Workaround by
|
// root package as "." and sometimes as "forgejo.org". Workaround by
|
||||||
// removing both of them for the sake of stable output.
|
// removing both of them for the sake of stable output.
|
||||||
if pkgName == "." || pkgName == "code.gitea.io/gitea" {
|
if pkgName == "." || pkgName == "forgejo.org" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/util"
|
"forgejo.org/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
383
build/lint-locale-usage/lint-locale-usage.go
Normal file
383
build/lint-locale-usage/lint-locale-usage.go
Normal file
|
@ -0,0 +1,383 @@
|
||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
goParser "go/parser"
|
||||||
|
"go/token"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
tmplParser "text/template/parse"
|
||||||
|
|
||||||
|
"forgejo.org/modules/container"
|
||||||
|
fjTemplates "forgejo.org/modules/templates"
|
||||||
|
"forgejo.org/modules/translation/localeiter"
|
||||||
|
"forgejo.org/modules/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// this works by first gathering all valid source string IDs from `en-US` reference files
|
||||||
|
// and then checking if all used source strings are actually defined
|
||||||
|
|
||||||
|
type LocatedError struct {
|
||||||
|
Location string
|
||||||
|
Kind string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e LocatedError) Error() string {
|
||||||
|
var sb strings.Builder
|
||||||
|
|
||||||
|
sb.WriteString(e.Location)
|
||||||
|
sb.WriteString(":\t")
|
||||||
|
if e.Kind != "" {
|
||||||
|
sb.WriteString(e.Kind)
|
||||||
|
sb.WriteString(": ")
|
||||||
|
}
|
||||||
|
sb.WriteString("ERROR: ")
|
||||||
|
sb.WriteString(e.Err.Error())
|
||||||
|
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitLocaleTrFunctions() map[string][]uint {
|
||||||
|
ret := make(map[string][]uint)
|
||||||
|
|
||||||
|
f0 := []uint{0}
|
||||||
|
ret["Tr"] = f0
|
||||||
|
ret["TrString"] = f0
|
||||||
|
ret["TrHTML"] = f0
|
||||||
|
|
||||||
|
ret["TrPluralString"] = []uint{1}
|
||||||
|
ret["TrN"] = []uint{1, 2}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
OnMsgid func(fset *token.FileSet, pos token.Pos, msgid string)
|
||||||
|
OnUnexpectedInvoke func(fset *token.FileSet, pos token.Pos, funcname string, argc int)
|
||||||
|
LocaleTrFunctions map[string][]uint
|
||||||
|
}
|
||||||
|
|
||||||
|
// the `Handle*File` functions follow the following calling convention:
|
||||||
|
// * `fname` is the name of the input file
|
||||||
|
// * `src` is either `nil` (then the function invokes `ReadFile` to read the file)
|
||||||
|
// or the contents of the file as {`[]byte`, or a `string`}
|
||||||
|
|
||||||
|
func (handler Handler) HandleGoFile(fname string, src any) error {
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
node, err := goParser.ParseFile(fset, fname, src, goParser.SkipObjectResolution)
|
||||||
|
if err != nil {
|
||||||
|
return LocatedError{
|
||||||
|
Location: fname,
|
||||||
|
Kind: "Go parser",
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ast.Inspect(node, func(n ast.Node) bool {
|
||||||
|
// search for function calls of the form `anything.Tr(any-string-lit, ...)`
|
||||||
|
|
||||||
|
call, ok := n.(*ast.CallExpr)
|
||||||
|
if !ok || len(call.Args) < 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
funSel, ok := call.Fun.(*ast.SelectorExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
ltf, ok := handler.LocaleTrFunctions[funSel.Sel.Name]
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var gotUnexpectedInvoke *int
|
||||||
|
|
||||||
|
for _, argNum := range ltf {
|
||||||
|
if len(call.Args) >= int(argNum+1) {
|
||||||
|
argLit, ok := call.Args[int(argNum)].(*ast.BasicLit)
|
||||||
|
if !ok || argLit.Kind != token.STRING {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract string content
|
||||||
|
arg, err := strconv.Unquote(argLit.Value)
|
||||||
|
if err == nil {
|
||||||
|
// found interesting strings
|
||||||
|
handler.OnMsgid(fset, argLit.ValuePos, arg)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
argc := len(call.Args)
|
||||||
|
gotUnexpectedInvoke = &argc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotUnexpectedInvoke != nil {
|
||||||
|
handler.OnUnexpectedInvoke(fset, funSel.Sel.NamePos, funSel.Sel.Name, *gotUnexpectedInvoke)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// derived from source: modules/templates/scopedtmpl/scopedtmpl.go, L169-L213
|
||||||
|
func (handler Handler) handleTemplateNode(fset *token.FileSet, node tmplParser.Node) {
|
||||||
|
switch node.Type() {
|
||||||
|
case tmplParser.NodeAction:
|
||||||
|
handler.handleTemplatePipeNode(fset, node.(*tmplParser.ActionNode).Pipe)
|
||||||
|
case tmplParser.NodeList:
|
||||||
|
nodeList := node.(*tmplParser.ListNode)
|
||||||
|
handler.handleTemplateFileNodes(fset, nodeList.Nodes)
|
||||||
|
case tmplParser.NodePipe:
|
||||||
|
handler.handleTemplatePipeNode(fset, node.(*tmplParser.PipeNode))
|
||||||
|
case tmplParser.NodeTemplate:
|
||||||
|
handler.handleTemplatePipeNode(fset, node.(*tmplParser.TemplateNode).Pipe)
|
||||||
|
case tmplParser.NodeIf:
|
||||||
|
nodeIf := node.(*tmplParser.IfNode)
|
||||||
|
handler.handleTemplateBranchNode(fset, nodeIf.BranchNode)
|
||||||
|
case tmplParser.NodeRange:
|
||||||
|
nodeRange := node.(*tmplParser.RangeNode)
|
||||||
|
handler.handleTemplateBranchNode(fset, nodeRange.BranchNode)
|
||||||
|
case tmplParser.NodeWith:
|
||||||
|
nodeWith := node.(*tmplParser.WithNode)
|
||||||
|
handler.handleTemplateBranchNode(fset, nodeWith.BranchNode)
|
||||||
|
|
||||||
|
case tmplParser.NodeCommand:
|
||||||
|
nodeCommand := node.(*tmplParser.CommandNode)
|
||||||
|
|
||||||
|
handler.handleTemplateFileNodes(fset, nodeCommand.Args)
|
||||||
|
|
||||||
|
if len(nodeCommand.Args) < 2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeChain, ok := nodeCommand.Args[0].(*tmplParser.ChainNode)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeIdent, ok := nodeChain.Node.(*tmplParser.IdentifierNode)
|
||||||
|
if !ok || nodeIdent.Ident != "ctx" || len(nodeChain.Field) != 2 || nodeChain.Field[0] != "Locale" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ltf, ok := handler.LocaleTrFunctions[nodeChain.Field[1]]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var gotUnexpectedInvoke *int
|
||||||
|
|
||||||
|
for _, argNum := range ltf {
|
||||||
|
if len(nodeCommand.Args) >= int(argNum+2) {
|
||||||
|
nodeString, ok := nodeCommand.Args[int(argNum+1)].(*tmplParser.StringNode)
|
||||||
|
if ok {
|
||||||
|
// found interesting strings
|
||||||
|
// the column numbers are a bit "off", but much better than nothing
|
||||||
|
handler.OnMsgid(fset, token.Pos(nodeString.Pos), nodeString.Text)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
argc := len(nodeCommand.Args) - 1
|
||||||
|
gotUnexpectedInvoke = &argc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotUnexpectedInvoke != nil {
|
||||||
|
handler.OnUnexpectedInvoke(fset, token.Pos(nodeChain.Pos), nodeChain.Field[1], *gotUnexpectedInvoke)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler Handler) handleTemplatePipeNode(fset *token.FileSet, pipeNode *tmplParser.PipeNode) {
|
||||||
|
if pipeNode == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: we can't pass `pipeNode.Cmds` to handleTemplateFileNodes due to incompatible argument types
|
||||||
|
for _, node := range pipeNode.Cmds {
|
||||||
|
handler.handleTemplateNode(fset, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler Handler) handleTemplateBranchNode(fset *token.FileSet, branchNode tmplParser.BranchNode) {
|
||||||
|
handler.handleTemplatePipeNode(fset, branchNode.Pipe)
|
||||||
|
handler.handleTemplateFileNodes(fset, branchNode.List.Nodes)
|
||||||
|
if branchNode.ElseList != nil {
|
||||||
|
handler.handleTemplateFileNodes(fset, branchNode.ElseList.Nodes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler Handler) handleTemplateFileNodes(fset *token.FileSet, nodes []tmplParser.Node) {
|
||||||
|
for _, node := range nodes {
|
||||||
|
handler.handleTemplateNode(fset, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler Handler) HandleTemplateFile(fname string, src any) error {
|
||||||
|
var tmplContent []byte
|
||||||
|
switch src2 := src.(type) {
|
||||||
|
case nil:
|
||||||
|
var err error
|
||||||
|
tmplContent, err = os.ReadFile(fname)
|
||||||
|
if err != nil {
|
||||||
|
return LocatedError{
|
||||||
|
Location: fname,
|
||||||
|
Kind: "ReadFile",
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case []byte:
|
||||||
|
tmplContent = src2
|
||||||
|
case string:
|
||||||
|
// SAFETY: we do not modify tmplContent below
|
||||||
|
tmplContent = util.UnsafeStringToBytes(src2)
|
||||||
|
default:
|
||||||
|
panic("invalid type for 'src'")
|
||||||
|
}
|
||||||
|
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
fset.AddFile(fname, 1, len(tmplContent)).SetLinesForContent(tmplContent)
|
||||||
|
// SAFETY: we do not modify tmplContent2 below
|
||||||
|
tmplContent2 := util.UnsafeBytesToString(tmplContent)
|
||||||
|
|
||||||
|
tmpl := template.New(fname)
|
||||||
|
tmpl.Funcs(fjTemplates.NewFuncMap())
|
||||||
|
tmplParsed, err := tmpl.Parse(tmplContent2)
|
||||||
|
if err != nil {
|
||||||
|
return LocatedError{
|
||||||
|
Location: fname,
|
||||||
|
Kind: "Template parser",
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handler.handleTemplateFileNodes(fset, tmplParsed.Root.Nodes)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This command assumes that we get started from the project root directory
|
||||||
|
//
|
||||||
|
// Possible command line flags:
|
||||||
|
//
|
||||||
|
// --allow-missing-msgids don't return an error code if missing message IDs are found
|
||||||
|
//
|
||||||
|
// EXIT CODES:
|
||||||
|
//
|
||||||
|
// 0 success, no issues found
|
||||||
|
// 1 unable to walk directory tree
|
||||||
|
// 2 unable to parse locale ini/json files
|
||||||
|
// 3 unable to parse go or text/template files
|
||||||
|
// 4 found missing message IDs
|
||||||
|
//
|
||||||
|
//nolint:forbidigo
|
||||||
|
func main() {
|
||||||
|
allowMissingMsgids := false
|
||||||
|
for _, arg := range os.Args[1:] {
|
||||||
|
if arg == "--allow-missing-msgids" {
|
||||||
|
allowMissingMsgids = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onError := func(err error) {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
os.Exit(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
msgids := make(container.Set[string])
|
||||||
|
|
||||||
|
localeFile := filepath.Join(filepath.Join("options", "locale"), "locale_en-US.ini")
|
||||||
|
localeContent, err := os.ReadFile(localeFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s:\tERROR: %s\n", localeFile, err.Error())
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = localeiter.IterateMessagesContent(localeContent, func(trKey, trValue string) error {
|
||||||
|
msgids[trKey] = struct{}{}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
fmt.Printf("%s:\tERROR: %s\n", localeFile, err.Error())
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
localeFile = filepath.Join(filepath.Join("options", "locale_next"), "locale_en-US.json")
|
||||||
|
localeContent, err = os.ReadFile(localeFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s:\tERROR: %s\n", localeFile, err.Error())
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := localeiter.IterateMessagesNextContent(localeContent, func(trKey, pluralForm, trValue string) error {
|
||||||
|
// ignore plural form
|
||||||
|
msgids[trKey] = struct{}{}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
fmt.Printf("%s:\tERROR: %s\n", localeFile, err.Error())
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
gotAnyMsgidError := false
|
||||||
|
|
||||||
|
handler := Handler{
|
||||||
|
OnMsgid: func(fset *token.FileSet, pos token.Pos, msgid string) {
|
||||||
|
if !msgids.Contains(msgid) {
|
||||||
|
gotAnyMsgidError = true
|
||||||
|
fmt.Printf("%s:\tmissing msgid: %s\n", fset.Position(pos).String(), msgid)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
OnUnexpectedInvoke: func(fset *token.FileSet, pos token.Pos, funcname string, argc int) {
|
||||||
|
gotAnyMsgidError = true
|
||||||
|
fmt.Printf("%s:\tunexpected invocation of %s with %d arguments\n", fset.Position(pos).String(), funcname, argc)
|
||||||
|
},
|
||||||
|
LocaleTrFunctions: InitLocaleTrFunctions(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := filepath.WalkDir(".", func(fpath string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
name := d.Name()
|
||||||
|
if d.IsDir() {
|
||||||
|
if name == "docker" || name == ".git" || name == "node_modules" {
|
||||||
|
return fs.SkipDir
|
||||||
|
}
|
||||||
|
} else if name == "bindata.go" || fpath == "modules/translation/i18n/i18n_test.go" {
|
||||||
|
// skip false positives
|
||||||
|
} else if strings.HasSuffix(name, ".go") {
|
||||||
|
onError(handler.HandleGoFile(fpath, nil))
|
||||||
|
} else if strings.HasSuffix(name, ".tmpl") {
|
||||||
|
if strings.HasPrefix(fpath, "tests") && strings.HasSuffix(name, ".ini.tmpl") {
|
||||||
|
// skip false positives
|
||||||
|
} else {
|
||||||
|
onError(handler.HandleTemplateFile(fpath, nil))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
fmt.Printf("walkdir ERROR: %s\n", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allowMissingMsgids && gotAnyMsgidError {
|
||||||
|
os.Exit(4)
|
||||||
|
}
|
||||||
|
}
|
50
build/lint-locale-usage/lint-locale-usage_test.go
Normal file
50
build/lint-locale-usage/lint-locale-usage_test.go
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/token"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildHandler(ret *[]string) Handler {
|
||||||
|
return Handler{
|
||||||
|
OnMsgid: func(fset *token.FileSet, pos token.Pos, msgid string) {
|
||||||
|
*ret = append(*ret, msgid)
|
||||||
|
},
|
||||||
|
OnUnexpectedInvoke: func(fset *token.FileSet, pos token.Pos, funcname string, argc int) {},
|
||||||
|
LocaleTrFunctions: InitLocaleTrFunctions(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleGoFileWrapped(t *testing.T, fname, src string) []string {
|
||||||
|
var ret []string
|
||||||
|
handler := buildHandler(&ret)
|
||||||
|
require.NoError(t, handler.HandleGoFile(fname, src))
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleTemplateFileWrapped(t *testing.T, fname, src string) []string {
|
||||||
|
var ret []string
|
||||||
|
handler := buildHandler(&ret)
|
||||||
|
require.NoError(t, handler.HandleTemplateFile(fname, src))
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsagesParser(t *testing.T) {
|
||||||
|
t.Run("go, simple", func(t *testing.T) {
|
||||||
|
assert.Equal(t,
|
||||||
|
[]string{"what.an.example"},
|
||||||
|
HandleGoFileWrapped(t, "<g1>", "package main\nfunc Render(ctx *context.Context) string { return ctx.Tr(\"what.an.example\"); }\n"))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("template, simple", func(t *testing.T) {
|
||||||
|
assert.Equal(t,
|
||||||
|
[]string{"what.an.example"},
|
||||||
|
HandleTemplateFileWrapped(t, "<t1>", "{{ ctx.Locale.Tr \"what.an.example\" }}\n"))
|
||||||
|
})
|
||||||
|
}
|
|
@ -14,9 +14,10 @@ import (
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"forgejo.org/modules/translation/localeiter"
|
||||||
|
|
||||||
"github.com/microcosm-cc/bluemonday"
|
"github.com/microcosm-cc/bluemonday"
|
||||||
"github.com/sergi/go-diff/diffmatchpatch"
|
"github.com/sergi/go-diff/diffmatchpatch"
|
||||||
"gopkg.in/ini.v1" //nolint:depguard
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -26,6 +27,8 @@ var (
|
||||||
|
|
||||||
// Matches href="", href="#", href="%s", href="#%s", href="%[1]s" and href="#%[1]s".
|
// Matches href="", href="#", href="%s", href="#%s", href="%[1]s" and href="#%[1]s".
|
||||||
placeHolderRegex = regexp.MustCompile(`href="#?(%s|%\[\d\]s)?"`)
|
placeHolderRegex = regexp.MustCompile(`href="#?(%s|%\[\d\]s)?"`)
|
||||||
|
|
||||||
|
dmp = diffmatchpatch.New()
|
||||||
)
|
)
|
||||||
|
|
||||||
func initBlueMondayPolicy() {
|
func initBlueMondayPolicy() {
|
||||||
|
@ -49,7 +52,7 @@ func initBlueMondayPolicy() {
|
||||||
policy.AllowAttrs("id").Matching(positionalPlaceholderRe).OnElements("code")
|
policy.AllowAttrs("id").Matching(positionalPlaceholderRe).OnElements("code")
|
||||||
|
|
||||||
// Allowed elements with no attributes. Must be a recognized tagname.
|
// Allowed elements with no attributes. Must be a recognized tagname.
|
||||||
policy.AllowElements("strong", "br", "b", "strike", "code", "i")
|
policy.AllowElements("strong", "br", "b", "strike", "code", "i", "kbd")
|
||||||
|
|
||||||
// TODO: Remove <c> in `actions.workflow.dispatch.trigger_found`.
|
// TODO: Remove <c> in `actions.workflow.dispatch.trigger_found`.
|
||||||
policy.AllowNoAttrs().OnElements("c")
|
policy.AllowNoAttrs().OnElements("c")
|
||||||
|
@ -79,41 +82,48 @@ func preprocessTranslationValue(value string) string {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkLocaleContent(localeContent []byte) []string {
|
func checkValue(trKey, value string) []string {
|
||||||
// Same configuration as Forgejo uses.
|
keyValue := preprocessTranslationValue(value)
|
||||||
cfg := ini.Empty(ini.LoadOptions{
|
|
||||||
IgnoreContinuation: true,
|
|
||||||
})
|
|
||||||
cfg.NameMapper = ini.SnackCase
|
|
||||||
|
|
||||||
if err := cfg.Append(localeContent); err != nil {
|
if html.UnescapeString(policy.Sanitize(keyValue)) == keyValue {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a nice diff of the difference.
|
||||||
|
diffs := dmp.DiffMain(keyValue, html.UnescapeString(policy.Sanitize(keyValue)), false)
|
||||||
|
diffs = dmp.DiffCleanupSemantic(diffs)
|
||||||
|
diffs = dmp.DiffCleanupEfficiency(diffs)
|
||||||
|
|
||||||
|
return []string{trKey + ": " + dmp.DiffPrettyText(diffs)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkLocaleContent(localeContent []byte) []string {
|
||||||
|
errors := []string{}
|
||||||
|
|
||||||
|
if err := localeiter.IterateMessagesContent(localeContent, func(trKey, trValue string) error {
|
||||||
|
errors = append(errors, checkValue(trKey, trValue)...)
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dmp := diffmatchpatch.New()
|
return errors
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkLocaleNextContent(localeContent []byte) []string {
|
||||||
errors := []string{}
|
errors := []string{}
|
||||||
|
|
||||||
for _, section := range cfg.Sections() {
|
if err := localeiter.IterateMessagesNextContent(localeContent, func(trKey, pluralForm, trValue string) error {
|
||||||
for _, key := range section.Keys() {
|
fullKey := trKey
|
||||||
var trKey string
|
if pluralForm != "" {
|
||||||
if section.Name() == "" || section.Name() == "DEFAULT" || section.Name() == "common" {
|
fullKey = trKey + "." + pluralForm
|
||||||
trKey = key.Name()
|
|
||||||
} else {
|
|
||||||
trKey = section.Name() + "." + key.Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
keyValue := preprocessTranslationValue(key.Value())
|
|
||||||
|
|
||||||
if html.UnescapeString(policy.Sanitize(keyValue)) != keyValue {
|
|
||||||
// Create a nice diff of the difference.
|
|
||||||
diffs := dmp.DiffMain(keyValue, html.UnescapeString(policy.Sanitize(keyValue)), false)
|
|
||||||
diffs = dmp.DiffCleanupSemantic(diffs)
|
|
||||||
diffs = dmp.DiffCleanupEfficiency(diffs)
|
|
||||||
|
|
||||||
errors = append(errors, trKey+": "+dmp.DiffPrettyText(diffs))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
errors = append(errors, checkValue(fullKey, trValue)...)
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,6 +137,7 @@ func main() {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Safety check that we are not reading the wrong directory.
|
||||||
if !slices.ContainsFunc(localeFiles, func(e fs.DirEntry) bool { return strings.HasSuffix(e.Name(), ".ini") }) {
|
if !slices.ContainsFunc(localeFiles, func(e fs.DirEntry) bool { return strings.HasSuffix(e.Name(), ".ini") }) {
|
||||||
fmt.Println("No locale files found")
|
fmt.Println("No locale files found")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -140,6 +151,7 @@ func main() {
|
||||||
|
|
||||||
localeContent, err := os.ReadFile(filepath.Join(localeDir, localeFile.Name()))
|
localeContent, err := os.ReadFile(filepath.Join(localeDir, localeFile.Name()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
fmt.Println(localeFile.Name())
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,5 +163,33 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check the locale next.
|
||||||
|
localeDir = filepath.Join("options", "locale_next")
|
||||||
|
localeFiles, err = os.ReadDir(localeDir)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safety check that we are not reading the wrong directory.
|
||||||
|
if !slices.ContainsFunc(localeFiles, func(e fs.DirEntry) bool { return strings.HasSuffix(e.Name(), ".json") }) {
|
||||||
|
fmt.Println("No locale_next files found")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, localeFile := range localeFiles {
|
||||||
|
localeContent, err := os.ReadFile(filepath.Join(localeDir, localeFile.Name()))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(localeFile.Name())
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkLocaleNextContent(localeContent); len(err) > 0 {
|
||||||
|
fmt.Println(localeFile.Name())
|
||||||
|
fmt.Println(strings.Join(err, "\n"))
|
||||||
|
fmt.Println()
|
||||||
|
exitCode = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
os.Exit(exitCode)
|
os.Exit(exitCode)
|
||||||
}
|
}
|
107
build/lint-locale/lint-locale_test.go
Normal file
107
build/lint-locale/lint-locale_test.go
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLocalizationPolicy(t *testing.T) {
|
||||||
|
initBlueMondayPolicy()
|
||||||
|
initRemoveTags()
|
||||||
|
|
||||||
|
t.Run("Remove tags", func(t *testing.T) {
|
||||||
|
assert.Empty(t, checkLocaleContent([]byte(`hidden_comment_types_description = Comment types checked here will not be shown inside issue pages. Checking "Label" for example removes all "<user> added/removed <label>" comments.`)))
|
||||||
|
|
||||||
|
assert.Equal(t, []string{"key: \x1b[31m<not-an-allowed-key>\x1b[0m REPLACED-TAG"}, checkLocaleContent([]byte(`key = "<not-an-allowed-key> <label>"`)))
|
||||||
|
assert.Equal(t, []string{"key: \x1b[31m<user@example.com>\x1b[0m REPLACED-TAG"}, checkLocaleContent([]byte(`key = "<user@example.com> <email@example.com>"`)))
|
||||||
|
assert.Equal(t, []string{"key: \x1b[31m<tag>\x1b[0m REPLACED-TAG \x1b[31m</tag>\x1b[0m"}, checkLocaleContent([]byte(`key = "<tag> <email@example.com> </tag>"`)))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Specific exception", func(t *testing.T) {
|
||||||
|
assert.Empty(t, checkLocaleContent([]byte(`workflow.dispatch.trigger_found = This workflow has a <c>workflow_dispatch</c> event trigger.`)))
|
||||||
|
assert.Empty(t, checkLocaleContent([]byte(`pulls.title_desc_one = wants to merge %[1]d commit from <code>%[2]s</code> into <code id="%[4]s">%[3]s</code>`)))
|
||||||
|
assert.Empty(t, checkLocaleContent([]byte(`editor.commit_directly_to_this_branch = Commit directly to the <strong class="%[2]s">%[1]s</strong> branch.`)))
|
||||||
|
|
||||||
|
assert.Equal(t, []string{"workflow.dispatch.trigger_found: This workflow has a \x1b[31m<d>\x1b[0mworkflow_dispatch\x1b[31m</d>\x1b[0m event trigger."}, checkLocaleContent([]byte(`workflow.dispatch.trigger_found = This workflow has a <d>workflow_dispatch</d> event trigger.`)))
|
||||||
|
assert.Equal(t, []string{"key: <code\x1b[31m id=\"branch_targe\"\x1b[0m>%[3]s</code>"}, checkLocaleContent([]byte(`key = <code id="branch_targe">%[3]s</code>`)))
|
||||||
|
assert.Equal(t, []string{"key: <a\x1b[31m class=\"ui sh\"\x1b[0m href=\"https://TO-BE-REPLACED.COM\">"}, checkLocaleContent([]byte(`key = <a class="ui sh" href="%[3]s">`)))
|
||||||
|
assert.Equal(t, []string{"key: <a\x1b[31m class=\"js-click-me\"\x1b[0m href=\"https://TO-BE-REPLACED.COM\">"}, checkLocaleContent([]byte(`key = <a class="js-click-me" href="%[3]s">`)))
|
||||||
|
assert.Equal(t, []string{"key: <strong\x1b[31m class=\"branch-target\"\x1b[0m>%[1]s</strong>"}, checkLocaleContent([]byte(`key = <strong class="branch-target">%[1]s</strong>`)))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("General safe tags", func(t *testing.T) {
|
||||||
|
assert.Empty(t, checkLocaleContent([]byte("error404 = The page you are trying to reach either <strong>does not exist</strong> or <strong>you are not authorized</strong> to view it.")))
|
||||||
|
assert.Empty(t, checkLocaleContent([]byte("teams.specific_repositories_helper = Members will only have access to repositories explicitly added to the team. Selecting this <strong>will not</strong> automatically remove repositories already added with <i>All repositories</i>.")))
|
||||||
|
assert.Empty(t, checkLocaleContent([]byte("sqlite_helper = File path for the SQLite3 database.<br>Enter an absolute path if you run Forgejo as a service.")))
|
||||||
|
assert.Empty(t, checkLocaleContent([]byte("hi_user_x = Hi <b>%s</b>,")))
|
||||||
|
assert.Empty(t, checkLocaleContent([]byte("key = Press <kbd>Shift</kbd>")))
|
||||||
|
|
||||||
|
assert.Equal(t, []string{"error404: The page you are trying to reach either <strong\x1b[31m title='aaa'\x1b[0m>does not exist</strong> or <strong>you are not authorized</strong> to view it."}, checkLocaleContent([]byte("error404 = The page you are trying to reach either <strong title='aaa'>does not exist</strong> or <strong>you are not authorized</strong> to view it.")))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("<a>", func(t *testing.T) {
|
||||||
|
assert.Empty(t, checkLocaleContent([]byte(`admin.new_user.text = Please <a href="%s">click here</a> to manage this user from the admin panel.`)))
|
||||||
|
assert.Empty(t, checkLocaleContent([]byte(`access_token_desc = Selected token permissions limit authorization only to the corresponding <a href="%[1]s" target="_blank">API</a> routes. Read the <a href="%[2]s" target="_blank">documentation</a> for more information.`)))
|
||||||
|
assert.Empty(t, checkLocaleContent([]byte(`webauthn_desc = Security keys are hardware devices containing cryptographic keys. They can be used for two-factor authentication. Security keys must support the <a rel="noreferrer" target="_blank" href="%s">WebAuthn Authenticator</a> standard.`)))
|
||||||
|
assert.Empty(t, checkLocaleContent([]byte("issues.closed_at = `closed this issue <a id=\"%[1]s\" href=\"#%[1]s\">%[2]s</a>`")))
|
||||||
|
|
||||||
|
assert.Equal(t, []string{"key: \x1b[31m<a href=\"https://example.com\">\x1b[0m"}, checkLocaleContent([]byte(`key = <a href="https://example.com">`)))
|
||||||
|
assert.Equal(t, []string{"key: \x1b[31m<a href=\"javascript:alert('1')\">\x1b[0m"}, checkLocaleContent([]byte(`key = <a href="javascript:alert('1')">`)))
|
||||||
|
assert.Equal(t, []string{"key: <a href=\"https://TO-BE-REPLACED.COM\"\x1b[31m download\x1b[0m>"}, checkLocaleContent([]byte(`key = <a href="%s" download>`)))
|
||||||
|
assert.Equal(t, []string{"key: <a href=\"https://TO-BE-REPLACED.COM\"\x1b[31m target=\"_self\"\x1b[0m>"}, checkLocaleContent([]byte(`key = <a href="%s" target="_self">`)))
|
||||||
|
assert.Equal(t, []string{"key: \x1b[31m<a href=\"https://example.com/%s\">\x1b[0m"}, checkLocaleContent([]byte(`key = <a href="https://example.com/%s">`)))
|
||||||
|
assert.Equal(t, []string{"key: \x1b[31m<a href=\"https://example.com/?q=%s\">\x1b[0m"}, checkLocaleContent([]byte(`key = <a href="https://example.com/?q=%s">`)))
|
||||||
|
assert.Equal(t, []string{"key: \x1b[31m<a href=\"%s/open-redirect\">\x1b[0m"}, checkLocaleContent([]byte(`key = <a href="%s/open-redirect">`)))
|
||||||
|
assert.Equal(t, []string{"key: \x1b[31m<a href=\"%s?q=open-redirect\">\x1b[0m"}, checkLocaleContent([]byte(`key = <a href="%s?q=open-redirect">`)))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Escaped HTML characters", func(t *testing.T) {
|
||||||
|
assert.Empty(t, checkLocaleContent([]byte("activity.git_stats_push_to_branch = `إلى %s و\"`")))
|
||||||
|
|
||||||
|
assert.Equal(t, []string{"key: و\x1b[31m \x1b[0m\x1b[32m\u00a0\x1b[0m"}, checkLocaleContent([]byte(`key = و `)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNextLocalizationPolicy(t *testing.T) {
|
||||||
|
initBlueMondayPolicy()
|
||||||
|
initRemoveTags()
|
||||||
|
|
||||||
|
t.Run("Nested locales", func(t *testing.T) {
|
||||||
|
assert.Empty(t, checkLocaleNextContent([]byte(`{
|
||||||
|
"settings": {
|
||||||
|
"hidden_comment_types_description": "Comment types checked here will not be shown inside issue pages. Checking \"Label\" for example removes all \"<user> added/removed <label>\" comments."
|
||||||
|
}
|
||||||
|
}`)))
|
||||||
|
|
||||||
|
assert.Equal(t, []string{"settings.hidden_comment_types_description: \"\x1b[31m<not-an-allowed-key>\x1b[0m REPLACED-TAG\""}, checkLocaleNextContent([]byte(`{
|
||||||
|
"settings": {
|
||||||
|
"hidden_comment_types_description": "\"<not-an-allowed-key> <label>\""
|
||||||
|
}
|
||||||
|
}`)))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Flat locales", func(t *testing.T) {
|
||||||
|
assert.Empty(t, checkLocaleNextContent([]byte(`{
|
||||||
|
"settings.hidden_comment_types_description": "Comment types checked here will not be shown inside issue pages. Checking \"Label\" for example removes all \"<user> added/removed <label>\" comments."
|
||||||
|
}`)))
|
||||||
|
|
||||||
|
assert.Equal(t, []string{"settings.hidden_comment_types_description: \"\x1b[31m<not-an-allowed-key>\x1b[0m REPLACED-TAG\""}, checkLocaleNextContent([]byte(`{
|
||||||
|
"settings.hidden_comment_types_description": "\"<not-an-allowed-key> <label>\""
|
||||||
|
}`)))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Plural form", func(t *testing.T) {
|
||||||
|
assert.Equal(t, []string{"repo.pulls.title_desc: key = \x1b[31m<a href=\"https://example.com\">\x1b[0m"}, checkLocaleNextContent([]byte(`{"repo.pulls.title_desc": {
|
||||||
|
"few": "key = <a href=\"%s\">",
|
||||||
|
"other": "key = <a href=\"https://example.com\">"
|
||||||
|
}}`)))
|
||||||
|
|
||||||
|
assert.Equal(t, []string{"repo.pulls.title_desc.few: key = \x1b[31m<a href=\"https://example.com\">\x1b[0m"}, checkLocaleNextContent([]byte(`{"repo.pulls.title_desc": {
|
||||||
|
"few": "key = <a href=\"https://example.com\">",
|
||||||
|
"other": "key = <a href=\"%s\">"
|
||||||
|
}}`)))
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,65 +0,0 @@
|
||||||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLocalizationPolicy(t *testing.T) {
|
|
||||||
initBlueMondayPolicy()
|
|
||||||
initRemoveTags()
|
|
||||||
|
|
||||||
t.Run("Remove tags", func(t *testing.T) {
|
|
||||||
assert.Empty(t, checkLocaleContent([]byte(`hidden_comment_types_description = Comment types checked here will not be shown inside issue pages. Checking "Label" for example removes all "<user> added/removed <label>" comments.`)))
|
|
||||||
|
|
||||||
assert.EqualValues(t, []string{"key: \x1b[31m<not-an-allowed-key>\x1b[0m REPLACED-TAG"}, checkLocaleContent([]byte(`key = "<not-an-allowed-key> <label>"`)))
|
|
||||||
assert.EqualValues(t, []string{"key: \x1b[31m<user@example.com>\x1b[0m REPLACED-TAG"}, checkLocaleContent([]byte(`key = "<user@example.com> <email@example.com>"`)))
|
|
||||||
assert.EqualValues(t, []string{"key: \x1b[31m<tag>\x1b[0m REPLACED-TAG \x1b[31m</tag>\x1b[0m"}, checkLocaleContent([]byte(`key = "<tag> <email@example.com> </tag>"`)))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Specific exception", func(t *testing.T) {
|
|
||||||
assert.Empty(t, checkLocaleContent([]byte(`workflow.dispatch.trigger_found = This workflow has a <c>workflow_dispatch</c> event trigger.`)))
|
|
||||||
assert.Empty(t, checkLocaleContent([]byte(`pulls.title_desc_one = wants to merge %[1]d commit from <code>%[2]s</code> into <code id="%[4]s">%[3]s</code>`)))
|
|
||||||
assert.Empty(t, checkLocaleContent([]byte(`editor.commit_directly_to_this_branch = Commit directly to the <strong class="%[2]s">%[1]s</strong> branch.`)))
|
|
||||||
|
|
||||||
assert.EqualValues(t, []string{"workflow.dispatch.trigger_found: This workflow has a \x1b[31m<d>\x1b[0mworkflow_dispatch\x1b[31m</d>\x1b[0m event trigger."}, checkLocaleContent([]byte(`workflow.dispatch.trigger_found = This workflow has a <d>workflow_dispatch</d> event trigger.`)))
|
|
||||||
assert.EqualValues(t, []string{"key: <code\x1b[31m id=\"branch_targe\"\x1b[0m>%[3]s</code>"}, checkLocaleContent([]byte(`key = <code id="branch_targe">%[3]s</code>`)))
|
|
||||||
assert.EqualValues(t, []string{"key: <a\x1b[31m class=\"ui sh\"\x1b[0m href=\"https://TO-BE-REPLACED.COM\">"}, checkLocaleContent([]byte(`key = <a class="ui sh" href="%[3]s">`)))
|
|
||||||
assert.EqualValues(t, []string{"key: <a\x1b[31m class=\"js-click-me\"\x1b[0m href=\"https://TO-BE-REPLACED.COM\">"}, checkLocaleContent([]byte(`key = <a class="js-click-me" href="%[3]s">`)))
|
|
||||||
assert.EqualValues(t, []string{"key: <strong\x1b[31m class=\"branch-target\"\x1b[0m>%[1]s</strong>"}, checkLocaleContent([]byte(`key = <strong class="branch-target">%[1]s</strong>`)))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("General safe tags", func(t *testing.T) {
|
|
||||||
assert.Empty(t, checkLocaleContent([]byte("error404 = The page you are trying to reach either <strong>does not exist</strong> or <strong>you are not authorized</strong> to view it.")))
|
|
||||||
assert.Empty(t, checkLocaleContent([]byte("teams.specific_repositories_helper = Members will only have access to repositories explicitly added to the team. Selecting this <strong>will not</strong> automatically remove repositories already added with <i>All repositories</i>.")))
|
|
||||||
assert.Empty(t, checkLocaleContent([]byte("sqlite_helper = File path for the SQLite3 database.<br>Enter an absolute path if you run Forgejo as a service.")))
|
|
||||||
assert.Empty(t, checkLocaleContent([]byte("hi_user_x = Hi <b>%s</b>,")))
|
|
||||||
|
|
||||||
assert.EqualValues(t, []string{"error404: The page you are trying to reach either <strong\x1b[31m title='aaa'\x1b[0m>does not exist</strong> or <strong>you are not authorized</strong> to view it."}, checkLocaleContent([]byte("error404 = The page you are trying to reach either <strong title='aaa'>does not exist</strong> or <strong>you are not authorized</strong> to view it.")))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("<a>", func(t *testing.T) {
|
|
||||||
assert.Empty(t, checkLocaleContent([]byte(`admin.new_user.text = Please <a href="%s">click here</a> to manage this user from the admin panel.`)))
|
|
||||||
assert.Empty(t, checkLocaleContent([]byte(`access_token_desc = Selected token permissions limit authorization only to the corresponding <a href="%[1]s" target="_blank">API</a> routes. Read the <a href="%[2]s" target="_blank">documentation</a> for more information.`)))
|
|
||||||
assert.Empty(t, checkLocaleContent([]byte(`webauthn_desc = Security keys are hardware devices containing cryptographic keys. They can be used for two-factor authentication. Security keys must support the <a rel="noreferrer" target="_blank" href="%s">WebAuthn Authenticator</a> standard.`)))
|
|
||||||
assert.Empty(t, checkLocaleContent([]byte("issues.closed_at = `closed this issue <a id=\"%[1]s\" href=\"#%[1]s\">%[2]s</a>`")))
|
|
||||||
|
|
||||||
assert.EqualValues(t, []string{"key: \x1b[31m<a href=\"https://example.com\">\x1b[0m"}, checkLocaleContent([]byte(`key = <a href="https://example.com">`)))
|
|
||||||
assert.EqualValues(t, []string{"key: \x1b[31m<a href=\"javascript:alert('1')\">\x1b[0m"}, checkLocaleContent([]byte(`key = <a href="javascript:alert('1')">`)))
|
|
||||||
assert.EqualValues(t, []string{"key: <a href=\"https://TO-BE-REPLACED.COM\"\x1b[31m download\x1b[0m>"}, checkLocaleContent([]byte(`key = <a href="%s" download>`)))
|
|
||||||
assert.EqualValues(t, []string{"key: <a href=\"https://TO-BE-REPLACED.COM\"\x1b[31m target=\"_self\"\x1b[0m>"}, checkLocaleContent([]byte(`key = <a href="%s" target="_self">`)))
|
|
||||||
assert.EqualValues(t, []string{"key: \x1b[31m<a href=\"https://example.com/%s\">\x1b[0m"}, checkLocaleContent([]byte(`key = <a href="https://example.com/%s">`)))
|
|
||||||
assert.EqualValues(t, []string{"key: \x1b[31m<a href=\"https://example.com/?q=%s\">\x1b[0m"}, checkLocaleContent([]byte(`key = <a href="https://example.com/?q=%s">`)))
|
|
||||||
assert.EqualValues(t, []string{"key: \x1b[31m<a href=\"%s/open-redirect\">\x1b[0m"}, checkLocaleContent([]byte(`key = <a href="%s/open-redirect">`)))
|
|
||||||
assert.EqualValues(t, []string{"key: \x1b[31m<a href=\"%s?q=open-redirect\">\x1b[0m"}, checkLocaleContent([]byte(`key = <a href="%s?q=open-redirect">`)))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Escaped HTML characters", func(t *testing.T) {
|
|
||||||
assert.Empty(t, checkLocaleContent([]byte("activity.git_stats_push_to_branch = `إلى %s و\"`")))
|
|
||||||
|
|
||||||
assert.EqualValues(t, []string{"key: و\x1b[31m \x1b[0m\x1b[32m\u00a0\x1b[0m"}, checkLocaleContent([]byte(`key = و `)))
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# this script runs in alpine image which only has `sh` shell
|
|
||||||
|
|
||||||
set +e
|
|
||||||
if sed --version 2>/dev/null | grep -q GNU; then
|
|
||||||
SED_INPLACE="sed -i"
|
|
||||||
else
|
|
||||||
SED_INPLACE="sed -i ''"
|
|
||||||
fi
|
|
||||||
set -e
|
|
||||||
|
|
||||||
if [ ! -f ./options/locale/locale_en-US.ini ]; then
|
|
||||||
echo "please run this script in the root directory of the project"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
mv ./options/locale/locale_en-US.ini ./options/
|
|
||||||
|
|
||||||
# the "ini" library for locale has many quirks, its behavior is different from Crowdin.
|
|
||||||
# see i18n_test.go for more details
|
|
||||||
|
|
||||||
# this script helps to unquote the Crowdin outputs for the quirky ini library
|
|
||||||
# * find all `key="...\"..."` lines
|
|
||||||
# * remove the leading quote
|
|
||||||
# * remove the trailing quote
|
|
||||||
# * unescape the quotes
|
|
||||||
# * eg: key="...\"..." => key=..."...
|
|
||||||
$SED_INPLACE -r -e '/^[-.A-Za-z0-9_]+[ ]*=[ ]*".*"$/ {
|
|
||||||
s/^([-.A-Za-z0-9_]+)[ ]*=[ ]*"/\1=/
|
|
||||||
s/"$//
|
|
||||||
s/\\"/"/g
|
|
||||||
}' ./options/locale/*.ini
|
|
||||||
|
|
||||||
# * if the escaped line is incomplete like `key="...` or `key=..."`, quote it with backticks
|
|
||||||
# * eg: key="... => key=`"...`
|
|
||||||
# * eg: key=..." => key=`..."`
|
|
||||||
$SED_INPLACE -r -e 's/^([-.A-Za-z0-9_]+)[ ]*=[ ]*(".*[^"])$/\1=`\2`/' ./options/locale/*.ini
|
|
||||||
$SED_INPLACE -r -e 's/^([-.A-Za-z0-9_]+)[ ]*=[ ]*([^"].*")$/\1=`\2`/' ./options/locale/*.ini
|
|
||||||
|
|
||||||
# Remove translation under 25% of en_us
|
|
||||||
baselines=$(wc -l "./options/locale_en-US.ini" | cut -d" " -f1)
|
|
||||||
baselines=$((baselines / 4))
|
|
||||||
for filename in ./options/locale/*.ini; do
|
|
||||||
lines=$(wc -l "$filename" | cut -d" " -f1)
|
|
||||||
if [ $lines -lt $baselines ]; then
|
|
||||||
echo "Removing $filename: $lines/$baselines"
|
|
||||||
rm "$filename"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
mv ./options/locale_en-US.ini ./options/locale/
|
|
|
@ -4,25 +4,28 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/private"
|
"forgejo.org/modules/private"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// CmdActions represents the available actions sub-commands.
|
||||||
// CmdActions represents the available actions sub-commands.
|
func cmdActions() *cli.Command {
|
||||||
CmdActions = &cli.Command{
|
return &cli.Command{
|
||||||
Name: "actions",
|
Name: "actions",
|
||||||
Usage: "Manage Forgejo Actions",
|
Usage: "Manage Forgejo Actions",
|
||||||
Subcommands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
subcmdActionsGenRunnerToken,
|
subcmdActionsGenRunnerToken(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
subcmdActionsGenRunnerToken = &cli.Command{
|
func subcmdActionsGenRunnerToken() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "generate-runner-token",
|
Name: "generate-runner-token",
|
||||||
Usage: "Generate a new token for a runner to use to register with the server",
|
Usage: "Generate a new token for a runner to use to register with the server",
|
||||||
Action: runGenerateActionsRunnerToken,
|
Action: runGenerateActionsRunnerToken,
|
||||||
|
@ -36,10 +39,10 @@ var (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
func runGenerateActionsRunnerToken(c *cli.Context) error {
|
func runGenerateActionsRunnerToken(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
setting.MustInstalled()
|
setting.MustInstalled()
|
||||||
|
|
82
cmd/admin.go
82
cmd/admin.go
|
@ -8,63 +8,71 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"forgejo.org/models/db"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "forgejo.org/models/repo"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"forgejo.org/modules/git"
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"forgejo.org/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"forgejo.org/modules/log"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
repo_module "forgejo.org/modules/repository"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// CmdAdmin represents the available admin sub-command.
|
||||||
// CmdAdmin represents the available admin sub-command.
|
func cmdAdmin() *cli.Command {
|
||||||
CmdAdmin = &cli.Command{
|
return &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(),
|
||||||
subcmdAuth,
|
subcmdAuth(),
|
||||||
subcmdSendMail,
|
subcmdSendMail(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
subcmdRepoSyncReleases = &cli.Command{
|
func subcmdRepoSyncReleases() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "repo-sync-releases",
|
Name: "repo-sync-releases",
|
||||||
Usage: "Synchronize repository releases with tags",
|
Usage: "Synchronize repository releases with tags",
|
||||||
Action: runRepoSyncReleases,
|
Action: runRepoSyncReleases,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
subcmdRegenerate = &cli.Command{
|
func subcmdRegenerate() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "regenerate",
|
Name: "regenerate",
|
||||||
Usage: "Regenerate specific files",
|
Usage: "Regenerate specific files",
|
||||||
Subcommands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
microcmdRegenHooks,
|
microcmdRegenHooks,
|
||||||
microcmdRegenKeys,
|
microcmdRegenKeys,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
subcmdAuth = &cli.Command{
|
func subcmdAuth() *cli.Command {
|
||||||
|
return &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(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
subcmdSendMail = &cli.Command{
|
func subcmdSendMail() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "sendmail",
|
Name: "sendmail",
|
||||||
Usage: "Send a message to all users",
|
Usage: "Send a message to all users",
|
||||||
Action: runSendMail,
|
Action: runSendMail,
|
||||||
|
@ -86,15 +94,17 @@ 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 {
|
func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
if err := initDB(ctx); err != nil {
|
||||||
|
|
|
@ -4,26 +4,30 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "forgejo.org/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"forgejo.org/models/db"
|
||||||
auth_service "code.gitea.io/gitea/services/auth"
|
auth_service "forgejo.org/services/auth"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func microcmdAuthDelete() *cli.Command {
|
||||||
microcmdAuthDelete = &cli.Command{
|
return &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{
|
}
|
||||||
|
|
||||||
|
func microcmdAuthList() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Usage: "List auth sources",
|
Usage: "List auth sources",
|
||||||
Action: runListAuth,
|
Action: runListAuth,
|
||||||
|
@ -54,10 +58,10 @@ var (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
func runListAuth(c *cli.Context) error {
|
func runListAuth(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
if err := initDB(ctx); err != nil {
|
||||||
|
@ -81,7 +85,7 @@ func runListAuth(c *cli.Context) error {
|
||||||
|
|
||||||
// loop through each source and print
|
// loop through each source and print
|
||||||
w := tabwriter.NewWriter(os.Stdout, c.Int("min-width"), c.Int("tab-width"), c.Int("padding"), padChar, flags)
|
w := tabwriter.NewWriter(os.Stdout, c.Int("min-width"), c.Int("tab-width"), c.Int("padding"), padChar, flags)
|
||||||
fmt.Fprintf(w, "ID\tName\tType\tEnabled\n")
|
fmt.Fprint(w, "ID\tName\tType\tEnabled\n")
|
||||||
for _, source := range authSources {
|
for _, source := range authSources {
|
||||||
fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", source.ID, source.Name, source.Type.String(), source.IsActive)
|
fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", source.ID, source.Name, source.Type.String(), source.IsActive)
|
||||||
}
|
}
|
||||||
|
@ -90,12 +94,12 @@ 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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
if err := initDB(ctx); err != nil {
|
||||||
|
|
|
@ -8,10 +8,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/auth"
|
"forgejo.org/models/auth"
|
||||||
"code.gitea.io/gitea/services/auth/source/ldap"
|
"forgejo.org/services/auth/source/ldap"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -23,8 +23,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 +102,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.",
|
||||||
|
@ -128,49 +130,59 @@ var (
|
||||||
Name: "page-size",
|
Name: "page-size",
|
||||||
Usage: "Search page size.",
|
Usage: "Search page size.",
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
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, cli *cli.Command) error {
|
||||||
return newAuthService().addLdapBindDn(c)
|
return newAuthService().addLdapBindDn(ctx, cli)
|
||||||
},
|
},
|
||||||
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, cli *cli.Command) error {
|
||||||
return newAuthService().updateLdapBindDn(c)
|
return newAuthService().updateLdapBindDn(ctx, cli)
|
||||||
},
|
},
|
||||||
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, cli *cli.Command) error {
|
||||||
return newAuthService().addLdapSimpleAuth(c)
|
return newAuthService().addLdapSimpleAuth(ctx, cli)
|
||||||
},
|
},
|
||||||
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, cli *cli.Command) error {
|
||||||
return newAuthService().updateLdapSimpleAuth(c)
|
return newAuthService().updateLdapSimpleAuth(ctx, cli)
|
||||||
},
|
},
|
||||||
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 {
|
||||||
|
@ -183,7 +195,7 @@ func newAuthService() *authService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseAuthSource assigns values on authSource according to command line flags.
|
// parseAuthSource assigns values on authSource according to command line flags.
|
||||||
func parseAuthSource(c *cli.Context, authSource *auth.Source) {
|
func parseAuthSource(c *cli.Command, authSource *auth.Source) {
|
||||||
if c.IsSet("name") {
|
if c.IsSet("name") {
|
||||||
authSource.Name = c.String("name")
|
authSource.Name = c.String("name")
|
||||||
}
|
}
|
||||||
|
@ -202,7 +214,7 @@ func parseAuthSource(c *cli.Context, authSource *auth.Source) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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")
|
||||||
}
|
}
|
||||||
|
@ -289,7 +301,7 @@ 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
|
||||||
}
|
}
|
||||||
|
@ -307,12 +319,12 @@ func (a *authService) getAuthSource(ctx context.Context, c *cli.Context, authTyp
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := a.initDB(ctx); err != nil {
|
if err := a.initDB(ctx); err != nil {
|
||||||
|
@ -336,8 +348,8 @@ 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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := a.initDB(ctx); err != nil {
|
if err := a.initDB(ctx); err != nil {
|
||||||
|
@ -358,12 +370,12 @@ 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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := a.initDB(ctx); err != nil {
|
if err := a.initDB(ctx); err != nil {
|
||||||
|
@ -387,8 +399,8 @@ 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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := a.initDB(ctx); err != nil {
|
if err := a.initDB(ctx); err != nil {
|
||||||
|
|
|
@ -7,19 +7,18 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/auth"
|
"forgejo.org/models/auth"
|
||||||
"code.gitea.io/gitea/services/auth/source/ldap"
|
"forgejo.org/modules/test"
|
||||||
|
"forgejo.org/services/auth/source/ldap"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"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 {
|
||||||
|
@ -216,22 +215,22 @@ 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, "should not call updateAuthSource", "case: %d", 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, "should not call getAuthSourceByID", "case: %d", 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
|
app.Flags = microcmdAuthAddLdapBindDn().Flags
|
||||||
app.Action = service.addLdapBindDn
|
app.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 {
|
||||||
|
@ -243,9 +242,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 {
|
||||||
|
@ -447,22 +444,22 @@ 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, "should not call updateAuthSource", "case: %d", 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, "should not call getAuthSourceByID", "case: %d", 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
|
app.Flags = microcmdAuthAddLdapSimpleAuth().Flags
|
||||||
app.Action = service.addLdapSimpleAuth
|
app.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 {
|
||||||
|
@ -474,9 +471,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 {
|
||||||
|
@ -898,7 +893,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, "should not call createAuthSource", "case: %d", n)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
||||||
|
@ -920,12 +915,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
|
app.Flags = microcmdAuthUpdateLdapBindDn().Flags
|
||||||
app.Action = service.updateLdapBindDn
|
app.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 {
|
||||||
|
@ -937,9 +932,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 {
|
||||||
|
@ -1288,7 +1281,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, "should not call createAuthSource", "case: %d", n)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
||||||
|
@ -1310,12 +1303,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
|
app.Flags = microcmdAuthUpdateLdapSimpleAuth().Flags
|
||||||
app.Action = service.updateLdapSimpleAuth
|
app.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,19 @@
|
||||||
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 "forgejo.org/models/auth"
|
||||||
"code.gitea.io/gitea/services/auth/source/oauth2"
|
"forgejo.org/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: "",
|
||||||
|
@ -120,23 +121,27 @@ 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 {
|
||||||
|
return &cli.Command{
|
||||||
Name: "add-oauth",
|
Name: "add-oauth",
|
||||||
Usage: "Add new Oauth authentication source",
|
Usage: "Add new Oauth authentication source",
|
||||||
Action: runAddOauth,
|
Action: runAddOauth,
|
||||||
Flags: oauthCLIFlags,
|
Flags: oauthCLIFlags(),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
microcmdAuthUpdateOauth = &cli.Command{
|
func microcmdAuthUpdateOauth() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "update-oauth",
|
Name: "update-oauth",
|
||||||
Usage: "Update existing Oauth authentication source",
|
Usage: "Update existing Oauth authentication source",
|
||||||
Action: runUpdateOauth,
|
Action: runUpdateOauth,
|
||||||
Flags: append(oauthCLIFlags[:1], append([]cli.Flag{idFlag}, oauthCLIFlags[1:]...)...),
|
Flags: append(oauthCLIFlags()[:1], append([]cli.Flag{idFlag()}, oauthCLIFlags()[1:]...)...),
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
func parseOAuth2Config(c *cli.Context) *oauth2.Source {
|
func parseOAuth2Config(_ context.Context, 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{
|
||||||
|
@ -168,15 +173,15 @@ func parseOAuth2Config(c *cli.Context) *oauth2.Source {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runAddOauth(c *cli.Context) error {
|
func runAddOauth(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
if err := initDB(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
config := parseOAuth2Config(c)
|
config := parseOAuth2Config(ctx, c)
|
||||||
if config.Provider == "openidConnect" {
|
if config.Provider == "openidConnect" {
|
||||||
discoveryURL, err := url.Parse(config.OpenIDConnectAutoDiscoveryURL)
|
discoveryURL, err := url.Parse(config.OpenIDConnectAutoDiscoveryURL)
|
||||||
if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") {
|
if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") {
|
||||||
|
@ -192,12 +197,12 @@ func runAddOauth(c *cli.Context) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func runUpdateOauth(c *cli.Context) error {
|
func 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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
if err := initDB(ctx); err != nil {
|
||||||
|
|
|
@ -4,18 +4,19 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "forgejo.org/models/auth"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"forgejo.org/modules/util"
|
||||||
"code.gitea.io/gitea/services/auth/source/smtp"
|
"forgejo.org/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: "",
|
||||||
|
@ -71,23 +72,27 @@ var (
|
||||||
Value: true,
|
Value: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
microcmdAuthAddSMTP = &cli.Command{
|
func microcmdAuthAddSMTP() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "add-smtp",
|
Name: "add-smtp",
|
||||||
Usage: "Add new SMTP authentication source",
|
Usage: "Add new SMTP authentication source",
|
||||||
Action: runAddSMTP,
|
Action: runAddSMTP,
|
||||||
Flags: smtpCLIFlags,
|
Flags: smtpCLIFlags(),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
microcmdAuthUpdateSMTP = &cli.Command{
|
func microcmdAuthUpdateSMTP() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "update-smtp",
|
Name: "update-smtp",
|
||||||
Usage: "Update existing SMTP authentication source",
|
Usage: "Update existing SMTP authentication source",
|
||||||
Action: runUpdateSMTP,
|
Action: runUpdateSMTP,
|
||||||
Flags: append(smtpCLIFlags[:1], append([]cli.Flag{idFlag}, smtpCLIFlags[1:]...)...),
|
Flags: append(smtpCLIFlags()[:1], append([]cli.Flag{idFlag()}, smtpCLIFlags()[1:]...)...),
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
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"}
|
||||||
|
@ -123,8 +128,8 @@ func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runAddSMTP(c *cli.Context) error {
|
func runAddSMTP(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
if err := initDB(ctx); err != nil {
|
||||||
|
@ -163,12 +168,12 @@ func runAddSMTP(c *cli.Context) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func runUpdateSMTP(c *cli.Context) error {
|
func 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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
if err := initDB(ctx); err != nil {
|
||||||
|
|
|
@ -4,11 +4,13 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
"context"
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
|
||||||
repo_service "code.gitea.io/gitea/services/repository"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
asymkey_model "forgejo.org/models/asymkey"
|
||||||
|
"forgejo.org/modules/graceful"
|
||||||
|
repo_service "forgejo.org/services/repository"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -25,8 +27,8 @@ var (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func runRegenerateHooks(_ *cli.Context) error {
|
func runRegenerateHooks(ctx context.Context, _ *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
if err := initDB(ctx); err != nil {
|
||||||
|
@ -35,8 +37,8 @@ func runRegenerateHooks(_ *cli.Context) error {
|
||||||
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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
if err := initDB(ctx); err != nil {
|
||||||
|
|
|
@ -4,18 +4,21 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var subcmdUser = &cli.Command{
|
func subcmdUser() *cli.Command {
|
||||||
Name: "user",
|
return &cli.Command{
|
||||||
Usage: "Modify users",
|
Name: "user",
|
||||||
Subcommands: []*cli.Command{
|
Usage: "Modify users",
|
||||||
microcmdUserCreate,
|
Commands: []*cli.Command{
|
||||||
microcmdUserList,
|
microcmdUserCreate(),
|
||||||
microcmdUserChangePassword,
|
microcmdUserList(),
|
||||||
microcmdUserDelete,
|
microcmdUserChangePassword(),
|
||||||
microcmdUserGenerateAccessToken,
|
microcmdUserDelete(),
|
||||||
microcmdUserMustChangePassword,
|
microcmdUserGenerateAccessToken(),
|
||||||
},
|
microcmdUserMustChangePassword(),
|
||||||
|
microcmdUserResetMFA(),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,49 +4,52 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "forgejo.org/models/user"
|
||||||
"code.gitea.io/gitea/modules/auth/password"
|
"forgejo.org/modules/auth/password"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"forgejo.org/modules/optional"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
user_service "code.gitea.io/gitea/services/user"
|
user_service "forgejo.org/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",
|
Value: "",
|
||||||
|
Usage: "The user to change password for",
|
||||||
|
},
|
||||||
|
&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",
|
||||||
|
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",
|
|
||||||
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 err := argsSet(c, "username", "password"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := installSignals()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
if err := initDB(ctx); err != nil {
|
||||||
|
|
|
@ -4,71 +4,92 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "forgejo.org/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"forgejo.org/models/db"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "forgejo.org/models/user"
|
||||||
pwd "code.gitea.io/gitea/modules/auth/password"
|
pwd "forgejo.org/modules/auth/password"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"forgejo.org/modules/optional"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/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{
|
Flags: []cli.Flag{
|
||||||
Name: "name",
|
&cli.StringFlag{
|
||||||
Usage: "Username. DEPRECATED: use username instead",
|
Name: "name",
|
||||||
|
Usage: "Username. DEPRECATED: use username instead",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "username",
|
||||||
|
Usage: "Username",
|
||||||
|
},
|
||||||
|
&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: "Set this option to false to prevent forcing the user to change their password after initial login",
|
||||||
|
Value: 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: "username",
|
|
||||||
Usage: "Username",
|
|
||||||
},
|
|
||||||
&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: "Set this option to false to prevent forcing the user to change their password after initial login",
|
|
||||||
Value: true,
|
|
||||||
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 {
|
||||||
|
// this command highly depends on the many setting options (create org, visibility, etc.), so it must have a full setting load first
|
||||||
|
// duplicate setting loading should be safe at the moment, but it should be refactored & improved in the future.
|
||||||
|
setting.LoadSettings()
|
||||||
|
|
||||||
if err := argsSet(c, "email"); err != nil {
|
if err := argsSet(c, "email"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -89,10 +110,10 @@ 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.Fprint(c.Root().ErrWriter, "--name flag is deprecated. Use --username instead.\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := installSignals()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
if err := initDB(ctx); err != nil {
|
||||||
|
@ -146,6 +167,7 @@ func runCreateUser(c *cli.Context) error {
|
||||||
IsAdmin: isAdmin,
|
IsAdmin: isAdmin,
|
||||||
MustChangePassword: mustChangePassword,
|
MustChangePassword: mustChangePassword,
|
||||||
Visibility: visibility,
|
Visibility: visibility,
|
||||||
|
FullName: c.String("fullname"),
|
||||||
}
|
}
|
||||||
|
|
||||||
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
||||||
|
@ -153,23 +175,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, overwriteDefault); err != nil {
|
if err := user_model.CreateUser(ctx, u, 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
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,49 +4,52 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "forgejo.org/models/user"
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"forgejo.org/modules/storage"
|
||||||
user_service "code.gitea.io/gitea/services/user"
|
user_service "forgejo.org/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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
if err := initDB(ctx); err != nil {
|
||||||
|
|
|
@ -4,49 +4,52 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "forgejo.org/models/auth"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "forgejo.org/models/user"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var microcmdUserGenerateAccessToken = &cli.Command{
|
func microcmdUserGenerateAccessToken() *cli.Command {
|
||||||
Name: "generate-access-token",
|
return &cli.Command{
|
||||||
Usage: "Generate an access token for a specific user",
|
Name: "generate-access-token",
|
||||||
Flags: []cli.Flag{
|
Usage: "Generate an access token for a specific user",
|
||||||
&cli.StringFlag{
|
Flags: []cli.Flag{
|
||||||
Name: "username",
|
&cli.StringFlag{
|
||||||
Aliases: []string{"u"},
|
Name: "username",
|
||||||
Usage: "Username",
|
Aliases: []string{"u"},
|
||||||
|
Usage: "Username",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "token-name",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Usage: "Token name",
|
||||||
|
Value: "gitea-admin",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "raw",
|
||||||
|
Usage: "Display only the token value",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "scopes",
|
||||||
|
Value: "all",
|
||||||
|
Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
Action: runGenerateAccessToken,
|
||||||
Name: "token-name",
|
}
|
||||||
Aliases: []string{"t"},
|
|
||||||
Usage: "Token name",
|
|
||||||
Value: "gitea-admin",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "raw",
|
|
||||||
Usage: "Display only the token value",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "scopes",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Comma separated list of scopes to apply to access token",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
if err := initDB(ctx); err != nil {
|
||||||
|
@ -77,6 +80,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,29 +4,32 @@
|
||||||
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 "forgejo.org/models/user"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var microcmdUserList = &cli.Command{
|
func microcmdUserList() *cli.Command {
|
||||||
Name: "list",
|
return &cli.Command{
|
||||||
Usage: "List users",
|
Name: "list",
|
||||||
Action: runListUsers,
|
Usage: "List users",
|
||||||
Flags: []cli.Flag{
|
Action: runListUsers,
|
||||||
&cli.BoolFlag{
|
Flags: []cli.Flag{
|
||||||
Name: "admin",
|
&cli.BoolFlag{
|
||||||
Usage: "List only admin users",
|
Name: "admin",
|
||||||
|
Usage: "List only admin users",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runListUsers(c *cli.Context) error {
|
func runListUsers(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
if err := initDB(ctx); err != nil {
|
||||||
|
@ -41,7 +44,7 @@ func runListUsers(c *cli.Context) error {
|
||||||
w := tabwriter.NewWriter(os.Stdout, 5, 0, 1, ' ', 0)
|
w := tabwriter.NewWriter(os.Stdout, 5, 0, 1, ' ', 0)
|
||||||
|
|
||||||
if c.IsSet("admin") {
|
if c.IsSet("admin") {
|
||||||
fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\n")
|
fmt.Fprint(w, "ID\tUsername\tEmail\tIsActive\n")
|
||||||
for _, u := range users {
|
for _, u := range users {
|
||||||
if u.IsAdmin {
|
if u.IsAdmin {
|
||||||
fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", u.ID, u.Name, u.Email, u.IsActive)
|
fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", u.ID, u.Name, u.Email, u.IsActive)
|
||||||
|
@ -49,7 +52,7 @@ func runListUsers(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
twofa := user_model.UserList(users).GetTwoFaStatus(ctx)
|
twofa := user_model.UserList(users).GetTwoFaStatus(ctx)
|
||||||
fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\tIsAdmin\t2FA\n")
|
fmt.Fprint(w, "ID\tUsername\tEmail\tIsActive\tIsAdmin\t2FA\n")
|
||||||
for _, u := range users {
|
for _, u := range users {
|
||||||
fmt.Fprintf(w, "%d\t%s\t%s\t%t\t%t\t%t\n", u.ID, u.Name, u.Email, u.IsActive, u.IsAdmin, twofa[u.ID])
|
fmt.Fprintf(w, "%d\t%s\t%s\t%t\t%t\t%t\n", u.ID, u.Name, u.Email, u.IsActive, u.IsAdmin, twofa[u.ID])
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,38 +4,41 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "forgejo.org/models/user"
|
||||||
|
|
||||||
"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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if c.NArg() == 0 && !c.IsSet("all") {
|
if c.NArg() == 0 && !c.IsSet("all") {
|
||||||
|
|
73
cmd/admin_user_reset_mfa.go
Normal file
73
cmd/admin_user_reset_mfa.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
auth_model "forgejo.org/models/auth"
|
||||||
|
user_model "forgejo.org/models/user"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func microcmdUserResetMFA() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "reset-mfa",
|
||||||
|
Usage: "Remove all two-factor authentication configurations for a user",
|
||||||
|
Action: runResetMFA,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "username",
|
||||||
|
Aliases: []string{"u"},
|
||||||
|
Value: "",
|
||||||
|
Usage: "The user to update",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runResetMFA(ctx context.Context, c *cli.Command) error {
|
||||||
|
if err := argsSet(c, "username"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := installSignals(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := initDB(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := user_model.GetUserByName(ctx, c.String("username"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
webAuthnList, err := auth_model.GetWebAuthnCredentialsByUID(ctx, user.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, credential := range webAuthnList {
|
||||||
|
if _, err := auth_model.DeleteCredential(ctx, credential.ID, user.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tfaModes, err := auth_model.GetTwoFactorByUID(ctx, user.ID)
|
||||||
|
if err == nil && tfaModes != nil {
|
||||||
|
if err := auth_model.DeleteTwoFactorByID(ctx, tfaModes.ID, user.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, is := err.(auth_model.ErrTwoFactorNotEnrolled); !is {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s's two-factor authentication settings have been removed!\n", user.Name)
|
||||||
|
return nil
|
||||||
|
}
|
77
cmd/cert.go
77
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"
|
||||||
|
@ -20,47 +21,49 @@ 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: "",
|
Value: "",
|
||||||
Usage: "Comma-separated hostnames and IPs to generate a certificate for",
|
Usage: "Comma-separated hostnames and IPs to generate a certificate for",
|
||||||
|
},
|
||||||
|
&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: "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,7 +92,7 @@ func pemBlockForKey(priv any) *pem.Block {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCert(c *cli.Context) error {
|
func runCert(ctx context.Context, c *cli.Command) error {
|
||||||
if err := argsSet(c, "host"); err != nil {
|
if err := argsSet(c, "host"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
34
cmd/cmd.go
34
cmd/cmd.go
|
@ -15,24 +15,26 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"forgejo.org/models/db"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"forgejo.org/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"forgejo.org/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 s, ok := c.Value(a).(string); ok {
|
||||||
return errors.New(a + " is required")
|
if util.IsEmptyString(s) {
|
||||||
|
return errors.New(a + " is required")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -73,8 +75,8 @@ If this is the intended configuration file complete the [database] section.`, se
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func installSignals() (context.Context, context.CancelFunc) {
|
func installSignals(ctx context.Context) (context.Context, context.CancelFunc) {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
go func() {
|
go func() {
|
||||||
// install notify
|
// install notify
|
||||||
signalChannel := make(chan os.Signal, 1)
|
signalChannel := make(chan os.Signal, 1)
|
||||||
|
@ -109,7 +111,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,16 +122,16 @@ 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(ctx context.Context, cli *cli.Command) (context.Context, error) {
|
||||||
return func(c *cli.Context) error {
|
return func(ctx context.Context, cli *cli.Command) (context.Context, error) {
|
||||||
level := defaultLevel
|
level := defaultLevel
|
||||||
if globalBool(c, "quiet") {
|
if globalBool(cli, "quiet") {
|
||||||
level = log.FATAL
|
level = log.FATAL
|
||||||
}
|
}
|
||||||
if globalBool(c, "debug") || globalBool(c, "verbose") {
|
if globalBool(cli, "debug") || globalBool(cli, "verbose") {
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
65
cmd/docs.go
65
cmd/docs.go
|
@ -1,65 +0,0 @@
|
||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdDocs represents the available docs sub-command.
|
|
||||||
var CmdDocs = &cli.Command{
|
|
||||||
Name: "docs",
|
|
||||||
Usage: "Output CLI documentation",
|
|
||||||
Description: "A command to output Forgejo's CLI documentation, optionally to a file.",
|
|
||||||
Action: runDocs,
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "man",
|
|
||||||
Usage: "Output man pages instead",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "output",
|
|
||||||
Aliases: []string{"o"},
|
|
||||||
Usage: "Path to output to instead of stdout (will overwrite if exists)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func runDocs(ctx *cli.Context) error {
|
|
||||||
docs, err := ctx.App.ToMarkdown()
|
|
||||||
if ctx.Bool("man") {
|
|
||||||
docs, err = ctx.App.ToMan()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ctx.Bool("man") {
|
|
||||||
// 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)
|
|
||||||
// https://github.com/urfave/cli/issues/1040
|
|
||||||
firstHashtagIndex := strings.Index(docs, "#")
|
|
||||||
|
|
||||||
if firstHashtagIndex > 0 {
|
|
||||||
docs = docs[firstHashtagIndex:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out := os.Stdout
|
|
||||||
if ctx.String("output") != "" {
|
|
||||||
fi, err := os.Create(ctx.String("output"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer fi.Close()
|
|
||||||
out = fi
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = fmt.Fprintln(out, docs)
|
|
||||||
return err
|
|
||||||
}
|
|
165
cmd/doctor.go
165
cmd/doctor.go
|
@ -4,6 +4,7 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
golog "log"
|
golog "log"
|
||||||
"os"
|
"os"
|
||||||
|
@ -11,89 +12,94 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"forgejo.org/models/db"
|
||||||
"code.gitea.io/gitea/models/migrations"
|
"forgejo.org/models/migrations"
|
||||||
migrate_base "code.gitea.io/gitea/models/migrations/base"
|
migrate_base "forgejo.org/models/migrations/base"
|
||||||
"code.gitea.io/gitea/modules/container"
|
"forgejo.org/modules/container"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"forgejo.org/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
"code.gitea.io/gitea/services/doctor"
|
"forgejo.org/services/doctor"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
"xorm.io/xorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdDoctor represents the available doctor sub-command.
|
// CmdDoctor represents the available doctor sub-command.
|
||||||
var CmdDoctor = &cli.Command{
|
func cmdDoctor() *cli.Command {
|
||||||
Name: "doctor",
|
return &cli.Command{
|
||||||
Usage: "Diagnose and optionally fix problems, convert or re-create database tables",
|
Name: "doctor",
|
||||||
Description: "A command to diagnose problems with the current Forgejo instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
|
Usage: "Diagnose and optionally fix problems, convert or re-create database tables",
|
||||||
|
Description: "A command to diagnose problems with the current Forgejo 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(),
|
||||||
},
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmdDoctorCheck = &cli.Command{
|
func cmdDoctorCheck() *cli.Command {
|
||||||
Name: "check",
|
return &cli.Command{
|
||||||
Usage: "Diagnose and optionally fix problems",
|
Name: "check",
|
||||||
Description: "A command to diagnose problems with the current Forgejo instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
|
Usage: "Diagnose and optionally fix problems",
|
||||||
Action: runDoctorCheck,
|
Description: "A command to diagnose problems with the current Forgejo instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
|
||||||
Flags: []cli.Flag{
|
Action: runDoctorCheck,
|
||||||
&cli.BoolFlag{
|
Flags: []cli.Flag{
|
||||||
Name: "list",
|
&cli.BoolFlag{
|
||||||
Usage: "List the available checks",
|
Name: "list",
|
||||||
|
Usage: "List the available checks",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "default",
|
||||||
|
Usage: "Run the default checks (if neither --run or --all is set, this is the default behaviour)",
|
||||||
|
},
|
||||||
|
&cli.StringSliceFlag{
|
||||||
|
Name: "run",
|
||||||
|
Usage: "Run the provided checks - (if --default is set, the default checks will also run)",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "all",
|
||||||
|
Usage: "Run all the available checks",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "fix",
|
||||||
|
Usage: "Automatically fix what we can",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "log-file",
|
||||||
|
Usage: `Name of the log file (no verbose log output by default). Set to "-" to output to stdout`,
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "color",
|
||||||
|
Aliases: []string{"H"},
|
||||||
|
Usage: "Use color for outputted information",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
}
|
||||||
Name: "default",
|
|
||||||
Usage: "Run the default checks (if neither --run or --all is set, this is the default behaviour)",
|
|
||||||
},
|
|
||||||
&cli.StringSliceFlag{
|
|
||||||
Name: "run",
|
|
||||||
Usage: "Run the provided checks - (if --default is set, the default checks will also run)",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "all",
|
|
||||||
Usage: "Run all the available checks",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "fix",
|
|
||||||
Usage: "Automatically fix what we can",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "log-file",
|
|
||||||
Usage: `Name of the log file (no verbose log output by default). Set to "-" to output to stdout`,
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "color",
|
|
||||||
Aliases: []string{"H"},
|
|
||||||
Usage: "Use color for outputted information",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmdRecreateTable = &cli.Command{
|
func cmdRecreateTable() *cli.Command {
|
||||||
Name: "recreate-table",
|
return &cli.Command{
|
||||||
Usage: "Recreate tables from XORM definitions and copy the data.",
|
Name: "recreate-table",
|
||||||
ArgsUsage: "[TABLE]... : (TABLEs to recreate - leave blank for all)",
|
Usage: "Recreate tables from XORM definitions and copy the data.",
|
||||||
Flags: []cli.Flag{
|
ArgsUsage: "[TABLE]... : (TABLEs to recreate - leave blank for all)",
|
||||||
&cli.BoolFlag{
|
Flags: []cli.Flag{
|
||||||
Name: "debug",
|
&cli.BoolFlag{
|
||||||
Usage: "Print SQL commands sent",
|
Name: "debug",
|
||||||
|
Usage: "Print SQL commands sent",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
Description: `The database definitions Forgejo uses change across versions, sometimes changing default values and leaving old unused columns.
|
||||||
Description: `The database definitions Forgejo uses change across versions, sometimes changing default values and leaving old unused columns.
|
|
||||||
|
|
||||||
This command will cause Xorm to recreate tables, copying over the data and deleting the old table.
|
This command will cause Xorm to recreate tables, copying over the data and deleting the old table.
|
||||||
|
|
||||||
You should back-up your database before doing this and ensure that your database is up-to-date first.`,
|
You should back-up your database before doing this and ensure that your database is up-to-date first.`,
|
||||||
Action: runRecreateTable,
|
Action: runRecreateTable,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRecreateTable(ctx *cli.Context) error {
|
func runRecreateTable(stdCtx context.Context, ctx *cli.Command) error {
|
||||||
stdCtx, cancel := installSignals()
|
stdCtx, cancel := installSignals(stdCtx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
// Redirect the default golog to here
|
// Redirect the default golog to here
|
||||||
|
@ -120,7 +126,7 @@ func runRecreateTable(ctx *cli.Context) error {
|
||||||
|
|
||||||
args := ctx.Args()
|
args := ctx.Args()
|
||||||
names := make([]string, 0, ctx.NArg())
|
names := make([]string, 0, ctx.NArg())
|
||||||
for i := 0; i < ctx.NArg(); i++ {
|
for i := range ctx.NArg() {
|
||||||
names = append(names, args.Get(i))
|
names = append(names, args.Get(i))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,24 +136,31 @@ 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(stdCtx, func(x db.Engine) error {
|
||||||
if err := migrations.EnsureUpToDate(x); err != nil {
|
engine, err := db.GetMasterEngine(x)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return recreateTables(x)
|
|
||||||
|
if err := migrations.EnsureUpToDate(engine); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return recreateTables(engine)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) {
|
func setupDoctorDefaultLogger(ctx *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 := ctx.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,8 +172,8 @@ func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDoctorCheck(ctx *cli.Context) error {
|
func runDoctorCheck(stdCtx context.Context, ctx *cli.Command) error {
|
||||||
stdCtx, cancel := installSignals()
|
stdCtx, cancel := installSignals(stdCtx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
colorize := log.CanColorStdout
|
colorize := log.CanColorStdout
|
||||||
|
|
|
@ -4,25 +4,28 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"forgejo.org/models/db"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"forgejo.org/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/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.
|
||||||
var cmdDoctorConvert = &cli.Command{
|
func cmdDoctorConvert() *cli.Command {
|
||||||
Name: "convert",
|
return &cli.Command{
|
||||||
Usage: "Convert the database",
|
Name: "convert",
|
||||||
Description: "A command to convert an existing MySQL database from utf8 to utf8mb4",
|
Usage: "Convert the database",
|
||||||
Action: runDoctorConvert,
|
Description: "A command to convert an existing MySQL database from utf8 to utf8mb4",
|
||||||
|
Action: runDoctorConvert,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDoctorConvert(ctx *cli.Context) error {
|
func runDoctorConvert(stdCtx context.Context, ctx *cli.Command) error {
|
||||||
stdCtx, cancel := installSignals()
|
stdCtx, cancel := installSignals(stdCtx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := initDB(stdCtx); err != nil {
|
if err := initDB(stdCtx); err != nil {
|
||||||
|
|
|
@ -7,11 +7,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"forgejo.org/modules/log"
|
||||||
"code.gitea.io/gitea/services/doctor"
|
"forgejo.org/services/doctor"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDoctorRun(t *testing.T) {
|
func TestDoctorRun(t *testing.T) {
|
||||||
|
@ -22,12 +22,12 @@ func TestDoctorRun(t *testing.T) {
|
||||||
|
|
||||||
SkipDatabaseInitialization: true,
|
SkipDatabaseInitialization: true,
|
||||||
})
|
})
|
||||||
app := cli.NewApp()
|
app := cli.Command{}
|
||||||
app.Commands = []*cli.Command{cmdDoctorCheck}
|
app.Commands = []*cli.Command{cmdDoctorCheck()}
|
||||||
err := app.Run([]string{"./gitea", "check", "--run", "test-check"})
|
err := app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check"})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = app.Run([]string{"./gitea", "check", "--run", "no-such"})
|
err = app.Run(t.Context(), []string{"./gitea", "check", "--run", "no-such"})
|
||||||
require.ErrorContains(t, err, `unknown checks: "no-such"`)
|
require.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"})
|
||||||
require.ErrorContains(t, err, `unknown checks: "no-such"`)
|
require.ErrorContains(t, err, `unknown checks: "no-such"`)
|
||||||
}
|
}
|
||||||
|
|
220
cmd/dump.go
220
cmd/dump.go
|
@ -5,6 +5,8 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
@ -13,16 +15,16 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"forgejo.org/models/db"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"forgejo.org/modules/json"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"forgejo.org/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"forgejo.org/modules/storage"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"forgejo.org/modules/util"
|
||||||
|
|
||||||
"code.forgejo.org/go-chi/session"
|
"code.forgejo.org/go-chi/session"
|
||||||
"github.com/mholt/archiver/v3"
|
"github.com/mholt/archiver/v3"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addReader(w archiver.Writer, r io.ReadCloser, info os.FileInfo, customName string, verbose bool) error {
|
func addReader(w archiver.Writer, r io.ReadCloser, info os.FileInfo, customName string, verbose bool) error {
|
||||||
|
@ -83,6 +85,10 @@ func (o *outputType) Set(value string) error {
|
||||||
return fmt.Errorf("allowed values are %s", o.Join())
|
return fmt.Errorf("allowed values are %s", o.Join())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *outputType) Get() any {
|
||||||
|
return o.String()
|
||||||
|
}
|
||||||
|
|
||||||
func (o outputType) String() string {
|
func (o outputType) String() string {
|
||||||
if o.selected == "" {
|
if o.selected == "" {
|
||||||
return o.Default
|
return o.Default
|
||||||
|
@ -96,76 +102,81 @@ var outputTypeEnum = &outputType{
|
||||||
}
|
}
|
||||||
|
|
||||||
// CmdDump represents the available dump sub-command.
|
// CmdDump represents the available dump sub-command.
|
||||||
var CmdDump = &cli.Command{
|
func cmdDump() *cli.Command {
|
||||||
Name: "dump",
|
return &cli.Command{
|
||||||
Usage: "Dump Forgejo files and database",
|
Name: "dump",
|
||||||
Description: `Dump compresses all related files and database into zip file.
|
Usage: "Dump Forgejo files and database",
|
||||||
|
Description: `Dump compresses all related files and database into zip file.
|
||||||
It can be used for backup and capture Forgejo server image to send to maintainer`,
|
It can be used for backup and capture Forgejo server image to send to maintainer`,
|
||||||
Action: runDump,
|
Action: runDump,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "file",
|
Name: "file",
|
||||||
Aliases: []string{"f"},
|
Aliases: []string{"f"},
|
||||||
Value: fmt.Sprintf("forgejo-dump-%d.zip", time.Now().Unix()),
|
Value: fmt.Sprintf("forgejo-dump-%d.zip", time.Now().Unix()),
|
||||||
Usage: "Name of the dump file which will be created. Supply '-' for stdout. See type for available types.",
|
Usage: "Name of the dump file which will be created. Supply '-' for stdout. See type for available types.",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "verbose",
|
||||||
|
Aliases: []string{"V"},
|
||||||
|
Usage: "Show process details",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "quiet",
|
||||||
|
Aliases: []string{"q"},
|
||||||
|
Usage: "Only display warnings and errors",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "tempdir",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Usage: "Temporary dir path",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "database",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
Usage: "Specify the database SQL syntax: sqlite3, mysql, postgres",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "skip-repository",
|
||||||
|
Aliases: []string{"R"},
|
||||||
|
Usage: "Skip repositories",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "skip-log",
|
||||||
|
Aliases: []string{"L"},
|
||||||
|
Usage: "Skip logs",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "skip-custom-dir",
|
||||||
|
Usage: "Skip custom directory",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "skip-lfs-data",
|
||||||
|
Usage: "Skip LFS data",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "skip-attachment-data",
|
||||||
|
Usage: "Skip attachment data",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "skip-package-data",
|
||||||
|
Usage: "Skip package data",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "skip-index",
|
||||||
|
Usage: "Skip bleve index data",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "skip-repo-archives",
|
||||||
|
Usage: "Skip repository archives",
|
||||||
|
},
|
||||||
|
&cli.GenericFlag{
|
||||||
|
Name: "type",
|
||||||
|
Value: outputTypeEnum,
|
||||||
|
Usage: fmt.Sprintf("Dump output format: %s", outputTypeEnum.Join()),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
}
|
||||||
Name: "verbose",
|
|
||||||
Aliases: []string{"V"},
|
|
||||||
Usage: "Show process details",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "quiet",
|
|
||||||
Aliases: []string{"q"},
|
|
||||||
Usage: "Only display warnings and errors",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "tempdir",
|
|
||||||
Aliases: []string{"t"},
|
|
||||||
Value: os.TempDir(),
|
|
||||||
Usage: "Temporary dir path",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "database",
|
|
||||||
Aliases: []string{"d"},
|
|
||||||
Usage: "Specify the database SQL syntax: sqlite3, mysql, postgres",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "skip-repository",
|
|
||||||
Aliases: []string{"R"},
|
|
||||||
Usage: "Skip the repository dumping",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "skip-log",
|
|
||||||
Aliases: []string{"L"},
|
|
||||||
Usage: "Skip the log dumping",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "skip-custom-dir",
|
|
||||||
Usage: "Skip custom directory",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "skip-lfs-data",
|
|
||||||
Usage: "Skip LFS data",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "skip-attachment-data",
|
|
||||||
Usage: "Skip attachment data",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "skip-package-data",
|
|
||||||
Usage: "Skip package data",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "skip-index",
|
|
||||||
Usage: "Skip bleve index data",
|
|
||||||
},
|
|
||||||
&cli.GenericFlag{
|
|
||||||
Name: "type",
|
|
||||||
Value: outputTypeEnum,
|
|
||||||
Usage: fmt.Sprintf("Dump output format: %s", outputTypeEnum.Join()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func fatal(format string, args ...any) {
|
func fatal(format string, args ...any) {
|
||||||
|
@ -173,7 +184,7 @@ func fatal(format string, args ...any) {
|
||||||
log.Fatal(format, args...)
|
log.Fatal(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDump(ctx *cli.Context) error {
|
func runDump(stdCtx context.Context, ctx *cli.Command) error {
|
||||||
var file *os.File
|
var file *os.File
|
||||||
fileName := ctx.String("file")
|
fileName := ctx.String("file")
|
||||||
outType := ctx.String("type")
|
outType := ctx.String("type")
|
||||||
|
@ -209,16 +220,16 @@ func runDump(ctx *cli.Context) error {
|
||||||
|
|
||||||
if !setting.InstallLock {
|
if !setting.InstallLock {
|
||||||
log.Error("Is '%s' really the right config path?\n", setting.CustomConf)
|
log.Error("Is '%s' really the right config path?\n", setting.CustomConf)
|
||||||
return fmt.Errorf("forgejo is not initialized")
|
return errors.New("forgejo is not initialized")
|
||||||
}
|
}
|
||||||
setting.LoadSettings() // cannot access session settings otherwise
|
setting.LoadSettings() // cannot access session settings otherwise
|
||||||
|
|
||||||
verbose := ctx.Bool("verbose")
|
verbose := ctx.Bool("verbose")
|
||||||
if verbose && ctx.Bool("quiet") {
|
if verbose && ctx.Bool("quiet") {
|
||||||
return fmt.Errorf("--quiet and --verbose cannot both be set")
|
return errors.New("--quiet and --verbose cannot both be set")
|
||||||
}
|
}
|
||||||
|
|
||||||
stdCtx, cancel := installSignals()
|
stdCtx, cancel := installSignals(stdCtx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
err := db.InitEngine(stdCtx)
|
err := db.InitEngine(stdCtx)
|
||||||
|
@ -233,7 +244,7 @@ func runDump(ctx *cli.Context) error {
|
||||||
if file == nil {
|
if file == nil {
|
||||||
file, err = os.Create(fileName)
|
file, err = os.Create(fileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatal("Unable to open %s: %v", fileName, err)
|
fatal("Failed to open %s: %v", fileName, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
@ -250,7 +261,7 @@ func runDump(ctx *cli.Context) error {
|
||||||
iface, err = archiver.ByExtension(fileName)
|
iface, err = archiver.ByExtension(fileName)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatal("Unable to get archiver for extension: %v", err)
|
fatal("Failed to get archiver for extension: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
w, _ := iface.(archiver.Writer)
|
w, _ := iface.(archiver.Writer)
|
||||||
|
@ -260,7 +271,7 @@ func runDump(ctx *cli.Context) error {
|
||||||
defer w.Close()
|
defer w.Close()
|
||||||
|
|
||||||
if ctx.IsSet("skip-repository") && ctx.Bool("skip-repository") {
|
if ctx.IsSet("skip-repository") && ctx.Bool("skip-repository") {
|
||||||
log.Info("Skip dumping local repositories")
|
log.Info("Skipping local repositories")
|
||||||
} else {
|
} else {
|
||||||
log.Info("Dumping local repositories... %s", setting.RepoRootPath)
|
log.Info("Dumping local repositories... %s", setting.RepoRootPath)
|
||||||
if err := addRecursiveExclude(w, "repos", setting.RepoRootPath, []string{absFileName}, verbose); err != nil {
|
if err := addRecursiveExclude(w, "repos", setting.RepoRootPath, []string{absFileName}, verbose); err != nil {
|
||||||
|
@ -268,9 +279,9 @@ func runDump(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.IsSet("skip-lfs-data") && ctx.Bool("skip-lfs-data") {
|
if ctx.IsSet("skip-lfs-data") && ctx.Bool("skip-lfs-data") {
|
||||||
log.Info("Skip dumping LFS data")
|
log.Info("Skipping 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 not enabled - skipping")
|
||||||
} else if err := storage.LFS.IterateObjects("", func(objPath string, object storage.Object) error {
|
} else if err := storage.LFS.IterateObjects("", func(objPath string, object storage.Object) error {
|
||||||
info, err := object.Stat()
|
info, err := object.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -284,18 +295,31 @@ func runDump(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpDir := ctx.String("tempdir")
|
tmpDir := ctx.String("tempdir")
|
||||||
|
if tmpDir == "" {
|
||||||
|
tmpDir, err = os.MkdirTemp("", "forgejo-dump-*")
|
||||||
|
if err != nil {
|
||||||
|
fatal("Failed to create temporary directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := util.Remove(tmpDir); err != nil {
|
||||||
|
log.Warn("Failed to remove temporary directory: %s: Error: %v", tmpDir, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
dbDump, err := os.CreateTemp(tmpDir, "forgejo-db.sql")
|
dbDump, err := os.CreateTemp(tmpDir, "forgejo-db.sql")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatal("Failed to create tmp file: %v", err)
|
fatal("Failed to create temporary file: %v", err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = dbDump.Close()
|
_ = dbDump.Close()
|
||||||
if err := util.Remove(dbDump.Name()); err != nil {
|
if err := util.Remove(dbDump.Name()); err != nil {
|
||||||
log.Warn("Unable to remove temporary file: %s: Error: %v", dbDump.Name(), err)
|
log.Warn("Failed to remove temporary database file: %s: Error: %v", dbDump.Name(), err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -331,16 +355,16 @@ func runDump(ctx *cli.Context) error {
|
||||||
fatal("Failed to include custom: %v", err)
|
fatal("Failed to include custom: %v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Info("Custom dir %s is inside data dir %s, skipped", setting.CustomPath, setting.AppDataPath)
|
log.Info("Custom dir %s is inside data dir %s, skipping", setting.CustomPath, setting.AppDataPath)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Info("Custom dir %s doesn't exist, skipped", setting.CustomPath)
|
log.Info("Custom dir %s does not exist, skipping", setting.CustomPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isExist, err := util.IsExist(setting.AppDataPath)
|
isExist, err := util.IsExist(setting.AppDataPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to check if %s exists. Error: %v", setting.AppDataPath, err)
|
log.Error("Failed to check if %s exists: %v", setting.AppDataPath, err)
|
||||||
}
|
}
|
||||||
if isExist {
|
if isExist {
|
||||||
log.Info("Packing data directory...%s", setting.AppDataPath)
|
log.Info("Packing data directory...%s", setting.AppDataPath)
|
||||||
|
@ -355,10 +379,16 @@ func runDump(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.IsSet("skip-index") && ctx.Bool("skip-index") {
|
if ctx.IsSet("skip-index") && ctx.Bool("skip-index") {
|
||||||
|
log.Info("Skipping bleve index data")
|
||||||
excludes = append(excludes, setting.Indexer.RepoPath)
|
excludes = append(excludes, setting.Indexer.RepoPath)
|
||||||
excludes = append(excludes, setting.Indexer.IssuePath)
|
excludes = append(excludes, setting.Indexer.IssuePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ctx.IsSet("skip-repo-archives") && ctx.Bool("skip-repo-archives") {
|
||||||
|
log.Info("Skipping repository archives data")
|
||||||
|
excludes = append(excludes, setting.RepoArchive.Storage.Path)
|
||||||
|
}
|
||||||
|
|
||||||
excludes = append(excludes, setting.RepoRootPath)
|
excludes = append(excludes, setting.RepoRootPath)
|
||||||
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)
|
||||||
|
@ -371,7 +401,7 @@ func runDump(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") {
|
if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") {
|
||||||
log.Info("Skip dumping attachment data")
|
log.Info("Skipping 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 {
|
||||||
|
@ -384,9 +414,9 @@ func runDump(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.IsSet("skip-package-data") && ctx.Bool("skip-package-data") {
|
if ctx.IsSet("skip-package-data") && ctx.Bool("skip-package-data") {
|
||||||
log.Info("Skip dumping package data")
|
log.Info("Skipping package data")
|
||||||
} else if !setting.Packages.Enabled {
|
} else if !setting.Packages.Enabled {
|
||||||
log.Info("Packages isn't enabled. Skip dumping package data")
|
log.Info("Package registry not enabled - skipping")
|
||||||
} else if err := storage.Packages.IterateObjects("", func(objPath string, object storage.Object) error {
|
} else if err := storage.Packages.IterateObjects("", func(objPath string, object storage.Object) error {
|
||||||
info, err := object.Stat()
|
info, err := object.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -402,11 +432,11 @@ func runDump(ctx *cli.Context) error {
|
||||||
// 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 ctx.IsSet("skip-log") && ctx.Bool("skip-log") {
|
||||||
log.Info("Skip dumping log files")
|
log.Info("Skipping log files")
|
||||||
} else {
|
} else {
|
||||||
isExist, err := util.IsExist(setting.Log.RootPath)
|
isExist, err := util.IsExist(setting.Log.RootPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to check if %s exists. Error: %v", setting.Log.RootPath, err)
|
log.Error("Failed to check if %s exists: %v", setting.Log.RootPath, err)
|
||||||
}
|
}
|
||||||
if isExist {
|
if isExist {
|
||||||
if err := addRecursiveExclude(w, "log", setting.Log.RootPath, []string{absFileName}, verbose); err != nil {
|
if err := addRecursiveExclude(w, "log", setting.Log.RootPath, []string{absFileName}, verbose); err != nil {
|
||||||
|
@ -456,7 +486,7 @@ func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeA
|
||||||
currentInsidePath := path.Join(insidePath, file.Name())
|
currentInsidePath := path.Join(insidePath, file.Name())
|
||||||
|
|
||||||
if util.SliceContainsString(excludeAbsPath, currentAbsPath) {
|
if util.SliceContainsString(excludeAbsPath, currentAbsPath) {
|
||||||
log.Debug("Skipping %q because matched an excluded path.", currentAbsPath)
|
log.Debug("Skipping %q (matched an excluded path)", currentAbsPath)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
133
cmd/dump_repo.go
133
cmd/dump_repo.go
|
@ -10,77 +10,84 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
"forgejo.org/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"forgejo.org/modules/log"
|
||||||
base "code.gitea.io/gitea/modules/migration"
|
base "forgejo.org/modules/migration"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"forgejo.org/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"forgejo.org/modules/util"
|
||||||
"code.gitea.io/gitea/services/convert"
|
"forgejo.org/services/convert"
|
||||||
"code.gitea.io/gitea/services/migrations"
|
"forgejo.org/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.
|
||||||
var CmdDumpRepository = &cli.Command{
|
func cmdDumpRepository() *cli.Command {
|
||||||
Name: "dump-repo",
|
return &cli.Command{
|
||||||
Usage: "Dump the repository from git/github/gitea/gitlab",
|
Name: "dump-repo",
|
||||||
Description: "This is a command for dumping the repository data.",
|
Usage: "Dump the repository from git/github/gitea/gitlab",
|
||||||
Action: runDumpRepository,
|
Description: "This is a command for dumping the repository data.",
|
||||||
Flags: []cli.Flag{
|
Action: runDumpRepository,
|
||||||
&cli.StringFlag{
|
Flags: []cli.Flag{
|
||||||
Name: "git_service",
|
&cli.StringFlag{
|
||||||
Value: "",
|
Name: "git_service",
|
||||||
Usage: "Git service, git, github, gitea, gitlab. If clone_addr could be recognized, this could be ignored.",
|
Value: "",
|
||||||
},
|
Usage: "Git service, git, github, gitea, gitlab. If clone_addr could be recognized, this could be ignored.",
|
||||||
&cli.StringFlag{
|
},
|
||||||
Name: "repo_dir",
|
&cli.StringFlag{
|
||||||
Aliases: []string{"r"},
|
Name: "repo_dir",
|
||||||
Value: "./data",
|
Aliases: []string{"r"},
|
||||||
Usage: "Repository dir path to store the data",
|
Value: "./data",
|
||||||
},
|
Usage: "Repository dir path to store the data",
|
||||||
&cli.StringFlag{
|
},
|
||||||
Name: "clone_addr",
|
&cli.StringFlag{
|
||||||
Value: "",
|
Name: "clone_addr",
|
||||||
Usage: "The URL will be clone, currently could be a git/github/gitea/gitlab http/https URL",
|
Value: "",
|
||||||
},
|
Usage: "The URL will be clone, currently could be a git/github/gitea/gitlab http/https URL",
|
||||||
&cli.StringFlag{
|
},
|
||||||
Name: "auth_username",
|
&cli.StringFlag{
|
||||||
Value: "",
|
Name: "auth_username",
|
||||||
Usage: "The username to visit the clone_addr",
|
Value: "",
|
||||||
},
|
Usage: "The username to visit the clone_addr",
|
||||||
&cli.StringFlag{
|
},
|
||||||
Name: "auth_password",
|
&cli.StringFlag{
|
||||||
Value: "",
|
Name: "auth_password",
|
||||||
Usage: "The password to visit the clone_addr",
|
Value: "",
|
||||||
},
|
Usage: "The password to visit the clone_addr",
|
||||||
&cli.StringFlag{
|
},
|
||||||
Name: "auth_token",
|
&cli.StringFlag{
|
||||||
Value: "",
|
Name: "auth_token",
|
||||||
Usage: "The personal token to visit the clone_addr",
|
Value: "",
|
||||||
},
|
Usage: "The personal token to visit the clone_addr",
|
||||||
&cli.StringFlag{
|
},
|
||||||
Name: "owner_name",
|
&cli.StringFlag{
|
||||||
Value: "",
|
Name: "owner_name",
|
||||||
Usage: "The data will be stored on a directory with owner name if not empty",
|
Value: "",
|
||||||
},
|
Usage: "The data will be stored on a directory with owner name if not empty",
|
||||||
&cli.StringFlag{
|
},
|
||||||
Name: "repo_name",
|
&cli.StringFlag{
|
||||||
Value: "",
|
Name: "repo_name",
|
||||||
Usage: "The data will be stored on a directory with repository name if not empty",
|
Value: "",
|
||||||
},
|
Usage: "The data will be stored on a directory with repository name if not empty",
|
||||||
&cli.StringFlag{
|
},
|
||||||
Name: "units",
|
&cli.StringFlag{
|
||||||
Value: "",
|
Name: "units",
|
||||||
Usage: `Which items will be migrated, one or more units should be separated as comma.
|
Value: "",
|
||||||
|
Usage: `Which items will be migrated, one or more units should be separated as comma.
|
||||||
wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`,
|
wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDumpRepository(ctx *cli.Context) error {
|
func runDumpRepository(stdCtx context.Context, ctx *cli.Command) error {
|
||||||
stdCtx, cancel := installSignals()
|
setupConsoleLogger(log.INFO, log.CanColorStderr, os.Stderr)
|
||||||
|
|
||||||
|
// setting.DisableLoggerInit()
|
||||||
|
setting.LoadSettings() // cannot access skip_tls_verify settings otherwise
|
||||||
|
|
||||||
|
stdCtx, cancel := installSignals(stdCtx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := initDB(stdCtx); err != nil {
|
if err := initDB(stdCtx); err != nil {
|
||||||
|
|
|
@ -4,38 +4,41 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/assetfs"
|
"forgejo.org/modules/assetfs"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"forgejo.org/modules/log"
|
||||||
"code.gitea.io/gitea/modules/options"
|
"forgejo.org/modules/options"
|
||||||
"code.gitea.io/gitea/modules/public"
|
"forgejo.org/modules/public"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/templates"
|
"forgejo.org/modules/templates"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"forgejo.org/modules/util"
|
||||||
|
|
||||||
"github.com/gobwas/glob"
|
"github.com/gobwas/glob"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdEmbedded represents the available extract sub-command.
|
// CmdEmbedded represents the available extract sub-command.
|
||||||
var (
|
func cmdEmbedded() *cli.Command {
|
||||||
CmdEmbedded = &cli.Command{
|
return &cli.Command{
|
||||||
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(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
subcmdList = &cli.Command{
|
func subcmdList() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Usage: "List files matching the given pattern",
|
Usage: "List files matching the given pattern",
|
||||||
Action: runList,
|
Action: runList,
|
||||||
|
@ -47,8 +50,10 @@ var (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
subcmdView = &cli.Command{
|
func subcmdView() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "view",
|
Name: "view",
|
||||||
Usage: "View a file matching the given pattern",
|
Usage: "View a file matching the given pattern",
|
||||||
Action: runView,
|
Action: runView,
|
||||||
|
@ -60,8 +65,10 @@ var (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
subcmdExtract = &cli.Command{
|
func subcmdExtract() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "extract",
|
Name: "extract",
|
||||||
Usage: "Extract resources",
|
Usage: "Extract resources",
|
||||||
Action: runExtract,
|
Action: runExtract,
|
||||||
|
@ -90,9 +97,9 @@ var (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
matchedAssetFiles []assetFile
|
var matchedAssetFiles []assetFile
|
||||||
)
|
|
||||||
|
|
||||||
type assetFile struct {
|
type assetFile struct {
|
||||||
fs *assetfs.LayeredFS
|
fs *assetfs.LayeredFS
|
||||||
|
@ -100,7 +107,7 @@ type assetFile struct {
|
||||||
path string
|
path string
|
||||||
}
|
}
|
||||||
|
|
||||||
func initEmbeddedExtractor(c *cli.Context) error {
|
func initEmbeddedExtractor(_ context.Context, 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,32 +122,32 @@ func initEmbeddedExtractor(c *cli.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runList(c *cli.Context) error {
|
func runList(ctx context.Context, c *cli.Command) error {
|
||||||
if err := runListDo(c); err != nil {
|
if err := runListDo(ctx, 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(ctx context.Context, c *cli.Command) error {
|
||||||
if err := runViewDo(c); err != nil {
|
if err := runViewDo(ctx, 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(ctx context.Context, c *cli.Command) error {
|
||||||
if err := runExtractDo(c); err != nil {
|
if err := runExtractDo(ctx, 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(ctx context.Context, c *cli.Command) error {
|
||||||
if err := initEmbeddedExtractor(c); err != nil {
|
if err := initEmbeddedExtractor(ctx, c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,8 +158,8 @@ func runListDo(c *cli.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runViewDo(c *cli.Context) error {
|
func runViewDo(ctx context.Context, c *cli.Command) error {
|
||||||
if err := initEmbeddedExtractor(c); err != nil {
|
if err := initEmbeddedExtractor(ctx, c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,8 +181,8 @@ func runViewDo(c *cli.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runExtractDo(c *cli.Context) error {
|
func runExtractDo(ctx context.Context, c *cli.Command) error {
|
||||||
if err := initEmbeddedExtractor(c); err != nil {
|
if err := initEmbeddedExtractor(ctx, c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,7 +278,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 {
|
||||||
|
|
|
@ -6,24 +6,25 @@ package forgejo
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
actions_model "forgejo.org/models/actions"
|
||||||
"code.gitea.io/gitea/modules/private"
|
"forgejo.org/modules/private"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
private_routers "code.gitea.io/gitea/routers/private"
|
private_routers "forgejo.org/routers/private"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CmdActions(ctx context.Context) *cli.Command {
|
func CmdActions(ctx context.Context) *cli.Command {
|
||||||
return &cli.Command{
|
return &cli.Command{
|
||||||
Name: "actions",
|
Name: "actions",
|
||||||
Usage: "Commands for managing Forgejo Actions",
|
Usage: "Commands for managing Forgejo Actions",
|
||||||
Subcommands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
SubcmdActionsGenerateRunnerToken(ctx),
|
SubcmdActionsGenerateRunnerToken(ctx),
|
||||||
SubcmdActionsGenerateRunnerSecret(ctx),
|
SubcmdActionsGenerateRunnerSecret(ctx),
|
||||||
SubcmdActionsRegister(ctx),
|
SubcmdActionsRegister(ctx),
|
||||||
|
@ -36,7 +37,7 @@ func SubcmdActionsGenerateRunnerToken(ctx context.Context) *cli.Command {
|
||||||
Name: "generate-runner-token",
|
Name: "generate-runner-token",
|
||||||
Usage: "Generate a new token for a runner to use to register with the server",
|
Usage: "Generate a new token for a runner to use to register with the server",
|
||||||
Before: prepareWorkPathAndCustomConf(ctx),
|
Before: prepareWorkPathAndCustomConf(ctx),
|
||||||
Action: func(cliCtx *cli.Context) error { return RunGenerateActionsRunnerToken(ctx, cliCtx) },
|
Action: RunGenerateActionsRunnerToken,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "scope",
|
Name: "scope",
|
||||||
|
@ -52,7 +53,7 @@ func SubcmdActionsGenerateRunnerSecret(ctx context.Context) *cli.Command {
|
||||||
return &cli.Command{
|
return &cli.Command{
|
||||||
Name: "generate-secret",
|
Name: "generate-secret",
|
||||||
Usage: "Generate a secret suitable for input to the register subcommand",
|
Usage: "Generate a secret suitable for input to the register subcommand",
|
||||||
Action: func(cliCtx *cli.Context) error { return RunGenerateSecret(ctx, cliCtx) },
|
Action: RunGenerateSecret,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +62,7 @@ func SubcmdActionsRegister(ctx context.Context) *cli.Command {
|
||||||
Name: "register",
|
Name: "register",
|
||||||
Usage: "Idempotent registration of a runner using a shared secret",
|
Usage: "Idempotent registration of a runner using a shared secret",
|
||||||
Before: prepareWorkPathAndCustomConf(ctx),
|
Before: prepareWorkPathAndCustomConf(ctx),
|
||||||
Action: func(cliCtx *cli.Context) error { return RunRegister(ctx, cliCtx) },
|
Action: RunRegister,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "secret",
|
Name: "secret",
|
||||||
|
@ -105,26 +106,26 @@ func SubcmdActionsRegister(ctx context.Context) *cli.Command {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func readSecret(ctx context.Context, cliCtx *cli.Context) (string, error) {
|
func readSecret(ctx context.Context, cli *cli.Command) (string, error) {
|
||||||
if cliCtx.IsSet("secret") {
|
if cli.IsSet("secret") {
|
||||||
return cliCtx.String("secret"), nil
|
return cli.String("secret"), nil
|
||||||
}
|
}
|
||||||
if cliCtx.IsSet("secret-stdin") {
|
if cli.IsSet("secret-stdin") {
|
||||||
buf, err := io.ReadAll(ContextGetStdin(ctx))
|
buf, err := io.ReadAll(ContextGetStdin(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return string(buf), nil
|
return string(buf), nil
|
||||||
}
|
}
|
||||||
if cliCtx.IsSet("secret-file") {
|
if cli.IsSet("secret-file") {
|
||||||
path := cliCtx.String("secret-file")
|
path := cli.String("secret-file")
|
||||||
buf, err := os.ReadFile(path)
|
buf, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return string(buf), nil
|
return string(buf), nil
|
||||||
}
|
}
|
||||||
return "", fmt.Errorf("at least one of the --secret, --secret-stdin, --secret-file options is required")
|
return "", errors.New("at least one of the --secret, --secret-stdin, --secret-file options is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateSecret(secret string) error {
|
func validateSecret(secret string) error {
|
||||||
|
@ -138,18 +139,18 @@ func validateSecret(secret string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLabels(cliCtx *cli.Context) (*[]string, error) {
|
func getLabels(cli *cli.Command) (*[]string, error) {
|
||||||
if !cliCtx.Bool("keep-labels") {
|
if !cli.Bool("keep-labels") {
|
||||||
lblValue := strings.Split(cliCtx.String("labels"), ",")
|
lblValue := strings.Split(cli.String("labels"), ",")
|
||||||
return &lblValue, nil
|
return &lblValue, nil
|
||||||
}
|
}
|
||||||
if cliCtx.String("labels") != "" {
|
if cli.String("labels") != "" {
|
||||||
return nil, fmt.Errorf("--labels and --keep-labels should not be used together")
|
return nil, errors.New("--labels and --keep-labels should not be used together")
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunRegister(ctx context.Context, cliCtx *cli.Context) error {
|
func RunRegister(ctx context.Context, cli *cli.Command) error {
|
||||||
var cancel context.CancelFunc
|
var cancel context.CancelFunc
|
||||||
if !ContextGetNoInit(ctx) {
|
if !ContextGetNoInit(ctx) {
|
||||||
ctx, cancel = installSignals(ctx)
|
ctx, cancel = installSignals(ctx)
|
||||||
|
@ -161,17 +162,17 @@ func RunRegister(ctx context.Context, cliCtx *cli.Context) error {
|
||||||
}
|
}
|
||||||
setting.MustInstalled()
|
setting.MustInstalled()
|
||||||
|
|
||||||
secret, err := readSecret(ctx, cliCtx)
|
secret, err := readSecret(ctx, cli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := validateSecret(secret); err != nil {
|
if err := validateSecret(secret); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
scope := cliCtx.String("scope")
|
scope := cli.String("scope")
|
||||||
name := cliCtx.String("name")
|
name := cli.String("name")
|
||||||
version := cliCtx.String("version")
|
version := cli.String("version")
|
||||||
labels, err := getLabels(cliCtx)
|
labels, err := getLabels(cli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -209,7 +210,7 @@ func RunRegister(ctx context.Context, cliCtx *cli.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunGenerateSecret(ctx context.Context, cliCtx *cli.Context) error {
|
func RunGenerateSecret(ctx context.Context, cli *cli.Command) error {
|
||||||
runner := actions_model.ActionRunner{}
|
runner := actions_model.ActionRunner{}
|
||||||
if err := runner.GenerateToken(); err != nil {
|
if err := runner.GenerateToken(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -220,7 +221,7 @@ func RunGenerateSecret(ctx context.Context, cliCtx *cli.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunGenerateActionsRunnerToken(ctx context.Context, cliCtx *cli.Context) error {
|
func RunGenerateActionsRunnerToken(ctx context.Context, cli *cli.Command) error {
|
||||||
if !ContextGetNoInit(ctx) {
|
if !ContextGetNoInit(ctx) {
|
||||||
var cancel context.CancelFunc
|
var cancel context.CancelFunc
|
||||||
ctx, cancel = installSignals(ctx)
|
ctx, cancel = installSignals(ctx)
|
||||||
|
@ -229,7 +230,7 @@ func RunGenerateActionsRunnerToken(ctx context.Context, cliCtx *cli.Context) err
|
||||||
|
|
||||||
setting.MustInstalled()
|
setting.MustInstalled()
|
||||||
|
|
||||||
scope := cliCtx.String("scope")
|
scope := cli.String("scope")
|
||||||
|
|
||||||
respText, extra := private.GenerateActionsRunnerToken(ctx, scope)
|
respText, extra := private.GenerateActionsRunnerToken(ctx, scope)
|
||||||
if extra.HasError() {
|
if extra.HasError() {
|
||||||
|
|
|
@ -4,14 +4,13 @@
|
||||||
package forgejo
|
package forgejo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/services/context"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestActions_getLabels(t *testing.T) {
|
func TestActions_getLabels(t *testing.T) {
|
||||||
|
@ -54,21 +53,21 @@ func TestActions_getLabels(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := SubcmdActionsRegister(context.Context{}).Flags
|
flags := SubcmdActionsRegister(t.Context()).Flags
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
t.Run(fmt.Sprintf("args: %v", c.args), func(t *testing.T) {
|
t.Run(fmt.Sprintf("args: %v", c.args), func(t *testing.T) {
|
||||||
// Create a copy of command to test
|
// Create a copy of command to test
|
||||||
var result *resultType
|
var result *resultType
|
||||||
app := cli.NewApp()
|
app := cli.Command{}
|
||||||
app.Flags = flags
|
app.Flags = flags
|
||||||
app.Action = func(ctx *cli.Context) error {
|
app.Action = func(_ context.Context, ctx *cli.Command) error {
|
||||||
labels, err := getLabels(ctx)
|
labels, err := getLabels(ctx)
|
||||||
result = &resultType{labels, err}
|
result = &resultType{labels, err}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run it
|
// Run it
|
||||||
_ = app.Run(c.args)
|
_ = app.Run(t.Context(), c.args)
|
||||||
|
|
||||||
// Test the results
|
// Test the results
|
||||||
require.NotNil(t, result)
|
require.NotNil(t, result)
|
||||||
|
|
|
@ -8,19 +8,19 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"forgejo.org/models"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"forgejo.org/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"forgejo.org/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"forgejo.org/modules/storage"
|
||||||
"code.gitea.io/gitea/services/f3/util"
|
"forgejo.org/services/f3/util"
|
||||||
|
|
||||||
_ "code.gitea.io/gitea/services/f3/driver" // register the driver
|
_ "forgejo.org/services/f3/driver" // register the driver
|
||||||
|
|
||||||
f3_cmd "code.forgejo.org/f3/gof3/v3/cmd"
|
f3_cmd "code.forgejo.org/f3/gof3/v3/cmd"
|
||||||
f3_logger "code.forgejo.org/f3/gof3/v3/logger"
|
f3_logger "code.forgejo.org/f3/gof3/v3/logger"
|
||||||
f3_util "code.forgejo.org/f3/gof3/v3/util"
|
f3_util "code.forgejo.org/f3/gof3/v3/util"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CmdF3(ctx context.Context) *cli.Command {
|
func CmdF3(ctx context.Context) *cli.Command {
|
||||||
|
@ -28,21 +28,21 @@ func CmdF3(ctx context.Context) *cli.Command {
|
||||||
return &cli.Command{
|
return &cli.Command{
|
||||||
Name: "f3",
|
Name: "f3",
|
||||||
Usage: "F3",
|
Usage: "F3",
|
||||||
Subcommands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
SubcmdF3Mirror(ctx),
|
SubcmdF3Mirror(ctx),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func SubcmdF3Mirror(ctx context.Context) *cli.Command {
|
func SubcmdF3Mirror(ctx context.Context) *cli.Command {
|
||||||
mirrorCmd := f3_cmd.CreateCmdMirror(ctx)
|
mirrorCmd := f3_cmd.CreateCmdMirror()
|
||||||
mirrorCmd.Before = prepareWorkPathAndCustomConf(ctx)
|
mirrorCmd.Before = prepareWorkPathAndCustomConf(ctx)
|
||||||
f3Action := mirrorCmd.Action
|
f3Action := mirrorCmd.Action
|
||||||
mirrorCmd.Action = func(c *cli.Context) error { return runMirror(ctx, c, f3Action) }
|
mirrorCmd.Action = func(ctx context.Context, cli *cli.Command) error { return runMirror(ctx, cli, f3Action) }
|
||||||
return mirrorCmd
|
return mirrorCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runMirror(ctx context.Context, c *cli.Context, action cli.ActionFunc) error {
|
func runMirror(ctx context.Context, c *cli.Command, action cli.ActionFunc) error {
|
||||||
setting.LoadF3Setting()
|
setting.LoadF3Setting()
|
||||||
if !setting.F3.Enabled {
|
if !setting.F3.Enabled {
|
||||||
return errors.New("F3 is disabled, it is not ready to be used and is only present for development purposes")
|
return errors.New("F3 is disabled, it is not ready to be used and is only present for development purposes")
|
||||||
|
@ -69,7 +69,7 @@ func runMirror(ctx context.Context, c *cli.Context, action cli.ActionFunc) error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := action(c)
|
err := action(ctx, c)
|
||||||
if panicError, ok := err.(f3_util.PanicError); ok {
|
if panicError, ok := err.(f3_util.PanicError); ok {
|
||||||
log.Debug("F3 Stack trace\n%s", panicError.Stack())
|
log.Debug("F3 Stack trace\n%s", panicError.Stack())
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,12 +11,12 @@ import (
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"forgejo.org/models/db"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"forgejo.org/modules/log"
|
||||||
"code.gitea.io/gitea/modules/private"
|
"forgejo.org/modules/private"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type key int
|
type key int
|
||||||
|
@ -34,7 +34,7 @@ func CmdForgejo(ctx context.Context) *cli.Command {
|
||||||
Name: "forgejo-cli",
|
Name: "forgejo-cli",
|
||||||
Usage: "Forgejo CLI",
|
Usage: "Forgejo CLI",
|
||||||
Flags: []cli.Flag{},
|
Flags: []cli.Flag{},
|
||||||
Subcommands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
CmdActions(ctx),
|
CmdActions(ctx),
|
||||||
CmdF3(ctx),
|
CmdF3(ctx),
|
||||||
},
|
},
|
||||||
|
@ -147,12 +147,12 @@ func handleCliResponseExtra(ctx context.Context, extra private.ResponseExtra) er
|
||||||
return cli.Exit(extra.Error, 1)
|
return cli.Exit(extra.Error, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareWorkPathAndCustomConf(ctx context.Context) func(c *cli.Context) error {
|
func prepareWorkPathAndCustomConf(ctx context.Context) func(ctx context.Context, cli *cli.Command) (context.Context, error) {
|
||||||
return func(c *cli.Context) error {
|
return func(ctx context.Context, cli *cli.Command) (context.Context, error) {
|
||||||
if !ContextGetNoInit(ctx) {
|
if !ContextGetNoInit(ctx) {
|
||||||
var args setting.ArgWorkPathAndCustomConf
|
var args setting.ArgWorkPathAndCustomConf
|
||||||
// from children to parent, check the global flags
|
// from children to parent, check the global flags
|
||||||
for _, curCtx := range c.Lineage() {
|
for _, curCtx := range cli.Lineage() {
|
||||||
if curCtx.IsSet("work-path") && args.WorkPath == "" {
|
if curCtx.IsSet("work-path") && args.WorkPath == "" {
|
||||||
args.WorkPath = curCtx.String("work-path")
|
args.WorkPath = curCtx.String("work-path")
|
||||||
}
|
}
|
||||||
|
@ -165,6 +165,6 @@ func prepareWorkPathAndCustomConf(ctx context.Context) func(c *cli.Context) erro
|
||||||
}
|
}
|
||||||
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
|
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
|
||||||
}
|
}
|
||||||
return nil
|
return ctx, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,56 +5,65 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/generate"
|
"forgejo.org/modules/generate"
|
||||||
|
|
||||||
"github.com/mattn/go-isatty"
|
"github.com/mattn/go-isatty"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// CmdGenerate represents the available generate sub-command.
|
||||||
// CmdGenerate represents the available generate sub-command.
|
func cmdGenerate() *cli.Command {
|
||||||
CmdGenerate = &cli.Command{
|
return &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(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
subcmdSecret = &cli.Command{
|
func subcmdSecret() *cli.Command {
|
||||||
|
return &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(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
microcmdGenerateInternalToken = &cli.Command{
|
func microcmdGenerateInternalToken() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "INTERNAL_TOKEN",
|
Name: "INTERNAL_TOKEN",
|
||||||
Usage: "Generate a new INTERNAL_TOKEN",
|
Usage: "Generate a new INTERNAL_TOKEN",
|
||||||
Action: runGenerateInternalToken,
|
Action: runGenerateInternalToken,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
microcmdGenerateLfsJwtSecret = &cli.Command{
|
func microcmdGenerateLfsJwtSecret() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "JWT_SECRET",
|
Name: "JWT_SECRET",
|
||||||
Aliases: []string{"LFS_JWT_SECRET"},
|
Aliases: []string{"LFS_JWT_SECRET"},
|
||||||
Usage: "Generate a new JWT_SECRET",
|
Usage: "Generate a new JWT_SECRET",
|
||||||
Action: runGenerateLfsJwtSecret,
|
Action: runGenerateLfsJwtSecret,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
microcmdGenerateSecretKey = &cli.Command{
|
func microcmdGenerateSecretKey() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "SECRET_KEY",
|
Name: "SECRET_KEY",
|
||||||
Usage: "Generate a new SECRET_KEY",
|
Usage: "Generate a new SECRET_KEY",
|
||||||
Action: runGenerateSecretKey,
|
Action: runGenerateSecretKey,
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
func runGenerateInternalToken(c *cli.Context) error {
|
func runGenerateInternalToken(ctx context.Context, c *cli.Command) error {
|
||||||
internalToken, err := generate.NewInternalToken()
|
internalToken, err := generate.NewInternalToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -63,28 +72,25 @@ func runGenerateInternalToken(c *cli.Context) error {
|
||||||
fmt.Printf("%s", internalToken)
|
fmt.Printf("%s", internalToken)
|
||||||
|
|
||||||
if isatty.IsTerminal(os.Stdout.Fd()) {
|
if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||||
fmt.Printf("\n")
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runGenerateLfsJwtSecret(c *cli.Context) error {
|
func runGenerateLfsJwtSecret(ctx context.Context, c *cli.Command) error {
|
||||||
_, jwtSecretBase64, err := generate.NewJwtSecret()
|
_, jwtSecretBase64 := generate.NewJwtSecret()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("%s", jwtSecretBase64)
|
fmt.Printf("%s", jwtSecretBase64)
|
||||||
|
|
||||||
if isatty.IsTerminal(os.Stdout.Fd()) {
|
if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||||
fmt.Printf("\n")
|
fmt.Print("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runGenerateSecretKey(c *cli.Context) error {
|
func runGenerateSecretKey(ctx context.Context, c *cli.Command) error {
|
||||||
secretKey, err := generate.NewSecretKey()
|
secretKey, err := generate.NewSecretKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -93,7 +99,7 @@ func runGenerateSecretKey(c *cli.Context) error {
|
||||||
fmt.Printf("%s", secretKey)
|
fmt.Printf("%s", secretKey)
|
||||||
|
|
||||||
if isatty.IsTerminal(os.Stdout.Fd()) {
|
if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||||
fmt.Printf("\n")
|
fmt.Print("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
128
cmd/hook.go
128
cmd/hook.go
|
@ -14,36 +14,38 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
"forgejo.org/modules/git"
|
||||||
"code.gitea.io/gitea/modules/git/pushoptions"
|
"forgejo.org/modules/git/pushoptions"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"forgejo.org/modules/log"
|
||||||
"code.gitea.io/gitea/modules/private"
|
"forgejo.org/modules/private"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
repo_module "forgejo.org/modules/repository"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
hookBatchSize = 30
|
hookBatchSize = 30
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// CmdHook represents the available hooks sub-command.
|
||||||
// CmdHook represents the available hooks sub-command.
|
func cmdHook() *cli.Command {
|
||||||
CmdHook = &cli.Command{
|
return &cli.Command{
|
||||||
Name: "hook",
|
Name: "hook",
|
||||||
Usage: "(internal) Should only be called by Git",
|
Usage: "(internal) Should only be called by Git",
|
||||||
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(),
|
||||||
subcmdHookProcReceive,
|
subcmdHookProcReceive(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
subcmdHookPreReceive = &cli.Command{
|
func subcmdHookPreReceive() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "pre-receive",
|
Name: "pre-receive",
|
||||||
Usage: "Delegate pre-receive Git hook",
|
Usage: "Delegate pre-receive Git hook",
|
||||||
Description: "This command should only be called by Git",
|
Description: "This command should only be called by Git",
|
||||||
|
@ -54,7 +56,10 @@ var (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
subcmdHookUpdate = &cli.Command{
|
}
|
||||||
|
|
||||||
|
func subcmdHookUpdate() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "update",
|
Name: "update",
|
||||||
Usage: "Delegate update Git hook",
|
Usage: "Delegate update Git hook",
|
||||||
Description: "This command should only be called by Git",
|
Description: "This command should only be called by Git",
|
||||||
|
@ -65,7 +70,10 @@ var (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
subcmdHookPostReceive = &cli.Command{
|
}
|
||||||
|
|
||||||
|
func subcmdHookPostReceive() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "post-receive",
|
Name: "post-receive",
|
||||||
Usage: "Delegate post-receive Git hook",
|
Usage: "Delegate post-receive Git hook",
|
||||||
Description: "This command should only be called by Git",
|
Description: "This command should only be called by Git",
|
||||||
|
@ -76,8 +84,11 @@ var (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
// Note: new hook since git 2.29
|
}
|
||||||
subcmdHookProcReceive = &cli.Command{
|
|
||||||
|
// Note: new hook since git 2.29
|
||||||
|
func subcmdHookProcReceive() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "proc-receive",
|
Name: "proc-receive",
|
||||||
Usage: "Delegate proc-receive Git hook",
|
Usage: "Delegate proc-receive Git hook",
|
||||||
Description: "This command should only be called by Git",
|
Description: "This command should only be called by Git",
|
||||||
|
@ -88,7 +99,7 @@ var (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
type delayWriter struct {
|
type delayWriter struct {
|
||||||
internal io.Writer
|
internal io.Writer
|
||||||
|
@ -161,14 +172,14 @@ 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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"), true)
|
||||||
|
|
||||||
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
||||||
if setting.OnlyAllowPushIfGiteaEnvironmentSet {
|
if setting.OnlyAllowPushIfGiteaEnvironmentSet {
|
||||||
|
@ -220,11 +231,6 @@ Forgejo or set your environment appropriately.`, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
supportProcReceive := false
|
|
||||||
if git.CheckGitVersionAtLeast("2.29") == nil {
|
|
||||||
supportProcReceive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
// TODO: support news feeds for wiki
|
// TODO: support news feeds for wiki
|
||||||
if isWiki {
|
if isWiki {
|
||||||
|
@ -242,34 +248,28 @@ Forgejo or set your environment appropriately.`, "")
|
||||||
total++
|
total++
|
||||||
lastline++
|
lastline++
|
||||||
|
|
||||||
// If the ref is a branch or tag, check if it's protected
|
// All references should be checked because permission check was delayed.
|
||||||
// if supportProcReceive all ref should be checked because
|
oldCommitIDs[count] = oldCommitID
|
||||||
// permission check was delayed
|
newCommitIDs[count] = newCommitID
|
||||||
if supportProcReceive || refFullName.IsBranch() || refFullName.IsTag() {
|
refFullNames[count] = refFullName
|
||||||
oldCommitIDs[count] = oldCommitID
|
count++
|
||||||
newCommitIDs[count] = newCommitID
|
fmt.Fprint(out, "*")
|
||||||
refFullNames[count] = refFullName
|
|
||||||
count++
|
|
||||||
fmt.Fprintf(out, "*")
|
|
||||||
|
|
||||||
if count >= hookBatchSize {
|
if count >= hookBatchSize {
|
||||||
fmt.Fprintf(out, " Checking %d references\n", count)
|
fmt.Fprintf(out, " Checking %d references\n", count)
|
||||||
|
|
||||||
hookOptions.OldCommitIDs = oldCommitIDs
|
hookOptions.OldCommitIDs = oldCommitIDs
|
||||||
hookOptions.NewCommitIDs = newCommitIDs
|
hookOptions.NewCommitIDs = newCommitIDs
|
||||||
hookOptions.RefFullNames = refFullNames
|
hookOptions.RefFullNames = refFullNames
|
||||||
extra := private.HookPreReceive(ctx, username, reponame, hookOptions)
|
extra := private.HookPreReceive(ctx, username, reponame, hookOptions)
|
||||||
if extra.HasError() {
|
if extra.HasError() {
|
||||||
return fail(ctx, extra.UserMsg, "HookPreReceive(batch) failed: %v", extra.Error)
|
return fail(ctx, extra.UserMsg, "HookPreReceive(batch) failed: %v", extra.Error)
|
||||||
}
|
|
||||||
count = 0
|
|
||||||
lastline = 0
|
|
||||||
}
|
}
|
||||||
} else {
|
count = 0
|
||||||
fmt.Fprintf(out, ".")
|
lastline = 0
|
||||||
}
|
}
|
||||||
if lastline >= hookBatchSize {
|
if lastline >= hookBatchSize {
|
||||||
fmt.Fprintf(out, "\n")
|
fmt.Fprint(out, "\n")
|
||||||
lastline = 0
|
lastline = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -286,7 +286,7 @@ Forgejo or set your environment appropriately.`, "")
|
||||||
return fail(ctx, extra.UserMsg, "HookPreReceive(last) failed: %v", extra.Error)
|
return fail(ctx, extra.UserMsg, "HookPreReceive(last) failed: %v", extra.Error)
|
||||||
}
|
}
|
||||||
} else if lastline > 0 {
|
} else if lastline > 0 {
|
||||||
fmt.Fprintf(out, "\n")
|
fmt.Fprint(out, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(out, "Checked %d references in total\n", total)
|
fmt.Fprintf(out, "Checked %d references in total\n", total)
|
||||||
|
@ -294,13 +294,13 @@ Forgejo or set your environment appropriately.`, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// runHookUpdate process the update hook: https://git-scm.com/docs/githooks#update
|
// runHookUpdate process the update hook: https://git-scm.com/docs/githooks#update
|
||||||
func runHookUpdate(c *cli.Context) error {
|
func runHookUpdate(ctx context.Context, c *cli.Command) error {
|
||||||
// Now if we're an internal don't do anything else
|
// Now if we're an internal don't do anything else
|
||||||
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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if c.NArg() != 3 {
|
if c.NArg() != 3 {
|
||||||
|
@ -326,11 +326,11 @@ func runHookUpdate(c *cli.Context) error {
|
||||||
return fail(ctx, fmt.Sprintf("The modification of %s is skipped as it's an internal reference.", refFullName), "")
|
return fail(ctx, fmt.Sprintf("The modification of %s is skipped as it's an internal reference.", refFullName), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func runHookPostReceive(c *cli.Context) error {
|
func runHookPostReceive(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"), true)
|
||||||
|
|
||||||
// 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 := git.NewCommand(ctx, "update-server-info").RunStdString(nil); err != nil {
|
||||||
|
@ -402,7 +402,7 @@ Forgejo or set your environment appropriately.`, "")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(out, ".")
|
fmt.Fprint(out, ".")
|
||||||
oldCommitIDs[count] = string(fields[0])
|
oldCommitIDs[count] = string(fields[0])
|
||||||
newCommitIDs[count] = string(fields[1])
|
newCommitIDs[count] = string(fields[1])
|
||||||
refFullNames[count] = git.RefName(fields[2])
|
refFullNames[count] = git.RefName(fields[2])
|
||||||
|
@ -490,11 +490,11 @@ func hookPrintResults(results []private.HookPostReceiveBranchResult) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runHookProcReceive(c *cli.Context) error {
|
func runHookProcReceive(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"), true)
|
||||||
|
|
||||||
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
||||||
if setting.OnlyAllowPushIfGiteaEnvironmentSet {
|
if setting.OnlyAllowPushIfGiteaEnvironmentSet {
|
||||||
|
@ -505,10 +505,6 @@ Forgejo or set your environment appropriately.`, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if git.CheckGitVersionAtLeast("2.29") != nil {
|
|
||||||
return fail(ctx, "No proc-receive support", "current git version doesn't support proc-receive.")
|
|
||||||
}
|
|
||||||
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
reader := bufio.NewReader(os.Stdin)
|
||||||
repoUser := os.Getenv(repo_module.EnvRepoUsername)
|
repoUser := os.Getenv(repo_module.EnvRepoUsername)
|
||||||
repoName := os.Getenv(repo_module.EnvRepoName)
|
repoName := os.Getenv(repo_module.EnvRepoName)
|
||||||
|
|
|
@ -6,7 +6,6 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
@ -15,12 +14,12 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/test"
|
"forgejo.org/modules/test"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Capture what's being written into a standard file descriptor.
|
// Capture what's being written into a standard file descriptor.
|
||||||
|
@ -42,7 +41,7 @@ func captureOutput(t *testing.T, stdFD *os.File) (finish func() (output string))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPktLine(t *testing.T) {
|
func TestPktLine(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
t.Run("Read", func(t *testing.T) {
|
t.Run("Read", func(t *testing.T) {
|
||||||
s := strings.NewReader("0000")
|
s := strings.NewReader("0000")
|
||||||
|
@ -135,14 +134,14 @@ func TestDelayWriter(t *testing.T) {
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
defer test.MockVariableValue(&setting.LocalURL, ts.URL+"/")()
|
defer test.MockVariableValue(&setting.LocalURL, ts.URL+"/")()
|
||||||
|
|
||||||
app := cli.NewApp()
|
app := cli.Command{}
|
||||||
app.Commands = []*cli.Command{subcmdHookPreReceive}
|
app.Commands = []*cli.Command{subcmdHookPreReceive()}
|
||||||
|
|
||||||
t.Run("Should delay", func(t *testing.T) {
|
t.Run("Should delay", func(t *testing.T) {
|
||||||
defer test.MockVariableValue(&setting.Git.VerbosePushDelay, time.Millisecond*500)()
|
defer test.MockVariableValue(&setting.Git.VerbosePushDelay, time.Millisecond*500)()
|
||||||
finish := captureOutput(t, os.Stdout)
|
finish := captureOutput(t, os.Stdout)
|
||||||
|
|
||||||
err = app.Run([]string{"./forgejo", "pre-receive"})
|
err = app.Run(t.Context(), []string{"./forgejo", "pre-receive"})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
out := finish()
|
out := finish()
|
||||||
|
|
||||||
|
@ -154,7 +153,7 @@ func TestDelayWriter(t *testing.T) {
|
||||||
defer test.MockVariableValue(&setting.Git.VerbosePushDelay, time.Second*5)()
|
defer test.MockVariableValue(&setting.Git.VerbosePushDelay, time.Second*5)()
|
||||||
finish := captureOutput(t, os.Stdout)
|
finish := captureOutput(t, os.Stdout)
|
||||||
|
|
||||||
err = app.Run([]string{"./forgejo", "pre-receive"})
|
err = app.Run(t.Context(), []string{"./forgejo", "pre-receive"})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
out := finish()
|
out := finish()
|
||||||
|
|
||||||
|
@ -164,15 +163,15 @@ func TestDelayWriter(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunHookUpdate(t *testing.T) {
|
func TestRunHookUpdate(t *testing.T) {
|
||||||
app := cli.NewApp()
|
app := cli.Command{}
|
||||||
app.Commands = []*cli.Command{subcmdHookUpdate}
|
app.Commands = []*cli.Command{subcmdHookUpdate()}
|
||||||
|
|
||||||
t.Run("Removal of internal reference", func(t *testing.T) {
|
t.Run("Removal of internal reference", func(t *testing.T) {
|
||||||
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
|
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
|
||||||
defer test.MockVariableValue(&setting.IsProd, false)()
|
defer test.MockVariableValue(&setting.IsProd, false)()
|
||||||
finish := captureOutput(t, os.Stderr)
|
finish := captureOutput(t, os.Stderr)
|
||||||
|
|
||||||
err := app.Run([]string{"./forgejo", "update", "refs/pull/1/head", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000000"})
|
err := app.Run(t.Context(), []string{"./forgejo", "update", "refs/pull/1/head", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000000"})
|
||||||
out := finish()
|
out := finish()
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
|
@ -184,7 +183,7 @@ func TestRunHookUpdate(t *testing.T) {
|
||||||
defer test.MockVariableValue(&setting.IsProd, false)()
|
defer test.MockVariableValue(&setting.IsProd, false)()
|
||||||
finish := captureOutput(t, os.Stderr)
|
finish := captureOutput(t, os.Stderr)
|
||||||
|
|
||||||
err := app.Run([]string{"./forgejo", "update", "refs/pull/1/head", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000001"})
|
err := app.Run(t.Context(), []string{"./forgejo", "update", "refs/pull/1/head", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000001"})
|
||||||
out := finish()
|
out := finish()
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
|
@ -192,12 +191,12 @@ func TestRunHookUpdate(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Removal of branch", func(t *testing.T) {
|
t.Run("Removal of branch", func(t *testing.T) {
|
||||||
err := app.Run([]string{"./forgejo", "update", "refs/head/main", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000000"})
|
err := app.Run(t.Context(), []string{"./forgejo", "update", "refs/head/main", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000000"})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Not enough arguments", func(t *testing.T) {
|
t.Run("Not enough arguments", func(t *testing.T) {
|
||||||
err := app.Run([]string{"./forgejo", "update"})
|
err := app.Run(t.Context(), []string{"./forgejo", "update"})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
79
cmd/keys.go
79
cmd/keys.go
|
@ -4,52 +4,55 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"forgejo.org/modules/log"
|
||||||
"code.gitea.io/gitea/modules/private"
|
"forgejo.org/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{
|
func cmdKeys() *cli.Command {
|
||||||
Name: "keys",
|
return &cli.Command{
|
||||||
Usage: "(internal) Should only be called by SSH server",
|
Name: "keys",
|
||||||
Description: "Queries the Forgejo database to get the authorized command for a given ssh key fingerprint",
|
Usage: "(internal) Should only be called by SSH server",
|
||||||
Before: PrepareConsoleLoggerLevel(log.FATAL),
|
Description: "Queries the Forgejo database to get the authorized command for a given ssh key fingerprint",
|
||||||
Action: runKeys,
|
Before: PrepareConsoleLoggerLevel(log.FATAL),
|
||||||
Flags: []cli.Flag{
|
Action: runKeys,
|
||||||
&cli.StringFlag{
|
Flags: []cli.Flag{
|
||||||
Name: "expected",
|
&cli.StringFlag{
|
||||||
Aliases: []string{"e"},
|
Name: "expected",
|
||||||
Value: "git",
|
Aliases: []string{"e"},
|
||||||
Usage: "Expected user for whom provide key commands",
|
Value: "git",
|
||||||
|
Usage: "Expected user for whom provide key commands",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "username",
|
||||||
|
Aliases: []string{"u"},
|
||||||
|
Value: "",
|
||||||
|
Usage: "Username trying to log in by SSH",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "type",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Value: "",
|
||||||
|
Usage: "Type of the SSH key provided to the SSH Server (requires content to be provided too)",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "content",
|
||||||
|
Aliases: []string{"k"},
|
||||||
|
Value: "",
|
||||||
|
Usage: "Base64 encoded content of the SSH key provided to the SSH Server (requires type to be provided too)",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
}
|
||||||
Name: "username",
|
|
||||||
Aliases: []string{"u"},
|
|
||||||
Value: "",
|
|
||||||
Usage: "Username trying to log in by SSH",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "type",
|
|
||||||
Aliases: []string{"t"},
|
|
||||||
Value: "",
|
|
||||||
Usage: "Type of the SSH key provided to the SSH Server (requires content to be provided too)",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "content",
|
|
||||||
Aliases: []string{"k"},
|
|
||||||
Value: "",
|
|
||||||
Usage: "Base64 encoded content of the SSH key provided to the SSH Server (requires type to be provided too)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,16 +71,16 @@ 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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"), true)
|
||||||
|
|
||||||
authorizedString, extra := private.AuthorizedPublicKeyByContent(ctx, content)
|
authorizedString, extra := private.AuthorizedPublicKeyByContent(ctx, content)
|
||||||
// do not use handleCliResponseExtra or cli.NewExitError, if it exists immediately, it breaks some tests like Test_CmdKeys
|
// do not use handleCliResponseExtra or cli.NewExitError, if it exists immediately, it breaks some tests like Test_CmdKeys
|
||||||
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,16 +4,17 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/private"
|
"forgejo.org/modules/private"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
setting.MustInstalled()
|
setting.MustInstalled()
|
||||||
|
|
126
cmd/main.go
126
cmd/main.go
|
@ -10,11 +10,11 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/cmd/forgejo"
|
"forgejo.org/cmd/forgejo"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"forgejo.org/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// cmdHelp is our own help subcommand with more information
|
// cmdHelp is our own help subcommand with more information
|
||||||
|
@ -25,18 +25,18 @@ func cmdHelp() *cli.Command {
|
||||||
Aliases: []string{"h"},
|
Aliases: []string{"h"},
|
||||||
Usage: "Shows a list of commands or help for one command",
|
Usage: "Shows a list of commands or help for one command",
|
||||||
ArgsUsage: "[command]",
|
ArgsUsage: "[command]",
|
||||||
Action: func(c *cli.Context) (err error) {
|
Action: func(ctx context.Context, c *cli.Command) (err error) {
|
||||||
lineage := c.Lineage() // The order is from child to parent: help, doctor, Gitea, {Command:nil}
|
lineage := c.Lineage() // The order is from child to parent: help, doctor, Forgejo
|
||||||
targetCmdIdx := 0
|
targetCmdIdx := 0
|
||||||
if c.Command.Name == "help" {
|
if c.Name == "help" {
|
||||||
targetCmdIdx = 1
|
targetCmdIdx = 1
|
||||||
}
|
}
|
||||||
if lineage[targetCmdIdx+1].Command != nil {
|
if targetCmdIdx+1 < len(lineage) {
|
||||||
err = cli.ShowCommandHelp(lineage[targetCmdIdx+1], lineage[targetCmdIdx].Command.Name)
|
err = cli.ShowCommandHelp(ctx, lineage[targetCmdIdx+1], lineage[targetCmdIdx].Name)
|
||||||
} else {
|
} else {
|
||||||
err = cli.ShowAppHelp(c)
|
err = cli.ShowAppHelp(c)
|
||||||
}
|
}
|
||||||
_, _ = fmt.Fprintf(c.App.Writer, `
|
_, _ = fmt.Fprintf(c.Root().Writer, `
|
||||||
DEFAULT CONFIGURATION:
|
DEFAULT CONFIGURATION:
|
||||||
AppPath: %s
|
AppPath: %s
|
||||||
WorkPath: %s
|
WorkPath: %s
|
||||||
|
@ -77,25 +77,25 @@ func appGlobalFlags() []cli.Flag {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareSubcommandWithConfig(command *cli.Command, globalFlags []cli.Flag) {
|
func prepareSubcommandWithConfig(command *cli.Command, globalFlags func() []cli.Flag) {
|
||||||
command.Flags = append(append([]cli.Flag{}, globalFlags...), command.Flags...)
|
command.Flags = append(globalFlags(), command.Flags...)
|
||||||
command.Action = prepareWorkPathAndCustomConf(command.Action)
|
command.Action = prepareWorkPathAndCustomConf(command.Action)
|
||||||
command.HideHelp = true
|
command.HideHelp = true
|
||||||
if command.Name != "help" {
|
if command.Name != "help" {
|
||||||
command.Subcommands = append(command.Subcommands, cmdHelp())
|
command.Commands = append(command.Commands, cmdHelp())
|
||||||
}
|
}
|
||||||
for i := range command.Subcommands {
|
for i := range command.Commands {
|
||||||
prepareSubcommandWithConfig(command.Subcommands[i], globalFlags)
|
prepareSubcommandWithConfig(command.Commands[i], globalFlags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config
|
// 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
|
// 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 {
|
func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(_ context.Context, _ *cli.Command) error {
|
||||||
return func(ctx *cli.Context) error {
|
return func(ctx context.Context, cli *cli.Command) error {
|
||||||
var args setting.ArgWorkPathAndCustomConf
|
var args setting.ArgWorkPathAndCustomConf
|
||||||
// from children to parent, check the global flags
|
// from children to parent, check the global flags
|
||||||
for _, curCtx := range ctx.Lineage() {
|
for _, curCtx := range cli.Lineage() {
|
||||||
if curCtx.IsSet("work-path") && args.WorkPath == "" {
|
if curCtx.IsSet("work-path") && args.WorkPath == "" {
|
||||||
args.WorkPath = curCtx.String("work-path")
|
args.WorkPath = curCtx.String("work-path")
|
||||||
}
|
}
|
||||||
|
@ -107,24 +107,24 @@ func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
|
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
|
||||||
if ctx.Bool("help") || action == nil {
|
if cli.Bool("help") || action == nil {
|
||||||
// the default behavior of "urfave/cli": "nil action" means "show help"
|
// the default behavior of "urfave/cli": "nil action" means "show help"
|
||||||
return cmdHelp().Action(ctx)
|
return cmdHelp().Action(ctx, cli)
|
||||||
}
|
}
|
||||||
return action(ctx)
|
return action(ctx, cli)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMainApp(version, versionExtra string) *cli.App {
|
func NewMainApp(version, versionExtra string) *cli.Command {
|
||||||
path, err := os.Executable()
|
path, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
executable := filepath.Base(path)
|
executable := filepath.Base(path)
|
||||||
|
|
||||||
var subCmdsStandalone []*cli.Command = make([]*cli.Command, 0, 10)
|
subCmdsStandalone := make([]*cli.Command, 0, 10)
|
||||||
var subCmdWithConfig []*cli.Command = make([]*cli.Command, 0, 10)
|
subCmdWithConfig := make([]*cli.Command, 0, 10)
|
||||||
var globalFlags []cli.Flag = make([]cli.Flag, 0, 10)
|
globalFlags := func() []cli.Flag { return []cli.Flag{} }
|
||||||
|
|
||||||
//
|
//
|
||||||
// If the executable is forgejo-cli, provide a Forgejo specific CLI
|
// If the executable is forgejo-cli, provide a Forgejo specific CLI
|
||||||
|
@ -133,14 +133,16 @@ func NewMainApp(version, versionExtra string) *cli.App {
|
||||||
if executable == "forgejo-cli" {
|
if executable == "forgejo-cli" {
|
||||||
subCmdsStandalone = append(subCmdsStandalone, forgejo.CmdActions(context.Background()))
|
subCmdsStandalone = append(subCmdsStandalone, forgejo.CmdActions(context.Background()))
|
||||||
subCmdWithConfig = append(subCmdWithConfig, forgejo.CmdF3(context.Background()))
|
subCmdWithConfig = append(subCmdWithConfig, forgejo.CmdF3(context.Background()))
|
||||||
globalFlags = append(globalFlags, []cli.Flag{
|
globalFlags = func() []cli.Flag {
|
||||||
&cli.BoolFlag{
|
return []cli.Flag{
|
||||||
Name: "quiet",
|
&cli.BoolFlag{
|
||||||
},
|
Name: "quiet",
|
||||||
&cli.BoolFlag{
|
},
|
||||||
Name: "verbose",
|
&cli.BoolFlag{
|
||||||
},
|
Name: "verbose",
|
||||||
}...)
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
//
|
//
|
||||||
// Otherwise provide a Gitea compatible CLI which includes Forgejo
|
// Otherwise provide a Gitea compatible CLI which includes Forgejo
|
||||||
|
@ -149,55 +151,54 @@ func NewMainApp(version, versionExtra string) *cli.App {
|
||||||
// binary and rename it to forgejo if they want.
|
// binary and rename it to forgejo if they want.
|
||||||
//
|
//
|
||||||
subCmdsStandalone = append(subCmdsStandalone, forgejo.CmdForgejo(context.Background()))
|
subCmdsStandalone = append(subCmdsStandalone, forgejo.CmdForgejo(context.Background()))
|
||||||
subCmdWithConfig = append(subCmdWithConfig, CmdActions)
|
subCmdWithConfig = append(subCmdWithConfig, cmdActions())
|
||||||
}
|
}
|
||||||
|
|
||||||
return innerNewMainApp(version, versionExtra, subCmdsStandalone, subCmdWithConfig, globalFlags)
|
return innerNewMainApp(version, versionExtra, subCmdsStandalone, subCmdWithConfig, globalFlags)
|
||||||
}
|
}
|
||||||
|
|
||||||
func innerNewMainApp(version, versionExtra string, subCmdsStandaloneArgs, subCmdWithConfigArgs []*cli.Command, globalFlagsArgs []cli.Flag) *cli.App {
|
func innerNewMainApp(version, versionExtra string, subCmdsStandaloneArgs, subCmdWithConfigArgs []*cli.Command, globalFlagsArgs func() []cli.Flag) *cli.Command {
|
||||||
app := cli.NewApp()
|
app := &cli.Command{}
|
||||||
app.HelpName = "forgejo"
|
app.Name = "forgejo"
|
||||||
app.Name = "Forgejo"
|
|
||||||
app.Usage = "Beyond coding. We forge."
|
app.Usage = "Beyond coding. We forge."
|
||||||
app.Description = `By default, forgejo will start serving using the web-server with no argument, which can alternatively be run by running the subcommand "web".`
|
app.Description = `By default, forgejo will start serving using the web-server with no argument, which can alternatively be run by running the subcommand "web".`
|
||||||
app.Version = version + versionExtra
|
app.Version = version + versionExtra
|
||||||
app.EnableBashCompletion = true
|
app.EnableShellCompletion = true
|
||||||
|
|
||||||
// these sub-commands need to use config file
|
// these sub-commands need to use 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"
|
cmdHelp(), // the "help" sub-command was used to show the more information for "work path" and "custom config"
|
||||||
CmdWeb,
|
cmdWeb(),
|
||||||
CmdServ,
|
cmdServ(),
|
||||||
CmdHook,
|
cmdHook(),
|
||||||
CmdKeys,
|
cmdKeys(),
|
||||||
CmdDump,
|
cmdDump(),
|
||||||
CmdAdmin,
|
cmdAdmin(),
|
||||||
CmdMigrate,
|
cmdMigrate(),
|
||||||
CmdDoctor,
|
cmdDoctor(),
|
||||||
CmdManager,
|
cmdManager(),
|
||||||
CmdEmbedded,
|
cmdEmbedded(),
|
||||||
CmdMigrateStorage,
|
cmdMigrateStorage(),
|
||||||
CmdDumpRepository,
|
cmdDumpRepository(),
|
||||||
CmdRestoreRepository,
|
cmdRestoreRepository(),
|
||||||
}
|
}
|
||||||
|
|
||||||
subCmdWithConfig = append(subCmdWithConfig, subCmdWithConfigArgs...)
|
subCmdWithConfig = append(subCmdWithConfig, subCmdWithConfigArgs...)
|
||||||
|
|
||||||
// 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,
|
|
||||||
}
|
}
|
||||||
subCmdStandalone = append(subCmdStandalone, subCmdsStandaloneArgs...)
|
subCmdStandalone = append(subCmdStandalone, subCmdsStandaloneArgs...)
|
||||||
|
|
||||||
app.DefaultCommand = CmdWeb.Name
|
app.DefaultCommand = cmdWeb().Name
|
||||||
|
|
||||||
globalFlags := appGlobalFlags()
|
globalFlags := func() []cli.Flag {
|
||||||
globalFlags = append(globalFlags, globalFlagsArgs...)
|
return append(appGlobalFlags(), globalFlagsArgs()...)
|
||||||
|
}
|
||||||
app.Flags = append(app.Flags, cli.VersionFlag)
|
app.Flags = append(app.Flags, cli.VersionFlag)
|
||||||
app.Flags = append(app.Flags, globalFlags...)
|
app.Flags = append(app.Flags, globalFlags()...)
|
||||||
app.HideHelp = true // use our own help action to show helps (with more information like default config)
|
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 {
|
||||||
|
@ -206,11 +207,12 @@ func innerNewMainApp(version, versionExtra string, subCmdsStandaloneArgs, subCmd
|
||||||
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)
|
err := app.Run(context.Background(), args)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -219,7 +221,7 @@ func RunMainApp(app *cli.App, args ...string) error {
|
||||||
cli.OsExiter(1)
|
cli.OsExiter(1)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, _ = fmt.Fprintf(app.ErrWriter, "Command error: %v\n", err)
|
_, _ = fmt.Fprintf(app.Root().ErrWriter, "Command error: %v\n", err)
|
||||||
cli.OsExiter(1)
|
cli.OsExiter(1)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,20 +4,21 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"forgejo.org/models/unittest"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/test"
|
"forgejo.org/modules/test"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
|
@ -28,10 +29,10 @@ 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(testCmdAction func(_ context.Context, ctx *cli.Command) error) *cli.Command {
|
||||||
app := NewMainApp("version", "version-extra")
|
app := NewMainApp("version", "version-extra")
|
||||||
testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction}
|
testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction}
|
||||||
prepareSubcommandWithConfig(testCmd, appGlobalFlags())
|
prepareSubcommandWithConfig(testCmd, appGlobalFlags)
|
||||||
app.Commands = append(app.Commands, testCmd)
|
app.Commands = append(app.Commands, testCmd)
|
||||||
app.DefaultCommand = testCmd.Name
|
app.DefaultCommand = testCmd.Name
|
||||||
return app
|
return app
|
||||||
|
@ -43,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
|
||||||
|
@ -66,7 +67,6 @@ 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.SubcommandHelpTemplate = "(subcommand help template)"
|
cli.SubcommandHelpTemplate = "(subcommand help template)"
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
|
@ -110,70 +110,55 @@ 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 {
|
defer test.MockProtect(&setting.AppWorkPath)()
|
||||||
_ = os.Setenv(k, v)
|
defer test.MockProtect(&setting.CustomPath)()
|
||||||
}
|
defer test.MockProtect(&setting.CustomConf)()
|
||||||
args := strings.Split(c.cmd, " ") // for test only, "split" is good enough
|
|
||||||
r, err := runTestApp(app, args...)
|
app := newTestApp(func(_ context.Context, ctx *cli.Command) error {
|
||||||
require.NoError(t, err, c.cmd)
|
_, _ = fmt.Fprint(ctx.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
|
||||||
assert.NotEmpty(t, c.exp, c.cmd)
|
return nil
|
||||||
assert.Contains(t, r.Stdout, c.exp, c.cmd)
|
})
|
||||||
|
|
||||||
|
for k, v := range c.env {
|
||||||
|
t.Setenv(k, v)
|
||||||
|
}
|
||||||
|
args := strings.Split(c.cmd, " ") // for test only, "split" is good enough
|
||||||
|
r, err := runTestApp(app, args...)
|
||||||
|
require.NoError(t, err, c.cmd)
|
||||||
|
assert.NotEmpty(t, c.exp, c.cmd)
|
||||||
|
assert.Contains(t, r.Stdout, c.exp, c.cmd+"\n"+r.Stdout)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCliCmdError(t *testing.T) {
|
func TestCliCmdError(t *testing.T) {
|
||||||
app := newTestApp(func(ctx *cli.Context) error { return fmt.Errorf("normal error") })
|
app := newTestApp(func(_ context.Context, ctx *cli.Command) error { return errors.New("normal error") })
|
||||||
r, err := runTestApp(app, "./gitea", "test-cmd")
|
r, err := runTestApp(app, "./gitea", "test-cmd")
|
||||||
require.Error(t, err)
|
require.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(func(_ context.Context, ctx *cli.Command) error { return cli.Exit("exit error", 2) })
|
||||||
r, err = runTestApp(app, "./gitea", "test-cmd")
|
r, err = runTestApp(app, "./gitea", "test-cmd")
|
||||||
require.Error(t, err)
|
require.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(func(_ context.Context, ctx *cli.Command) error { return nil })
|
||||||
r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such")
|
r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such")
|
||||||
require.Error(t, err)
|
require.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.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stderr)
|
||||||
assert.Equal(t, "", r.Stderr) // the cli package's strange behavior, the error message is not in stderr ....
|
assert.Empty(t, r.Stdout)
|
||||||
|
|
||||||
app = newTestApp(func(ctx *cli.Context) error { return nil })
|
app = newTestApp(func(_ context.Context, ctx *cli.Command) error { return nil })
|
||||||
r, err = runTestApp(app, "./gitea", "test-cmd")
|
r, err = runTestApp(app, "./gitea", "test-cmd")
|
||||||
require.NoError(t, err)
|
require.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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,30 +4,34 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/private"
|
"forgejo.org/modules/private"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// CmdManager represents the manager command
|
||||||
// CmdManager represents the manager command
|
func cmdManager() *cli.Command {
|
||||||
CmdManager = &cli.Command{
|
return &cli.Command{
|
||||||
Name: "manager",
|
Name: "manager",
|
||||||
Usage: "Manage the running forgejo process",
|
Usage: "Manage the running forgejo process",
|
||||||
Description: "This is a command for managing the running forgejo process",
|
Description: "This is a command for managing the running forgejo process",
|
||||||
Subcommands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
subcmdShutdown,
|
subcmdShutdown(),
|
||||||
subcmdRestart,
|
subcmdRestart(),
|
||||||
subcmdReloadTemplates,
|
subcmdReloadTemplates(),
|
||||||
subcmdFlushQueues,
|
subcmdFlushQueues(),
|
||||||
subcmdLogging,
|
subcmdLogging(),
|
||||||
subCmdProcesses,
|
subCmdProcesses(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
subcmdShutdown = &cli.Command{
|
}
|
||||||
|
|
||||||
|
func subcmdShutdown() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "shutdown",
|
Name: "shutdown",
|
||||||
Usage: "Gracefully shutdown the running process",
|
Usage: "Gracefully shutdown the running process",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
@ -37,7 +41,10 @@ var (
|
||||||
},
|
},
|
||||||
Action: runShutdown,
|
Action: runShutdown,
|
||||||
}
|
}
|
||||||
subcmdRestart = &cli.Command{
|
}
|
||||||
|
|
||||||
|
func subcmdRestart() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "restart",
|
Name: "restart",
|
||||||
Usage: "Gracefully restart the running process - (not implemented for windows servers)",
|
Usage: "Gracefully restart the running process - (not implemented for windows servers)",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
@ -47,7 +54,10 @@ var (
|
||||||
},
|
},
|
||||||
Action: runRestart,
|
Action: runRestart,
|
||||||
}
|
}
|
||||||
subcmdReloadTemplates = &cli.Command{
|
}
|
||||||
|
|
||||||
|
func subcmdReloadTemplates() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "reload-templates",
|
Name: "reload-templates",
|
||||||
Usage: "Reload template files in the running process",
|
Usage: "Reload template files in the running process",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
@ -57,7 +67,10 @@ var (
|
||||||
},
|
},
|
||||||
Action: runReloadTemplates,
|
Action: runReloadTemplates,
|
||||||
}
|
}
|
||||||
subcmdFlushQueues = &cli.Command{
|
}
|
||||||
|
|
||||||
|
func subcmdFlushQueues() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "flush-queues",
|
Name: "flush-queues",
|
||||||
Usage: "Flush queues in the running process",
|
Usage: "Flush queues in the running process",
|
||||||
Action: runFlushQueues,
|
Action: runFlushQueues,
|
||||||
|
@ -76,7 +89,10 @@ var (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
subCmdProcesses = &cli.Command{
|
}
|
||||||
|
|
||||||
|
func subCmdProcesses() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "processes",
|
Name: "processes",
|
||||||
Usage: "Display running processes within the current process",
|
Usage: "Display running processes within the current process",
|
||||||
Action: runProcesses,
|
Action: runProcesses,
|
||||||
|
@ -106,49 +122,49 @@ var (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
func runShutdown(c *cli.Context) error {
|
func runShutdown(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"), false)
|
||||||
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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"), false)
|
||||||
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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"), false)
|
||||||
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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"), false)
|
||||||
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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"), false)
|
||||||
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,18 +4,19 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"forgejo.org/modules/log"
|
||||||
"code.gitea.io/gitea/modules/private"
|
"forgejo.org/modules/private"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func defaultLoggingFlags() []cli.Flag {
|
||||||
defaultLoggingFlags = []cli.Flag{
|
return []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "logger",
|
Name: "logger",
|
||||||
Usage: `Logger name - will default to "default"`,
|
Usage: `Logger name - will default to "default"`,
|
||||||
|
@ -56,11 +57,13 @@ var (
|
||||||
Name: "debug",
|
Name: "debug",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
subcmdLogging = &cli.Command{
|
func subcmdLogging() *cli.Command {
|
||||||
|
return &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 (Forgejo will buffer logs up to a certain point and will drop them after that point)",
|
Usage: "Pause logging (Forgejo will buffer logs up to a certain point and will drop them after that point)",
|
||||||
|
@ -104,11 +107,11 @@ 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",
|
||||||
Flags: append(defaultLoggingFlags, []cli.Flag{
|
Flags: append(defaultLoggingFlags(), []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "filename",
|
Name: "filename",
|
||||||
Aliases: []string{"f"},
|
Aliases: []string{"f"},
|
||||||
|
@ -152,7 +155,7 @@ var (
|
||||||
}, {
|
}, {
|
||||||
Name: "conn",
|
Name: "conn",
|
||||||
Usage: "Add a net conn logger",
|
Usage: "Add a net conn logger",
|
||||||
Flags: append(defaultLoggingFlags, []cli.Flag{
|
Flags: append(defaultLoggingFlags(), []cli.Flag{
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "reconnect-on-message",
|
Name: "reconnect-on-message",
|
||||||
Aliases: []string{"R"},
|
Aliases: []string{"R"},
|
||||||
|
@ -193,13 +196,13 @@ var (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
func runRemoveLogger(c *cli.Context) error {
|
func runRemoveLogger(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"), false)
|
||||||
logger := c.String("logger")
|
logger := c.String("logger")
|
||||||
if len(logger) == 0 {
|
if len(logger) == 0 {
|
||||||
logger = log.DEFAULT
|
logger = log.DEFAULT
|
||||||
|
@ -210,11 +213,11 @@ 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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"), false)
|
||||||
vals := map[string]any{}
|
vals := map[string]any{}
|
||||||
mode := "conn"
|
mode := "conn"
|
||||||
vals["net"] = "tcp"
|
vals["net"] = "tcp"
|
||||||
|
@ -237,14 +240,14 @@ 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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"), false)
|
||||||
vals := map[string]any{}
|
vals := map[string]any{}
|
||||||
mode := "file"
|
mode := "file"
|
||||||
if c.IsSet("filename") {
|
if c.IsSet("filename") {
|
||||||
|
@ -270,10 +273,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,47 +303,47 @@ 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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"), false)
|
||||||
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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"), false)
|
||||||
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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"), false)
|
||||||
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()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"), false)
|
||||||
|
|
||||||
extra := private.SetLogSQL(ctx, !c.Bool("off"))
|
extra := private.SetLogSQL(ctx, !c.Bool("off"))
|
||||||
return handleCliResponseExtra(extra)
|
return handleCliResponseExtra(extra)
|
||||||
|
|
|
@ -6,24 +6,26 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"forgejo.org/models/db"
|
||||||
"code.gitea.io/gitea/models/migrations"
|
"forgejo.org/models/migrations"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"forgejo.org/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
|
|
||||||
"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{
|
func cmdMigrate() *cli.Command {
|
||||||
Name: "migrate",
|
return &cli.Command{
|
||||||
Usage: "Migrate the database",
|
Name: "migrate",
|
||||||
Description: "This is a command for migrating the database, so that you can run 'forgejo admin user create' before starting the server.",
|
Usage: "Migrate the database",
|
||||||
Action: runMigrate,
|
Description: "This is a command for migrating the database, so that you can run 'forgejo admin user create' before starting the server.",
|
||||||
|
Action: runMigrate,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runMigrate(ctx *cli.Context) error {
|
func runMigrate(stdCtx context.Context, ctx *cli.Command) error {
|
||||||
stdCtx, cancel := installSignals()
|
stdCtx, cancel := installSignals(stdCtx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := initDB(stdCtx); err != nil {
|
if err := initDB(stdCtx); err != nil {
|
||||||
|
@ -36,7 +38,13 @@ 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(), func(dbEngine db.Engine) error {
|
||||||
|
masterEngine, err := db.GetMasterEngine(dbEngine)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return migrations.Migrate(masterEngine)
|
||||||
|
}); err != nil {
|
||||||
log.Fatal("Failed to initialize ORM engine: %v", err)
|
log.Fatal("Failed to initialize ORM engine: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,90 +10,93 @@ import (
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
actions_model "forgejo.org/models/actions"
|
||||||
"code.gitea.io/gitea/models/db"
|
"forgejo.org/models/db"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "forgejo.org/models/git"
|
||||||
"code.gitea.io/gitea/models/migrations"
|
"forgejo.org/models/migrations"
|
||||||
packages_model "code.gitea.io/gitea/models/packages"
|
packages_model "forgejo.org/models/packages"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "forgejo.org/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "forgejo.org/models/user"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"forgejo.org/modules/log"
|
||||||
packages_module "code.gitea.io/gitea/modules/packages"
|
packages_module "forgejo.org/modules/packages"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"forgejo.org/modules/storage"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdMigrateStorage represents the available migrate storage sub-command.
|
// CmdMigrateStorage represents the available migrate storage sub-command.
|
||||||
var CmdMigrateStorage = &cli.Command{
|
func cmdMigrateStorage() *cli.Command {
|
||||||
Name: "migrate-storage",
|
return &cli.Command{
|
||||||
Usage: "Migrate the storage",
|
Name: "migrate-storage",
|
||||||
Description: "Copies stored files from storage configured in app.ini to parameter-configured storage",
|
Usage: "Migrate the storage",
|
||||||
Action: runMigrateStorage,
|
Description: "Copies stored files from storage configured in app.ini to parameter-configured storage",
|
||||||
Flags: []cli.Flag{
|
Action: runMigrateStorage,
|
||||||
&cli.StringFlag{
|
Flags: []cli.Flag{
|
||||||
Name: "type",
|
&cli.StringFlag{
|
||||||
Aliases: []string{"t"},
|
Name: "type",
|
||||||
Value: "",
|
Aliases: []string{"t"},
|
||||||
Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log', 'actions-artifacts'",
|
Value: "",
|
||||||
|
Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log', 'actions-artifacts'",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "storage",
|
||||||
|
Aliases: []string{"s"},
|
||||||
|
Value: "",
|
||||||
|
Usage: "New storage type: local (default) or minio",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "path",
|
||||||
|
Aliases: []string{"p"},
|
||||||
|
Value: "",
|
||||||
|
Usage: "New storage placement if store is local (leave blank for default)",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "minio-endpoint",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Minio storage endpoint",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "minio-access-key-id",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Minio storage accessKeyID",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "minio-secret-access-key",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Minio storage secretAccessKey",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "minio-bucket",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Minio storage bucket",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "minio-location",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Minio storage location to create bucket",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "minio-base-path",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Minio storage base path on the bucket",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "minio-use-ssl",
|
||||||
|
Usage: "Enable SSL for minio",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "minio-insecure-skip-verify",
|
||||||
|
Usage: "Skip SSL verification",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "minio-checksum-algorithm",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Minio checksum algorithm (default/md5)",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
}
|
||||||
Name: "storage",
|
|
||||||
Aliases: []string{"s"},
|
|
||||||
Value: "",
|
|
||||||
Usage: "New storage type: local (default) or minio",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "path",
|
|
||||||
Aliases: []string{"p"},
|
|
||||||
Value: "",
|
|
||||||
Usage: "New storage placement if store is local (leave blank for default)",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "minio-endpoint",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Minio storage endpoint",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "minio-access-key-id",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Minio storage accessKeyID",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "minio-secret-access-key",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Minio storage secretAccessKey",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "minio-bucket",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Minio storage bucket",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "minio-location",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Minio storage location to create bucket",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "minio-base-path",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Minio storage base path on the bucket",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "minio-use-ssl",
|
|
||||||
Usage: "Enable SSL for minio",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "minio-insecure-skip-verify",
|
|
||||||
Usage: "Skip SSL verification",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "minio-checksum-algorithm",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Minio checksum algorithm (default/md5)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func migrateAttachments(ctx context.Context, dstStorage storage.ObjectStorage) error {
|
func migrateAttachments(ctx context.Context, dstStorage storage.ObjectStorage) error {
|
||||||
|
@ -181,8 +184,8 @@ func migrateActionsArtifacts(ctx context.Context, dstStorage storage.ObjectStora
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func runMigrateStorage(ctx *cli.Context) error {
|
func runMigrateStorage(stdCtx context.Context, ctx *cli.Command) error {
|
||||||
stdCtx, cancel := installSignals()
|
stdCtx, cancel := installSignals(stdCtx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := initDB(stdCtx); err != nil {
|
if err := initDB(stdCtx); err != nil {
|
||||||
|
@ -195,7 +198,9 @@ 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(), func(e db.Engine) error {
|
||||||
|
return migrations.Migrate(e.(*xorm.Engine))
|
||||||
|
}); err != nil {
|
||||||
log.Fatal("Failed to initialize ORM engine: %v", err)
|
log.Fatal("Failed to initialize ORM engine: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,22 +4,21 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/actions"
|
"forgejo.org/models/actions"
|
||||||
"code.gitea.io/gitea/models/db"
|
"forgejo.org/models/db"
|
||||||
"code.gitea.io/gitea/models/packages"
|
"forgejo.org/models/packages"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"forgejo.org/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "forgejo.org/models/user"
|
||||||
packages_module "code.gitea.io/gitea/modules/packages"
|
packages_module "forgejo.org/modules/packages"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"forgejo.org/modules/storage"
|
||||||
"code.gitea.io/gitea/modules/test"
|
"forgejo.org/modules/test"
|
||||||
packages_service "code.gitea.io/gitea/services/packages"
|
packages_service "forgejo.org/services/packages"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -31,7 +30,7 @@ func createLocalStorage(t *testing.T) (storage.ObjectStorage, string) {
|
||||||
p := t.TempDir()
|
p := t.TempDir()
|
||||||
|
|
||||||
storage, err := storage.NewLocalStorage(
|
storage, err := storage.NewLocalStorage(
|
||||||
context.Background(),
|
t.Context(),
|
||||||
&setting.Storage{
|
&setting.Storage{
|
||||||
Path: p,
|
Path: p,
|
||||||
})
|
})
|
||||||
|
@ -72,7 +71,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()
|
||||||
|
|
||||||
dstStorage, p := createLocalStorage(t)
|
dstStorage, p := createLocalStorage(t)
|
||||||
|
|
||||||
|
@ -82,8 +81,8 @@ func TestMigratePackages(t *testing.T) {
|
||||||
entries, err := os.ReadDir(p)
|
entries, err := os.ReadDir(p)
|
||||||
require.NoError(t, err)
|
require.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())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMigrateActionsArtifacts(t *testing.T) {
|
func TestMigrateActionsArtifacts(t *testing.T) {
|
||||||
|
|
|
@ -4,52 +4,55 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/private"
|
"forgejo.org/modules/private"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/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.
|
||||||
var CmdRestoreRepository = &cli.Command{
|
func cmdRestoreRepository() *cli.Command {
|
||||||
Name: "restore-repo",
|
return &cli.Command{
|
||||||
Usage: "Restore the repository from disk",
|
Name: "restore-repo",
|
||||||
Description: "This is a command for restoring the repository data.",
|
Usage: "Restore the repository from disk",
|
||||||
Action: runRestoreRepository,
|
Description: "This is a command for restoring the repository data.",
|
||||||
Flags: []cli.Flag{
|
Action: runRestoreRepository,
|
||||||
&cli.StringFlag{
|
Flags: []cli.Flag{
|
||||||
Name: "repo_dir",
|
&cli.StringFlag{
|
||||||
Aliases: []string{"r"},
|
Name: "repo_dir",
|
||||||
Value: "./data",
|
Aliases: []string{"r"},
|
||||||
Usage: "Repository dir path to restore from",
|
Value: "./data",
|
||||||
},
|
Usage: "Repository dir path to restore from",
|
||||||
&cli.StringFlag{
|
},
|
||||||
Name: "owner_name",
|
&cli.StringFlag{
|
||||||
Value: "",
|
Name: "owner_name",
|
||||||
Usage: "Restore destination owner name",
|
Value: "",
|
||||||
},
|
Usage: "Restore destination owner name",
|
||||||
&cli.StringFlag{
|
},
|
||||||
Name: "repo_name",
|
&cli.StringFlag{
|
||||||
Value: "",
|
Name: "repo_name",
|
||||||
Usage: "Restore destination repository name",
|
Value: "",
|
||||||
},
|
Usage: "Restore destination repository name",
|
||||||
&cli.StringFlag{
|
},
|
||||||
Name: "units",
|
&cli.StringFlag{
|
||||||
Value: "",
|
Name: "units",
|
||||||
Usage: `Which items will be restored, one or more units should be separated as comma.
|
Value: "",
|
||||||
|
Usage: `Which items will be restored, one or more units should be separated as comma.
|
||||||
wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`,
|
wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`,
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "validation",
|
||||||
|
Usage: "Sanity check the content of the files before trying to load them",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
}
|
||||||
Name: "validation",
|
|
||||||
Usage: "Sanity check the content of the files before trying to load them",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRestoreRepository(c *cli.Context) error {
|
func runRestoreRepository(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
setting.MustInstalled()
|
setting.MustInstalled()
|
||||||
|
|
86
cmd/serv.go
86
cmd/serv.go
|
@ -18,22 +18,22 @@ import (
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
asymkey_model "forgejo.org/models/asymkey"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "forgejo.org/models/git"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"forgejo.org/models/perm"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"forgejo.org/modules/git"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"forgejo.org/modules/json"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"forgejo.org/modules/log"
|
||||||
"code.gitea.io/gitea/modules/pprof"
|
"forgejo.org/modules/pprof"
|
||||||
"code.gitea.io/gitea/modules/private"
|
"forgejo.org/modules/private"
|
||||||
"code.gitea.io/gitea/modules/process"
|
"forgejo.org/modules/process"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
repo_module "forgejo.org/modules/repository"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
"code.gitea.io/gitea/services/lfs"
|
"forgejo.org/services/lfs"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"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 (
|
const (
|
||||||
|
@ -41,35 +41,40 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdServ represents the available serv sub-command.
|
// CmdServ represents the available serv sub-command.
|
||||||
var CmdServ = &cli.Command{
|
func cmdServ() *cli.Command {
|
||||||
Name: "serv",
|
return &cli.Command{
|
||||||
Usage: "(internal) Should only be called by SSH shell",
|
Name: "serv",
|
||||||
Description: "Serv provides access auth for repositories",
|
Usage: "(internal) Should only be called by SSH shell",
|
||||||
Before: PrepareConsoleLoggerLevel(log.FATAL),
|
Description: "Serv provides access auth for repositories",
|
||||||
Action: runServ,
|
Before: PrepareConsoleLoggerLevel(log.FATAL),
|
||||||
Flags: []cli.Flag{
|
Action: runServ,
|
||||||
&cli.BoolFlag{
|
Flags: []cli.Flag{
|
||||||
Name: "enable-pprof",
|
&cli.BoolFlag{
|
||||||
|
Name: "enable-pprof",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "debug",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
}
|
||||||
Name: "debug",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setup(ctx context.Context, debug bool) {
|
func setup(ctx context.Context, debug, gitNeeded bool) {
|
||||||
if debug {
|
if debug {
|
||||||
setupConsoleLogger(log.TRACE, false, os.Stderr)
|
setupConsoleLogger(log.TRACE, false, os.Stderr)
|
||||||
} else {
|
} else {
|
||||||
setupConsoleLogger(log.FATAL, false, os.Stderr)
|
setupConsoleLogger(log.FATAL, false, os.Stderr)
|
||||||
}
|
}
|
||||||
setting.MustInstalled()
|
setting.MustInstalled()
|
||||||
|
// Sanity check to ensure path is not relative, see: https://github.com/go-gitea/gitea/pull/19317
|
||||||
if _, err := os.Stat(setting.RepoRootPath); err != nil {
|
if _, err := os.Stat(setting.RepoRootPath); err != nil {
|
||||||
_ = 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 gitNeeded {
|
||||||
_ = fail(ctx, "Failed to init git", "Failed to init git, err: %v", err)
|
if err := git.InitSimple(context.Background()); err != nil {
|
||||||
|
_ = fail(ctx, "Failed to init git", "Failed to init git, err: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,12 +133,12 @@ func handleCliResponseExtra(extra private.ResponseExtra) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runServ(c *cli.Context) error {
|
func runServ(ctx context.Context, c *cli.Command) error {
|
||||||
ctx, cancel := installSignals()
|
ctx, cancel := installSignals(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
// FIXME: This needs to internationalised
|
// FIXME: This needs to internationalised
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"), true)
|
||||||
|
|
||||||
if setting.SSH.Disabled {
|
if setting.SSH.Disabled {
|
||||||
fmt.Println("Forgejo: SSH has been disabled")
|
fmt.Println("Forgejo: SSH has been disabled")
|
||||||
|
@ -188,12 +193,10 @@ func runServ(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(words) < 2 {
|
if len(words) < 2 {
|
||||||
if git.CheckGitVersionAtLeast("2.29") == nil {
|
// for AGit Flow
|
||||||
// for AGit Flow
|
if cmd == "ssh_info" {
|
||||||
if cmd == "ssh_info" {
|
fmt.Print(`{"type":"agit","version":1}`)
|
||||||
fmt.Print(`{"type":"gitea","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)
|
||||||
}
|
}
|
||||||
|
@ -253,11 +256,12 @@ func runServ(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if verb == lfsAuthenticateVerb {
|
if verb == lfsAuthenticateVerb {
|
||||||
if lfsVerb == "upload" {
|
switch lfsVerb {
|
||||||
|
case "upload":
|
||||||
requestedMode = perm.AccessModeWrite
|
requestedMode = perm.AccessModeWrite
|
||||||
} else if lfsVerb == "download" {
|
case "download":
|
||||||
requestedMode = perm.AccessModeRead
|
requestedMode = perm.AccessModeRead
|
||||||
} else {
|
default:
|
||||||
return fail(ctx, "Unknown LFS verb", "Unknown lfs verb %s", lfsVerb)
|
return fail(ctx, "Unknown LFS verb", "Unknown lfs verb %s", lfsVerb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
116
cmd/web.go
116
cmd/web.go
|
@ -12,61 +12,64 @@ 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"
|
"forgejo.org/modules/container"
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"forgejo.org/modules/graceful"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"forgejo.org/modules/log"
|
||||||
"code.gitea.io/gitea/modules/process"
|
"forgejo.org/modules/process"
|
||||||
"code.gitea.io/gitea/modules/public"
|
"forgejo.org/modules/public"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
"code.gitea.io/gitea/routers"
|
"forgejo.org/routers"
|
||||||
"code.gitea.io/gitea/routers/install"
|
"forgejo.org/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
|
||||||
var PIDFile = "/run/gitea.pid"
|
var PIDFile = "/run/gitea.pid"
|
||||||
|
|
||||||
// CmdWeb represents the available web sub-command.
|
// CmdWeb represents the available web sub-command.
|
||||||
var CmdWeb = &cli.Command{
|
func cmdWeb() *cli.Command {
|
||||||
Name: "web",
|
return &cli.Command{
|
||||||
Usage: "Start the Forgejo web server",
|
Name: "web",
|
||||||
Description: `The Forgejo web server is the only thing you need to run,
|
Usage: "Start the Forgejo web server",
|
||||||
|
Description: `The Forgejo web server is the only thing you need to run,
|
||||||
and it takes care of all the other things for you`,
|
and it takes care of all the other things for you`,
|
||||||
Before: PrepareConsoleLoggerLevel(log.INFO),
|
Before: PrepareConsoleLoggerLevel(log.INFO),
|
||||||
Action: runWeb,
|
Action: runWeb,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "port",
|
Name: "port",
|
||||||
Aliases: []string{"p"},
|
Aliases: []string{"p"},
|
||||||
Value: "3000",
|
Value: "3000",
|
||||||
Usage: "Temporary port number to prevent conflict",
|
Usage: "Temporary port number to prevent conflict",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "install-port",
|
||||||
|
Value: "3000",
|
||||||
|
Usage: "Temporary port number to run the install page on to prevent conflict",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "pid",
|
||||||
|
Aliases: []string{"P"},
|
||||||
|
Value: PIDFile,
|
||||||
|
Usage: "Custom pid file path",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "quiet",
|
||||||
|
Aliases: []string{"q"},
|
||||||
|
Usage: "Only display Fatal logging errors until logging is set-up",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "verbose",
|
||||||
|
Usage: "Set initial logging to TRACE level until logging is properly set-up",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
}
|
||||||
Name: "install-port",
|
|
||||||
Value: "3000",
|
|
||||||
Usage: "Temporary port number to run the install page on to prevent conflict",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "pid",
|
|
||||||
Aliases: []string{"P"},
|
|
||||||
Value: PIDFile,
|
|
||||||
Usage: "Custom pid file path",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "quiet",
|
|
||||||
Aliases: []string{"q"},
|
|
||||||
Usage: "Only display Fatal logging errors until logging is set-up",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "verbose",
|
|
||||||
Usage: "Set initial logging to TRACE level until logging is properly set-up",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runHTTPRedirector() {
|
func runHTTPRedirector() {
|
||||||
|
@ -115,9 +118,19 @@ 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(_ context.Context, ctx *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())
|
||||||
|
@ -150,7 +163,7 @@ func serveInstall(ctx *cli.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveInstalled(ctx *cli.Context) error {
|
func serveInstalled(_ context.Context, ctx *cli.Command) error {
|
||||||
setting.InitCfgProvider(setting.CustomConf)
|
setting.InitCfgProvider(setting.CustomConf)
|
||||||
setting.LoadCommonSettings()
|
setting.LoadCommonSettings()
|
||||||
setting.MustInstalled()
|
setting.MustInstalled()
|
||||||
|
@ -184,12 +197,9 @@ func serveInstalled(ctx *cli.Context) error {
|
||||||
publicFilesSet.Remove(".well-known")
|
publicFilesSet.Remove(".well-known")
|
||||||
publicFilesSet.Remove("assets")
|
publicFilesSet.Remove("assets")
|
||||||
publicFilesSet.Remove("robots.txt")
|
publicFilesSet.Remove("robots.txt")
|
||||||
for _, fn := range publicFilesSet.Values() {
|
for fn := range publicFilesSet.Seq() {
|
||||||
log.Error("Found legacy public asset %q in CustomPath. Please move it to %s/public/assets/%s", fn, setting.CustomPath, fn)
|
log.Error("Found legacy public asset %q in CustomPath. Please move it to %s/public/assets/%s", fn, setting.CustomPath, fn)
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(filepath.Join(setting.CustomPath, "robots.txt")); err == nil {
|
|
||||||
log.Error(`Found legacy public asset "robots.txt" in CustomPath. Please move it to %s/public/robots.txt`, setting.CustomPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
routers.InitWebInstalled(graceful.GetManager().HammerContext())
|
routers.InitWebInstalled(graceful.GetManager().HammerContext())
|
||||||
|
|
||||||
|
@ -225,7 +235,7 @@ func servePprof() {
|
||||||
finished()
|
finished()
|
||||||
}
|
}
|
||||||
|
|
||||||
func runWeb(ctx *cli.Context) error {
|
func runWeb(ctx context.Context, cli *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))
|
||||||
|
@ -243,12 +253,12 @@ func runWeb(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set pid file setting
|
// Set pid file setting
|
||||||
if ctx.IsSet("pid") {
|
if cli.IsSet("pid") {
|
||||||
createPIDFile(ctx.String("pid"))
|
createPIDFile(cli.String("pid"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !setting.InstallLock {
|
if !setting.InstallLock {
|
||||||
if err := serveInstall(ctx); err != nil {
|
if err := serveInstall(ctx, cli); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -259,7 +269,7 @@ func runWeb(ctx *cli.Context) error {
|
||||||
go servePprof()
|
go servePprof()
|
||||||
}
|
}
|
||||||
|
|
||||||
return serveInstalled(ctx)
|
return serveInstalled(ctx, cli)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setPort(port string) error {
|
func setPort(port string) error {
|
||||||
|
|
|
@ -12,10 +12,10 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"forgejo.org/modules/graceful"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"forgejo.org/modules/log"
|
||||||
"code.gitea.io/gitea/modules/process"
|
"forgejo.org/modules/process"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
)
|
)
|
||||||
|
@ -54,8 +54,8 @@ func runACME(listenAddr string, m http.Handler) error {
|
||||||
altTLSALPNPort = p
|
altTLSALPNPort = p
|
||||||
}
|
}
|
||||||
|
|
||||||
magic := certmagic.NewDefault()
|
certmagic.Default.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
|
||||||
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,7 +65,8 @@ 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{
|
|
||||||
|
certmagic.DefaultACME = certmagic.ACMEIssuer{
|
||||||
CA: setting.AcmeURL,
|
CA: setting.AcmeURL,
|
||||||
TrustedRoots: certPool,
|
TrustedRoots: certPool,
|
||||||
Email: setting.AcmeEmail,
|
Email: setting.AcmeEmail,
|
||||||
|
@ -75,7 +76,11 @@ 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}
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,9 @@ import (
|
||||||
"net/http/fcgi"
|
"net/http/fcgi"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"forgejo.org/modules/graceful"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"forgejo.org/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
func runHTTP(network, listenAddr, name string, m http.Handler, useProxyProtocol bool) error {
|
func runHTTP(network, listenAddr, name string, m http.Handler, useProxyProtocol bool) error {
|
||||||
|
|
|
@ -9,9 +9,9 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"forgejo.org/modules/graceful"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"forgejo.org/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
|
|
||||||
"github.com/klauspost/cpuid/v2"
|
"github.com/klauspost/cpuid/v2"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
#! /bin/bash
|
|
||||||
# Heavily inspired by https://github.com/urfave/cli
|
# Heavily inspired by https://github.com/urfave/cli
|
||||||
|
|
||||||
_cli_bash_autocomplete() {
|
_cli_bash_autocomplete() {
|
||||||
|
@ -7,9 +6,9 @@ _cli_bash_autocomplete() {
|
||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
if [[ "$cur" == "-"* ]]; then
|
if [[ "$cur" == "-"* ]]; then
|
||||||
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion )
|
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-shell-completion )
|
||||||
else
|
else
|
||||||
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
|
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-shell-completion )
|
||||||
fi
|
fi
|
||||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||||
return 0
|
return 0
|
||||||
|
|
|
@ -9,9 +9,9 @@ _cli_zsh_autocomplete() {
|
||||||
local cur
|
local cur
|
||||||
cur=${words[-1]}
|
cur=${words[-1]}
|
||||||
if [[ "$cur" == "-"* ]]; then
|
if [[ "$cur" == "-"* ]]; then
|
||||||
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}")
|
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-shell-completion)}")
|
||||||
else
|
else
|
||||||
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}")
|
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-shell-completion)}")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${opts[1]}" != "" ]]; then
|
if [[ "${opts[1]}" != "" ]]; then
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
`backport`
|
|
||||||
==========
|
|
||||||
|
|
||||||
`backport` is a command to help create backports of PRs. It backports a
|
|
||||||
provided PR from main on to a released version.
|
|
||||||
|
|
||||||
It will create a backport branch, cherry-pick the PR's merge commit, adjust
|
|
||||||
the commit message and then push this back up to your fork's remote.
|
|
||||||
|
|
||||||
The default version will read from `docs/config.yml`. You can override this
|
|
||||||
using the option `--version`.
|
|
||||||
|
|
||||||
The upstream branches will be fetched, using the remote `origin`. This can
|
|
||||||
be overridden using `--upstream`, and fetching can be avoided using
|
|
||||||
`--no-fetch`.
|
|
||||||
|
|
||||||
By default the branch created will be called `backport-$PR-$VERSION`. You
|
|
||||||
can override this using the option `--backport-branch`. This branch will
|
|
||||||
be created from `--release-branch` which is `release/$(VERSION)`
|
|
||||||
by default and will be pulled from `$(UPSTREAM)`.
|
|
||||||
|
|
||||||
The merge-commit as determined by the github API will be used as the SHA to
|
|
||||||
cherry-pick. You can override this using `--cherry-pick`.
|
|
||||||
|
|
||||||
The commit message will be amended to add the `Backport` header.
|
|
||||||
`--no-amend-message` can be set to stop this from happening.
|
|
||||||
|
|
||||||
If cherry-pick is successful the backported branch will be pushed up to your
|
|
||||||
fork using your remote. These will be determined using `git remote -v`. You
|
|
||||||
can set your fork name using `--fork-user` and your remote name using
|
|
||||||
`--remote`. You can avoid pushing using `--no-push`.
|
|
||||||
|
|
||||||
If the push is successful, `xdg-open` will be called to open a backport url.
|
|
||||||
You can stop this using `--no-xdg-open`.
|
|
||||||
|
|
||||||
Installation
|
|
||||||
============
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go install contrib/backport/backport.go
|
|
||||||
```
|
|
|
@ -1,474 +0,0 @@
|
||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
//nolint:forbidigo
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"os/signal"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/google/go-github/v64/github"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
const defaultVersion = "v1.18" // to backport to
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
app := cli.NewApp()
|
|
||||||
app.Name = "backport"
|
|
||||||
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.ArgsUsage = "<PR-to-backport>"
|
|
||||||
|
|
||||||
app.Flags = []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "version",
|
|
||||||
Usage: "Version branch to backport on to",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "upstream",
|
|
||||||
Value: "origin",
|
|
||||||
Usage: "Upstream remote for the Gitea upstream",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "release-branch",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Release branch to backport on. Will default to release/<version>",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "cherry-pick",
|
|
||||||
Usage: "SHA to cherry-pick as backport",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "backport-branch",
|
|
||||||
Usage: "Backport branch to backport on to (default: backport-<pr>-<version>",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "remote",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Remote for your fork of the Gitea upstream",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "fork-user",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Forked user name on Github",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "no-fetch",
|
|
||||||
Usage: "Set this flag to prevent fetch of remote branches",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "no-amend-message",
|
|
||||||
Usage: "Set this flag to prevent automatic amendment of the commit message",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "no-push",
|
|
||||||
Usage: "Set this flag to prevent pushing the backport up to your fork",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "no-xdg-open",
|
|
||||||
Usage: "Set this flag to not use xdg-open to open the PR URL",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "continue",
|
|
||||||
Usage: "Set this flag to continue from a git cherry-pick that has broken",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cli.AppHelpTemplate = `NAME:
|
|
||||||
{{.Name}} - {{.Usage}}
|
|
||||||
USAGE:
|
|
||||||
{{.HelpName}} {{if .VisibleFlags}}[options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
|
|
||||||
{{if len .Authors}}
|
|
||||||
AUTHOR:
|
|
||||||
{{range .Authors}}{{ . }}{{end}}
|
|
||||||
{{end}}{{if .Commands}}
|
|
||||||
OPTIONS:
|
|
||||||
{{range .VisibleFlags}}{{.}}
|
|
||||||
{{end}}{{end}}
|
|
||||||
`
|
|
||||||
|
|
||||||
app.Action = runBackport
|
|
||||||
|
|
||||||
if err := app.Run(os.Args); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Unable to backport: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runBackport(c *cli.Context) error {
|
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
continuing := c.Bool("continue")
|
|
||||||
|
|
||||||
var pr string
|
|
||||||
|
|
||||||
version := c.String("version")
|
|
||||||
if version == "" && continuing {
|
|
||||||
// determine version from current branch name
|
|
||||||
var err error
|
|
||||||
pr, version, err = readCurrentBranch(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if version == "" {
|
|
||||||
version = readVersion()
|
|
||||||
}
|
|
||||||
if version == "" {
|
|
||||||
version = defaultVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
upstream := c.String("upstream")
|
|
||||||
if upstream == "" {
|
|
||||||
upstream = "origin"
|
|
||||||
}
|
|
||||||
|
|
||||||
forkUser := c.String("fork-user")
|
|
||||||
remote := c.String("remote")
|
|
||||||
if remote == "" && !c.Bool("--no-push") {
|
|
||||||
var err error
|
|
||||||
remote, forkUser, err = determineRemote(ctx, forkUser)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
upstreamReleaseBranch := c.String("release-branch")
|
|
||||||
if upstreamReleaseBranch == "" {
|
|
||||||
upstreamReleaseBranch = path.Join("release", version)
|
|
||||||
}
|
|
||||||
|
|
||||||
localReleaseBranch := path.Join(upstream, upstreamReleaseBranch)
|
|
||||||
|
|
||||||
args := c.Args().Slice()
|
|
||||||
if len(args) == 0 && pr == "" {
|
|
||||||
return fmt.Errorf("no PR number provided\nProvide a PR number to backport")
|
|
||||||
} else if len(args) != 1 && pr == "" {
|
|
||||||
return fmt.Errorf("multiple PRs provided %v\nOnly a single PR can be backported at a time", args)
|
|
||||||
}
|
|
||||||
if pr == "" {
|
|
||||||
pr = args[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
backportBranch := c.String("backport-branch")
|
|
||||||
if backportBranch == "" {
|
|
||||||
backportBranch = "backport-" + pr + "-" + version
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("* Backporting %s to %s as %s\n", pr, localReleaseBranch, backportBranch)
|
|
||||||
|
|
||||||
sha := c.String("cherry-pick")
|
|
||||||
if sha == "" {
|
|
||||||
var err error
|
|
||||||
sha, err = determineSHAforPR(ctx, pr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if sha == "" {
|
|
||||||
return fmt.Errorf("unable to determine sha for cherry-pick of %s", pr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.Bool("no-fetch") {
|
|
||||||
if err := fetchRemoteAndMain(ctx, upstream, upstreamReleaseBranch); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !continuing {
|
|
||||||
if err := checkoutBackportBranch(ctx, backportBranch, localReleaseBranch); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cherrypick(ctx, sha); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.Bool("no-amend-message") {
|
|
||||||
if err := amendCommit(ctx, pr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.Bool("no-push") {
|
|
||||||
url := "https://github.com/go-gitea/gitea/compare/" + upstreamReleaseBranch + "..." + forkUser + ":" + backportBranch
|
|
||||||
|
|
||||||
if err := gitPushUp(ctx, remote, backportBranch); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.Bool("no-xdg-open") {
|
|
||||||
if err := xdgOpen(ctx, url); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Printf("* Navigate to %s to open PR\n", url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func xdgOpen(ctx context.Context, url string) error {
|
|
||||||
fmt.Printf("* `xdg-open %s`\n", url)
|
|
||||||
out, err := exec.CommandContext(ctx, "xdg-open", url).Output()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "%s", string(out))
|
|
||||||
return fmt.Errorf("unable to xdg-open to %s: %w", url, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func gitPushUp(ctx context.Context, remote, backportBranch string) error {
|
|
||||||
fmt.Printf("* `git push -u %s %s`\n", remote, backportBranch)
|
|
||||||
out, err := exec.CommandContext(ctx, "git", "push", "-u", remote, backportBranch).Output()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "%s", string(out))
|
|
||||||
return fmt.Errorf("unable to push up to %s: %w", remote, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func amendCommit(ctx context.Context, pr string) error {
|
|
||||||
fmt.Printf("* Amending commit to prepend `Backport #%s` to body\n", pr)
|
|
||||||
out, err := exec.CommandContext(ctx, "git", "log", "-1", "--pretty=format:%B").Output()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "%s", string(out))
|
|
||||||
return fmt.Errorf("unable to get last log message: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := strings.SplitN(string(out), "\n", 2)
|
|
||||||
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return fmt.Errorf("unable to interpret log message:\n%s", string(out))
|
|
||||||
}
|
|
||||||
subject, body := parts[0], parts[1]
|
|
||||||
if !strings.HasSuffix(subject, " (#"+pr+")") {
|
|
||||||
subject = subject + " (#" + pr + ")"
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err = exec.CommandContext(ctx, "git", "commit", "--amend", "-m", subject+"\n\nBackport #"+pr+"\n"+body).Output()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "%s", string(out))
|
|
||||||
return fmt.Errorf("unable to amend last log message: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cherrypick(ctx context.Context, sha string) error {
|
|
||||||
// Check if a CHERRY_PICK_HEAD exists
|
|
||||||
if _, err := os.Stat(".git/CHERRY_PICK_HEAD"); err == nil {
|
|
||||||
// Assume that we are in the middle of cherry-pick - continue it
|
|
||||||
fmt.Println("* Attempting git cherry-pick --continue")
|
|
||||||
out, err := exec.CommandContext(ctx, "git", "cherry-pick", "--continue").Output()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "git cherry-pick --continue failed:\n%s\n", string(out))
|
|
||||||
return fmt.Errorf("unable to continue cherry-pick: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("* Attempting git cherry-pick %s\n", sha)
|
|
||||||
out, err := exec.CommandContext(ctx, "git", "cherry-pick", sha).Output()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "git cherry-pick %s failed:\n%s\n", sha, string(out))
|
|
||||||
return fmt.Errorf("git cherry-pick %s failed: %w", sha, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkoutBackportBranch(ctx context.Context, backportBranch, releaseBranch string) error {
|
|
||||||
out, err := exec.CommandContext(ctx, "git", "branch", "--show-current").Output()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to check current branch %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
currentBranch := strings.TrimSpace(string(out))
|
|
||||||
fmt.Printf("* Current branch is %s\n", currentBranch)
|
|
||||||
if currentBranch == backportBranch {
|
|
||||||
fmt.Printf("* Current branch is %s - not checking out\n", currentBranch)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := exec.CommandContext(ctx, "git", "rev-list", "-1", backportBranch).Output(); err == nil {
|
|
||||||
fmt.Printf("* Branch %s already exists. Checking it out...\n", backportBranch)
|
|
||||||
return exec.CommandContext(ctx, "git", "checkout", "-f", backportBranch).Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("* `git checkout -b %s %s`\n", backportBranch, releaseBranch)
|
|
||||||
return exec.CommandContext(ctx, "git", "checkout", "-b", backportBranch, releaseBranch).Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchRemoteAndMain(ctx context.Context, remote, releaseBranch string) error {
|
|
||||||
fmt.Printf("* `git fetch %s main`\n", remote)
|
|
||||||
out, err := exec.CommandContext(ctx, "git", "fetch", remote, "main").Output()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(string(out))
|
|
||||||
return fmt.Errorf("unable to fetch %s from %s: %w", "main", remote, err)
|
|
||||||
}
|
|
||||||
fmt.Println(string(out))
|
|
||||||
|
|
||||||
fmt.Printf("* `git fetch %s %s`\n", remote, releaseBranch)
|
|
||||||
out, err = exec.CommandContext(ctx, "git", "fetch", remote, releaseBranch).Output()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(string(out))
|
|
||||||
return fmt.Errorf("unable to fetch %s from %s: %w", releaseBranch, remote, err)
|
|
||||||
}
|
|
||||||
fmt.Println(string(out))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func determineRemote(ctx context.Context, forkUser string) (string, string, error) {
|
|
||||||
out, err := exec.CommandContext(ctx, "git", "remote", "-v").Output()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Unable to list git remotes:\n%s\n", string(out))
|
|
||||||
return "", "", fmt.Errorf("unable to determine forked remote: %w", err)
|
|
||||||
}
|
|
||||||
lines := strings.Split(string(out), "\n")
|
|
||||||
for _, line := range lines {
|
|
||||||
fields := strings.Split(line, "\t")
|
|
||||||
name, remote := fields[0], fields[1]
|
|
||||||
// only look at pushers
|
|
||||||
if !strings.HasSuffix(remote, " (push)") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// only look at github.com pushes
|
|
||||||
if !strings.Contains(remote, "github.com") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// ignore go-gitea/gitea
|
|
||||||
if strings.Contains(remote, "go-gitea/gitea") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !strings.Contains(remote, forkUser) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(remote, "git@github.com:") {
|
|
||||||
forkUser = strings.TrimPrefix(remote, "git@github.com:")
|
|
||||||
} else if strings.HasPrefix(remote, "https://github.com/") {
|
|
||||||
forkUser = strings.TrimPrefix(remote, "https://github.com/")
|
|
||||||
} else if strings.HasPrefix(remote, "https://www.github.com/") {
|
|
||||||
forkUser = strings.TrimPrefix(remote, "https://www.github.com/")
|
|
||||||
} else if forkUser == "" {
|
|
||||||
return "", "", fmt.Errorf("unable to extract forkUser from remote %s: %s", name, remote)
|
|
||||||
}
|
|
||||||
idx := strings.Index(forkUser, "/")
|
|
||||||
if idx >= 0 {
|
|
||||||
forkUser = forkUser[:idx]
|
|
||||||
}
|
|
||||||
return name, forkUser, nil
|
|
||||||
}
|
|
||||||
return "", "", fmt.Errorf("unable to find appropriate remote in:\n%s", string(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
func readCurrentBranch(ctx context.Context) (pr, version string, err error) {
|
|
||||||
out, err := exec.CommandContext(ctx, "git", "branch", "--show-current").Output()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Unable to read current git branch:\n%s\n", string(out))
|
|
||||||
return "", "", fmt.Errorf("unable to read current git branch: %w", err)
|
|
||||||
}
|
|
||||||
parts := strings.Split(strings.TrimSpace(string(out)), "-")
|
|
||||||
|
|
||||||
if len(parts) != 3 || parts[0] != "backport" {
|
|
||||||
fmt.Fprintf(os.Stderr, "Unable to continue from git branch:\n%s\n", string(out))
|
|
||||||
return "", "", fmt.Errorf("unable to continue from git branch:\n%s", string(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
return parts[1], parts[2], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readVersion() string {
|
|
||||||
bs, err := os.ReadFile("docs/config.yaml")
|
|
||||||
if err != nil {
|
|
||||||
if err == os.ErrNotExist {
|
|
||||||
log.Println("`docs/config.yaml` not present")
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
fmt.Fprintf(os.Stderr, "Unable to read `docs/config.yaml`: %v\n", err)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
type params struct {
|
|
||||||
Version string
|
|
||||||
}
|
|
||||||
type docConfig struct {
|
|
||||||
Params params
|
|
||||||
}
|
|
||||||
dc := &docConfig{}
|
|
||||||
if err := yaml.Unmarshal(bs, dc); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Unable to read `docs/config.yaml`: %v\n", err)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if dc.Params.Version == "" {
|
|
||||||
fmt.Fprintf(os.Stderr, "No version in `docs/config.yaml`")
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
version := dc.Params.Version
|
|
||||||
if version[0] != 'v' {
|
|
||||||
version = "v" + version
|
|
||||||
}
|
|
||||||
|
|
||||||
split := strings.SplitN(version, ".", 3)
|
|
||||||
|
|
||||||
return strings.Join(split[:2], ".")
|
|
||||||
}
|
|
||||||
|
|
||||||
func determineSHAforPR(ctx context.Context, prStr string) (string, error) {
|
|
||||||
prNum, err := strconv.Atoi(prStr)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
client := github.NewClient(http.DefaultClient)
|
|
||||||
|
|
||||||
pr, _, err := client.PullRequests.Get(ctx, "go-gitea", "gitea", prNum)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if pr.Merged == nil || !*pr.Merged {
|
|
||||||
return "", fmt.Errorf("PR #%d is not yet merged - cannot determine sha to backport", prNum)
|
|
||||||
}
|
|
||||||
|
|
||||||
if pr.MergeCommitSHA != nil {
|
|
||||||
return *pr.MergeCommitSHA, 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
|
|
||||||
}
|
|
|
@ -1,45 +1,46 @@
|
||||||
Environment To Ini
|
Environment To Ini
|
||||||
==================
|
==================
|
||||||
|
|
||||||
Multiple docker users have requested that the Gitea docker is changed
|
This tool allows defining Forgejo's entire configuration via environment
|
||||||
to permit arbitrary configuration via environment variables.
|
variables, mostly geared towards usage in Docker.
|
||||||
|
|
||||||
Gitea needs to use an ini file for configuration because the running
|
Forgejo needs to use an INI file for configuration because the running
|
||||||
environment that starts the docker may not be the same as that used
|
environment that starts the container may not be the same as the one used
|
||||||
by the hooks. An ini file also gives a good default and means that
|
by the hooks. An INI file also gives a good default and means that
|
||||||
users do not have to completely provide a full environment.
|
users do not have to provide the entire set of environment variables.
|
||||||
|
|
||||||
With those caveats above, this command provides a generic way of
|
With those caveats above, this command provides a generic way of
|
||||||
converting suitably structured environment variables into any ini
|
converting suitably structured environment variables into any ini
|
||||||
value.
|
value.
|
||||||
|
|
||||||
To use the command is very simple just run it and the default gitea
|
When run, `environment-to-ini` will write the config files based on the
|
||||||
app.ini will be rewritten to take account of the variables provided,
|
environment variables provided.
|
||||||
however there are various options to give slightly different
|
Check with the `-h` flag for several options to alter this behaviour.
|
||||||
behavior and these can be interrogated with the `-h` option.
|
|
||||||
|
|
||||||
The environment variables should be of the form:
|
Environment variables of the form "FORGEJO__SECTION_NAME__KEY_NAME"
|
||||||
|
will be mapped to the ini section "[section_name]" and the key
|
||||||
|
"KEY_NAME" with the value as provided.
|
||||||
|
|
||||||
GITEA__SECTION_NAME__KEY_NAME
|
Environment variables of the form "FORGEJO__SECTION_NAME__KEY_NAME__FILE"
|
||||||
|
will be mapped to the ini section "[section_name]" and the key
|
||||||
Note, SECTION_NAME in the notation above is case-insensitive.
|
"KEY_NAME" with the value loaded from the specified file.
|
||||||
|
|
||||||
Environment variables are usually restricted to a reduced character
|
Environment variables are usually restricted to a reduced character
|
||||||
set "0-9A-Z_" - in order to allow the setting of sections with
|
set "0-9A-Z_" - in order to allow the setting of sections with
|
||||||
characters outside of that set, they should be escaped as following:
|
characters outside of that set, they should be escaped as following:
|
||||||
"_0X2E_" for "." and "_0X2D_" for "-". The entire section and key names
|
"_0X2E_" for ".". The entire section and key names can be escaped as
|
||||||
can be escaped as a UTF8 byte string if necessary. E.g. to configure:
|
a UTF8 byte string if necessary. E.g. to configure:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
...
|
...
|
||||||
[log.console]
|
[log.console]
|
||||||
COLORIZE=false
|
COLORIZE=false
|
||||||
STDERR=true
|
STDERR=true
|
||||||
...
|
...
|
||||||
"""
|
"""
|
||||||
|
|
||||||
You would set the environment variables: "GITEA__LOG_0x2E_CONSOLE__COLORIZE=false"
|
You would set the environment variables: "FORGEJO__LOG_0x2E_CONSOLE__COLORIZE=false"
|
||||||
and "GITEA__LOG_0x2E_CONSOLE__STDERR=false". Other examples can be found
|
and "FORGEJO__LOG_0x2E_CONSOLE__STDERR=false". Other examples can be found
|
||||||
on the configuration cheat sheet.
|
on the configuration cheat sheet.
|
||||||
|
|
||||||
To build locally, run:
|
To build locally, run:
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue