mirror of
https://codeberg.org/forgejo/forgejo
synced 2025-10-19 12:41:12 +02:00
Compare commits
694 commits
Author | SHA1 | Date | |
---|---|---|---|
|
8caea1062c | ||
|
0a7e438e43 | ||
|
6726861e49 | ||
|
c54cfa3af8 | ||
|
3f5a01e42b | ||
|
3f1731a765 | ||
|
c22ec65856 | ||
|
bc0568b3ee | ||
|
e20c674560 | ||
|
5b13c6e024 | ||
|
fe4a2ae7c1 | ||
|
affadc359e | ||
|
8ed95dc4c6 | ||
|
e56fdf1ec5 | ||
|
52454651ea | ||
|
4d7b59a9f9 | ||
|
ee7a4a827a | ||
|
6db2e23078 | ||
|
46e491f05e | ||
|
d2604ab313 | ||
|
ad70b09eea | ||
|
c5b292b635 | ||
|
b818a028a1 | ||
|
87380722b5 | ||
|
e777b59673 | ||
|
d2d388bdcd | ||
|
5494d8b3cd |
||
|
ed0bec9aa9 | ||
|
1c7e189dd0 | ||
|
626ff29545 | ||
|
558e9bdf5f |
||
|
c3d64da23c | ||
|
709c315bc4 |
||
|
49bcd792cd |
||
|
a0be0f22fc |
||
|
56d9b4b14d | ||
|
82ad5189fe | ||
|
adc01a086a | ||
|
67a9b80c51 | ||
|
c009f450a6 | ||
|
906e2e7c4a | ||
|
78d92aafd7 | ||
|
8eb8f49581 | ||
|
d0a6f93f9e | ||
|
0a8b6a9429 | ||
|
92684b6208 | ||
|
15cb5cc6c1 | ||
|
19c9f6194b | ||
|
a80f8f9d01 | ||
|
1a93e211e4 | ||
|
79f6f8e508 | ||
|
ee34e3a317 | ||
|
f200d53eb1 | ||
|
e9528ec4a8 | ||
|
e972a5bf49 | ||
|
e13da8aae3 | ||
|
e588a8642b | ||
|
079c73635c | ||
|
688a83e405 | ||
|
edc6072da2 | ||
|
03d77fd465 | ||
|
4841b310d8 | ||
|
f250195256 | ||
|
9a35b6ba77 | ||
|
220c8d173b | ||
|
188810c9e9 | ||
|
4adec07103 | ||
|
89469d14e9 | ||
|
b529b80132 | ||
|
39f389eb17 | ||
|
2189b35545 | ||
|
ee41e6eead | ||
|
71257fff2c | ||
|
419eb0a4a2 | ||
|
84e9b566a0 | ||
|
2b4754c1d7 | ||
|
07bff8143e | ||
|
fc9db11c56 | ||
|
a5abeba442 | ||
|
c79793be88 | ||
|
5281ac76cb | ||
|
3897001cec | ||
|
815857cfc0 | ||
|
08cb52a768 | ||
|
d5742c31fb |
||
|
a28526a521 | ||
|
098cb0786d | ||
|
9db4e2f38f | ||
|
1fa23c219f | ||
|
f80a5e7445 | ||
|
68bf916acd | ||
|
563291579e | ||
|
94c068e91e | ||
|
b5e9b26ae0 | ||
|
5ed86bf257 | ||
|
5ac21c69aa | ||
|
2ba873cbff | ||
|
16e415ebf1 | ||
|
7ce74a31e2 | ||
|
b71df03e35 | ||
|
6610eb1dbf | ||
|
86dbec6b57 | ||
|
643cd4fa6e | ||
|
aa9006bca0 | ||
|
abd69183ea | ||
|
fcf2b0a187 | ||
|
068e318629 | ||
|
f8d25228ce | ||
|
fd08eba8d2 | ||
|
f2570811a4 | ||
|
1e113fd8dc | ||
|
0a02ffd355 | ||
|
9a29241cde | ||
|
02ea77c6a0 | ||
|
25ba696b6a | ||
|
152b98da90 | ||
|
877d4ae833 | ||
|
c434b963b4 | ||
|
0cf3030d7b | ||
|
112ad8d1da |
||
|
f87b76160e | ||
|
cb7d2057ce | ||
|
8c6f572615 |
||
|
8bd5169c5f | ||
|
957a76df3b | ||
|
32c187e5bb | ||
|
25233f8aad | ||
|
c37ccaeff1 | ||
|
ac6555f3fd | ||
|
cbf13d73d4 | ||
|
5c9211f381 | ||
|
d24ee72e42 | ||
|
8db9870295 | ||
|
8435a8eb16 | ||
|
bfee082c01 | ||
|
ec172f2d0e | ||
|
796f129b8d | ||
|
10e976699a | ||
|
7b1ed4d723 | ||
|
e4779f32c5 | ||
|
7530e8a941 | ||
|
6d1818ffdb | ||
|
0145621e8d | ||
|
1cd0c5e99b | ||
|
dcd431b0d4 | ||
|
7939521a10 | ||
|
187ad99f3c | ||
|
ee4f9b02f9 | ||
|
debf12f6c5 | ||
|
5687a8ef65 | ||
|
710600f459 | ||
|
52925c44c7 | ||
|
f30fc08468 | ||
|
98073ac28d | ||
|
c271c73e53 | ||
|
16e66254c6 | ||
|
b470c6c454 | ||
|
cd7a8f4546 |
||
|
0b923a03b4 | ||
|
0a18c04a4b | ||
|
c8ec9bc2cc | ||
|
5da9c6ce64 | ||
|
fd27fceacb | ||
|
64cfb3580d | ||
|
152fd11bc6 | ||
|
42097bc0cb | ||
|
b29ab34338 | ||
|
7863af5ead | ||
|
8e5f0d1e82 | ||
|
ecea06f853 | ||
|
4ac038af66 |
||
|
8931588849 | ||
|
21798fa0d0 | ||
|
a0011375b7 | ||
|
02cd96856f | ||
|
25fa794d26 | ||
|
00ff39f5df | ||
|
ff888dbee3 | ||
|
2dd333f8fb | ||
|
668e9bb2ac | ||
|
01419d9c36 | ||
|
c08bdaacdb | ||
|
aa345c9e0c | ||
|
c00c5d9c48 | ||
|
0d71bacd08 | ||
|
177c711b80 | ||
|
bccf6cd9bb | ||
|
27b4648c63 | ||
|
e24e975ce8 | ||
|
0564a6fea9 | ||
|
0020d5aa49 | ||
|
ff84ab2014 | ||
|
3b44a7f317 | ||
|
3fafca69bc | ||
|
11dee00c73 |
||
|
98f2d40599 | ||
|
be274b43a6 | ||
|
cddfc7749b | ||
|
334fbc3440 | ||
|
f9f50ac382 | ||
|
4dcec32505 |
||
|
bdc99aef87 | ||
|
89e0323351 | ||
|
548cd0c059 | ||
|
30413435d8 | ||
|
14c5462019 | ||
|
389b32f51a | ||
|
5c6645a8af | ||
|
b755f09e95 | ||
|
9def32985b | ||
|
eb21dd17b8 | ||
|
0b1942150f | ||
|
8767613338 | ||
|
c0e4624c81 | ||
|
69a9be0513 | ||
|
f7de5f2684 | ||
|
30b4bca5a6 | ||
|
3780b93fad | ||
|
f004adc51d | ||
|
3aadf346ff | ||
|
b816bf9232 | ||
|
60cab1bafd | ||
|
fd849bb9f2 | ||
|
ceb96734b7 | ||
|
8cb7c19bf4 | ||
|
a440e3ec15 | ||
|
204ab32f82 | ||
|
4e1d4caf98 | ||
|
62f2515138 | ||
|
f8c1617e25 | ||
|
32ca0f5d63 | ||
|
597cfe3abf | ||
|
601afedee0 |
||
|
9232f08ee3 | ||
|
46bcf4efc0 | ||
|
9420945daa | ||
|
7f41b1d172 | ||
|
8038d8b44d | ||
|
262a2253aa | ||
|
55f0feb874 | ||
|
f0d4e898d7 | ||
|
252efbda5c | ||
|
c697de9517 | ||
|
7217965542 | ||
|
2aa73a8fad | ||
|
6d5bdce9dd | ||
|
1d8fb306d7 | ||
|
39cf3f6868 | ||
|
512b3fad5b | ||
|
9354efceb1 | ||
|
bafdd4a56e | ||
|
b602d58161 | ||
|
2cf5eb65a3 | ||
|
c6a2b7e0a6 | ||
|
81d90e1b0d | ||
|
c7f35f00b8 | ||
|
6dd18d3f58 | ||
|
ac96bd1788 | ||
|
eaa83f52f2 | ||
|
a806e63863 | ||
|
e311aa7cae | ||
|
a13ba09a8e | ||
|
a082555c04 | ||
|
e0d6705b71 | ||
|
46c3814a4a | ||
|
82728d903d | ||
|
18705e2fe0 | ||
|
8e813902c5 | ||
|
b8906423df | ||
|
cee204b5a5 | ||
|
39581232a1 | ||
|
055bd9181b | ||
|
985b77f137 | ||
|
f66b2eeef8 | ||
|
0e25815b1f | ||
|
676fb7e0a7 | ||
|
648b6a52ea | ||
|
03bf9c982b | ||
|
bc3227c9ff | ||
|
49a28ba554 | ||
|
cb17e7542c | ||
|
13142acf60 | ||
|
7af04391a3 | ||
|
36875e1859 | ||
|
4247c37300 | ||
|
8f4ebab023 | ||
|
cba500459f | ||
|
654c02a8dd |
||
|
9162c82150 | ||
|
9a423c0e67 | ||
|
ed3b70cbb9 | ||
|
a87153b089 | ||
|
bd59fa4df3 | ||
|
6ae943758f | ||
|
af7066de64 | ||
|
9b54852aad | ||
|
436402a91e |
||
|
5596cd8d7a | ||
|
654b6bf041 | ||
|
18cd9b5efa | ||
|
9ed225b100 | ||
|
48e29ff861 | ||
|
1b13fda06b | ||
|
39607fca1d | ||
|
b982fde455 | ||
|
608f9ee8e6 | ||
|
c064ce4ad0 | ||
|
829062808a | ||
|
3bf52efe63 | ||
|
d2a6e2362a |
||
|
5ce1b564dc |
||
|
7287495064 |
||
|
e746cc80a4 |
||
|
a511e37572 |
||
|
cf1fda81f6 |
||
|
90e974cd24 |
||
|
1fc1f24cad |
||
|
ca7fcacddc |
||
|
50837322cc |
||
|
4019b99217 |
||
|
5fdd6ce9a6 |
||
|
4dfb3facb4 |
||
|
7bf7c0cb61 |
||
|
85e839e21d |
||
|
f7fb1226a4 |
||
|
9fb75a141d |
||
|
1c66c4e11a |
||
|
900ea0ce5a |
||
|
f7f7d086e4 |
||
|
374a29fd35 |
||
|
9f955b300b |
||
|
d00200dc3e |
||
|
145dea59bb |
||
|
9828aca733 | ||
|
86ce1477c1 | ||
|
11218ac43c | ||
|
ab6ea6a743 | ||
|
6ac0ab3549 | ||
|
9d896028bd | ||
|
f447661345 | ||
|
e101a8e2dd | ||
|
20f6639f11 |
||
|
33b9bf20bc |
||
|
dfe64e53d0 |
||
|
6ad7d5759d |
||
|
558b79aa9c | ||
|
b047a60a09 |
||
|
f7b0eb16c8 |
||
|
014bf73db8 | ||
|
3d4536286b | ||
|
a8490e6637 | ||
|
cd08265406 | ||
|
995dba14ec | ||
|
9d8a740c6a |
||
|
3f7f977834 |
||
|
6c506fc2f0 |
||
|
0606f05707 | ||
|
7d3fcde71c | ||
|
ff03a4eff6 | ||
|
cf0e697d13 | ||
|
c258003be9 | ||
|
8bfb9d210f | ||
|
0a8d7826a4 | ||
|
0447117b6b | ||
|
3f58c4de33 | ||
|
b17d81c1e9 | ||
|
1dacb0b1be |
||
|
ecda48307d | ||
|
f58f84562a | ||
|
82fabb3df0 | ||
|
f15cffb737 | ||
|
a8029b7282 | ||
|
2d011ae6bc | ||
|
db3bdbdbc1 |
||
|
ccc0b16251 | ||
|
af5df243d4 | ||
|
58c27fda89 | ||
|
b0b6bd3658 | ||
|
f6bc8f7cd7 | ||
|
cb4ffd29cf | ||
|
99f93beada | ||
|
d0b301aae6 | ||
|
b9bd821fb2 | ||
|
f9a6657248 | ||
|
3382cd31a9 | ||
|
011e876f5c | ||
|
b0c453902b | ||
|
079e67da48 | ||
|
d6838462b8 | ||
|
ff99331225 | ||
|
b361ce7733 | ||
|
a9950f2fbe | ||
|
d259ce6c48 | ||
|
aa99751314 |
||
|
748da95b48 | ||
|
c922ac5f38 | ||
|
4abf9e9db4 | ||
|
bff92cb5a3 | ||
|
fb1095d141 | ||
|
e3bfa5133f | ||
|
da9bedc967 | ||
|
4eac7adcc9 | ||
|
b8f15e4ea0 | ||
|
68295b2e00 | ||
|
fa5011b988 | ||
|
0b552407fe | ||
|
445e2b1344 | ||
|
ae785c1aa2 | ||
|
9524b8c370 | ||
|
a1451655eb | ||
|
dfbba28d7c | ||
|
b1a6d66cf5 |
||
|
4237603dd6 |
||
|
fe5f16205f |
||
|
9a8bdc6cbd |
||
|
1a466def75 |
||
|
745bc4b58b | ||
|
a9d09e5019 | ||
|
da635229bf |
||
|
04e04b7073 | ||
|
1f2bbbd4aa | ||
|
f1cfd152e2 | ||
|
b78c1bd998 | ||
|
d84a5217ee | ||
|
64ad2a53bb | ||
|
03d4073cdd | ||
|
eb719691b1 | ||
|
a1f04ddca1 | ||
|
ea3b4ed6ac | ||
|
52e1c84e2c | ||
|
b8f92b6a26 | ||
|
dd653c4784 | ||
|
72bac98365 | ||
|
c872758c05 | ||
|
4e2908ff9d | ||
|
6f3c4a5466 | ||
|
c0a1a604e6 |
||
|
a2b73b7b11 | ||
|
a1dd77d115 | ||
|
6faaf807ff | ||
|
bc0c2b7d0d | ||
|
b51f97e97d | ||
|
16a0c97fbf | ||
|
c081f20776 | ||
|
6b6fa21b25 | ||
|
542eb40b9b | ||
|
7b76e5510b |
||
|
73dac787a3 | ||
|
b6046c17a1 | ||
|
56f4671c82 | ||
|
5294cff95f | ||
|
0a444a374e | ||
|
cc31b744d1 | ||
|
eb18fccac5 | ||
|
f2ffae12cf | ||
|
15231669e6 | ||
|
e50cfc8499 | ||
|
7566ebfba7 | ||
|
64193310ee | ||
|
4392dee96d | ||
|
2269831c9f | ||
|
cdb6296d58 | ||
|
a8ef6af0fd | ||
|
7af647025a | ||
|
a2d6b79195 | ||
|
648a75e687 | ||
|
e4cd25057f | ||
|
1331a7f75c | ||
|
d539fe7bf7 | ||
|
f185a4ce67 | ||
|
fb57260056 | ||
|
3ff78d243a | ||
|
5d9da94890 | ||
|
be7b87c1c2 |
||
|
79af994eae | ||
|
022ab86988 | ||
|
98e2a64e2d | ||
|
1761dae2db | ||
|
93b13e4d52 | ||
|
d3ca92df79 | ||
|
01f1f4ac75 | ||
|
b2469c2a9c | ||
|
e2dbbbfbc6 | ||
|
13e48ead92 | ||
|
bc0d14119c | ||
|
388e4eb44b | ||
|
106707b40f | ||
|
29eaab5ff4 | ||
|
b2c8a1cfd3 | ||
|
4f0c2ec258 | ||
|
6f1e4be0c1 | ||
|
370b498e6d | ||
|
421cfba3cf | ||
|
5624b9f094 | ||
|
4694cbf95b | ||
|
ee35b617b7 | ||
|
d22f9d1f38 | ||
|
02de040a5e | ||
|
24014c349e | ||
|
b1b418a939 | ||
|
e271c24100 | ||
|
fe22e2397c | ||
|
e8acd8afd3 |
||
|
57148eb1e8 | ||
|
d4e4a2a1e3 | ||
|
61334f7982 | ||
|
f9c2c910d6 | ||
|
1b32d7a874 | ||
|
38c83ae6e1 | ||
|
f4894b0edd | ||
|
bfa9c89e6f | ||
|
87a7bf2436 | ||
|
2f70869519 | ||
|
b52cec753f | ||
|
4d20a74c04 | ||
|
4d06d62515 | ||
|
f3ccfc4969 | ||
|
7643bdd2b5 | ||
|
82daae4c7c | ||
|
83ea43cf49 | ||
|
c3b18a6deb | ||
|
c11dd3fb46 | ||
|
b06f4fdd63 | ||
|
0fb9fc752b | ||
|
d87e2e7e40 | ||
|
c17dd6c5b3 | ||
|
6007f2d3d5 | ||
|
8235a9752d | ||
|
ed3c5588ad | ||
|
d74c9daa8a | ||
|
cf46b22272 | ||
|
34caa374d6 | ||
|
0c7612bca4 | ||
|
65a9c12a1f | ||
|
ccc33dd2df | ||
|
8e4f50a909 | ||
|
199d9cf65c | ||
|
8fc7295869 | ||
|
6ee4dd753a | ||
|
9b470d2709 | ||
|
95e8bbd5f0 | ||
|
b83a554921 | ||
|
4c34615e4e | ||
|
a16204d99f | ||
|
a5f7acdd2e | ||
|
1b21719897 | ||
|
8c8d646099 | ||
|
b705cfcdd8 | ||
|
fce430c6ba | ||
|
bc4c1d64cb | ||
|
fe6ba0c7c9 | ||
|
f27b46436c | ||
|
eaea89b7f0 | ||
|
88c3b6dead | ||
|
6e9b97e377 | ||
|
c9949fbc64 | ||
|
27e853454d | ||
|
5645456cac | ||
|
dd3f24deef | ||
|
501cc5c7c5 | ||
|
8efb6c09db |
||
|
0e1bafdff3 | ||
|
772bb20875 | ||
|
09c9108a35 | ||
|
ec8c0a50c2 | ||
|
d61bd928a4 | ||
|
93f029cb75 | ||
|
1695ff8125 | ||
|
7f6b9a8867 | ||
|
ac43750ada | ||
|
e47fa23729 | ||
|
e186b5c039 | ||
|
5158493ba6 | ||
|
48cc6e684a | ||
|
f1fc008d02 | ||
|
1937fcf476 | ||
|
81e59014da | ||
|
e4bf651aa7 | ||
|
d07821d275 | ||
|
9f5e1157f0 | ||
|
f666b882a8 | ||
|
9dfdacf54f | ||
|
0b74ecebe0 | ||
|
dc6626453a |
||
|
74f7250259 | ||
|
316073a925 | ||
|
a8d8a4c106 | ||
|
51b42e3a84 | ||
|
049899b56b | ||
|
2c01097315 | ||
|
b332d1c2e4 | ||
|
7c06db89e3 | ||
|
7cd313951a | ||
|
3f1ed6dde4 | ||
|
86a13589fa | ||
|
ddc9240a14 | ||
|
3c7e3ec9e2 | ||
|
ba42ebf1ae | ||
|
8ac572682b | ||
|
13b560c191 | ||
|
24d6972f6b | ||
|
f324ee73c5 | ||
|
48035bbd4e | ||
|
32e8610b20 | ||
|
11934670ec | ||
|
0636b3087f | ||
|
34b77270e8 | ||
|
f8d6a61157 | ||
|
eb8ed7e5e9 | ||
|
cee2aae4ca | ||
|
c69e8cde7a | ||
|
63236ed693 | ||
|
1d310e6df5 | ||
|
da1c0f7f18 | ||
|
f5cbb9604d | ||
|
ff5ef8fe8b | ||
|
466e7bfcb8 | ||
|
288c56f5d3 | ||
|
74981d9e97 | ||
|
212e8ac348 | ||
|
6e239a7f65 | ||
|
5f514a6e4d | ||
|
d8c5083c6f | ||
|
d4873965c8 | ||
|
a789dd76d5 | ||
|
72620db8df | ||
|
b669564f39 | ||
|
aca7e8a9af | ||
|
b580c830e0 | ||
|
4935e6e1a3 | ||
|
bc2e4942fc | ||
|
8cc2086402 |
||
|
c0eeb75322 | ||
|
bcde3aea4f | ||
|
abb95c8c92 | ||
|
0ecd9d9682 | ||
|
1ed750a33a | ||
|
6f501b1fdf | ||
|
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 | ||
|
76b3f4cd6a | ||
|
1b9ac27578 | ||
|
a2e7446fe7 | ||
|
7ad20a2730 | ||
|
184e068f37 | ||
|
414199fc66 | ||
|
aee5e1fb94 | ||
|
d3c712fe2a | ||
|
4a1f4acf76 | ||
|
30bfa13308 | ||
|
507a12bf82 | ||
|
69bd7a1f1b | ||
|
7ab27a7a7f | ||
|
2bca029f6f | ||
|
8844b6b8e5 |
||
|
9e966ac91f | ||
|
6ed62c14d3 | ||
|
744363597d | ||
|
7a6b5b6dd9 | ||
|
43fb63a063 |
1852 changed files with 57157 additions and 21254 deletions
|
@ -11,7 +11,7 @@ include_file = ["main.go"]
|
|||
include_dir = ["cmd", "models", "modules", "options", "routers", "services"]
|
||||
exclude_dir = [
|
||||
"models/fixtures",
|
||||
"models/migrations/fixtures",
|
||||
"models/gitea_migrations/fixtures",
|
||||
"modules/avatar/identicon/testdata",
|
||||
"modules/avatar/testdata",
|
||||
"modules/git/tests",
|
||||
|
|
|
@ -13,34 +13,30 @@ forgejo.org/models
|
|||
IsErrSHANotFound
|
||||
IsErrMergeDivergingFastForwardOnly
|
||||
|
||||
forgejo.org/models/activities
|
||||
GetActivityByID
|
||||
NewFederatedUserActivity
|
||||
CreateUserActivity
|
||||
GetFollowingFeeds
|
||||
FederatedUserActivity.loadActor
|
||||
|
||||
forgejo.org/models/auth
|
||||
WebAuthnCredentials
|
||||
|
||||
forgejo.org/models/db
|
||||
TruncateBeans
|
||||
TruncateBeansCascade
|
||||
InTransaction
|
||||
DumpTables
|
||||
GetTableNames
|
||||
extendBeansForCascade
|
||||
|
||||
forgejo.org/models/dbfs
|
||||
file.renameTo
|
||||
Create
|
||||
Rename
|
||||
|
||||
forgejo.org/models/forgefed
|
||||
GetFederationHost
|
||||
|
||||
forgejo.org/models/forgejo/semver
|
||||
GetVersion
|
||||
SetVersionString
|
||||
SetVersion
|
||||
|
||||
forgejo.org/models/forgejo_migrations
|
||||
resetMigrations
|
||||
|
||||
forgejo.org/models/git
|
||||
RemoveDeletedBranchByID
|
||||
|
||||
|
@ -61,17 +57,10 @@ forgejo.org/models/user
|
|||
IsErrExternalLoginUserAlreadyExist
|
||||
IsErrExternalLoginUserNotExist
|
||||
NewFederatedUser
|
||||
NewFederatedUserFollower
|
||||
IsErrUserSettingIsNotExist
|
||||
GetUserAllSettings
|
||||
DeleteUserSetting
|
||||
GetFederatedUser
|
||||
GetFederatedUserByUserID
|
||||
UpdateFederatedUser
|
||||
GetFollowersForUser
|
||||
AddFollower
|
||||
RemoveFollower
|
||||
IsFollowingAp
|
||||
|
||||
forgejo.org/modules/activitypub
|
||||
NewContext
|
||||
|
@ -102,24 +91,14 @@ forgejo.org/modules/eventsource
|
|||
Event.String
|
||||
|
||||
forgejo.org/modules/forgefed
|
||||
NewForgeFollowFromAp
|
||||
NewForgeFollow
|
||||
ForgeFollow.MarshalJSON
|
||||
ForgeFollow.UnmarshalJSON
|
||||
ForgeFollow.Validate
|
||||
NewForgeUndoLike
|
||||
ForgeUndoLike.UnmarshalJSON
|
||||
ForgeUndoLike.Validate
|
||||
NewForgeUserActivityFromAp
|
||||
NewForgeUserActivity
|
||||
ForgeUserActivity.Validate
|
||||
NewPersonIDFromModel
|
||||
GetItemByType
|
||||
JSONUnmarshalerFn
|
||||
NotEmpty
|
||||
NewForgeUserActivityNoteFromAp
|
||||
newNote
|
||||
ForgeUserActivityNote.Validate
|
||||
ToRepository
|
||||
OnRepository
|
||||
|
||||
|
@ -231,7 +210,6 @@ forgejo.org/modules/util/filebuffer
|
|||
|
||||
forgejo.org/modules/validation
|
||||
IsErrNotValid
|
||||
ValidateIDExists
|
||||
|
||||
forgejo.org/modules/web
|
||||
RouteMock
|
||||
|
@ -248,6 +226,15 @@ forgejo.org/routers/web/org
|
|||
forgejo.org/services/context
|
||||
GetPrivateContext
|
||||
|
||||
forgejo.org/services/federation
|
||||
FollowRemoteActor
|
||||
|
||||
forgejo.org/services/mailer
|
||||
ActionCloseIssueByCommit.isActionAdditionalData
|
||||
|
||||
forgejo.org/services/notify
|
||||
UnregisterNotifier
|
||||
|
||||
forgejo.org/services/repository
|
||||
IsErrForkAlreadyExist
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "Gitea DevContainer",
|
||||
"image": "mcr.microsoft.com/devcontainers/go:1.24-bullseye",
|
||||
"image": "mcr.microsoft.com/devcontainers/go:2.0-bullseye",
|
||||
"features": {
|
||||
// installs nodejs into container
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "22"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/git-lfs:1.2.4": {},
|
||||
"ghcr.io/devcontainers/features/git-lfs:1.2.5": {},
|
||||
"ghcr.io/warrenbuckley/codespace-features/sqlite:1": {}
|
||||
},
|
||||
"customizations": {
|
||||
|
|
|
@ -23,8 +23,9 @@ body:
|
|||
It is running the latest development branch and will confirm the problem is not already fixed.
|
||||
If you can reproduce it, provide a URL in the description.
|
||||
options:
|
||||
- "Yes"
|
||||
- "No"
|
||||
- "Yes, I've linked the repository below"
|
||||
- "No, I've tried it and the problem is not present there"
|
||||
- "No, I can't try it on the test instance for some reason"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
|
|
@ -23,8 +23,9 @@ body:
|
|||
It is running the latest development branch and will confirm the problem is not already fixed.
|
||||
If you can reproduce it, provide a URL in the description.
|
||||
options:
|
||||
- "Yes"
|
||||
- "No"
|
||||
- "Yes, I've linked the repository below"
|
||||
- "No, I've tried it and the problem is not present there"
|
||||
- "No, I can't try it on the test instance for some reason"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
|
|
@ -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
|
|
@ -17,7 +17,7 @@ runs:
|
|||
apt-get -q install -qq -y zstd
|
||||
|
||||
- name: "Set up Go using setup-go"
|
||||
uses: https://data.forgejo.org/actions/setup-go@v5
|
||||
uses: https://data.forgejo.org/actions/setup-go@v6
|
||||
id: go-version
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
|
|
|
@ -25,10 +25,10 @@ jobs:
|
|||
if: vars.ROLE == 'forgejo-coding'
|
||||
runs-on: lxc-bookworm
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
|
||||
- id: forgejo
|
||||
uses: https://data.forgejo.org/actions/setup-forgejo@v2.0.4
|
||||
uses: https://data.forgejo.org/actions/setup-forgejo@v3.0.4
|
||||
with:
|
||||
user: root
|
||||
password: admin1234
|
||||
|
|
|
@ -33,7 +33,7 @@ jobs:
|
|||
# root is used for testing, allow it
|
||||
if: vars.ROLE == 'forgejo-integration' || github.repository_owner == 'root'
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
@ -43,11 +43,11 @@ jobs:
|
|||
repository="${{ github.repository }}"
|
||||
echo "value=${repository##*/}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: https://data.forgejo.org/actions/setup-node@v4
|
||||
- uses: https://data.forgejo.org/actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- uses: https://data.forgejo.org/actions/setup-go@v5
|
||||
- uses: https://data.forgejo.org/actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
|
||||
|
@ -164,7 +164,7 @@ jobs:
|
|||
|
||||
- name: build container & release
|
||||
if: ${{ secrets.TOKEN != '' }}
|
||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.3.5
|
||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.4.1
|
||||
with:
|
||||
forgejo: "${{ env.GITHUB_SERVER_URL }}"
|
||||
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
|
||||
|
@ -183,7 +183,7 @@ jobs:
|
|||
|
||||
- name: build rootless container
|
||||
if: ${{ secrets.TOKEN != '' }}
|
||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.3.5
|
||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.4.1
|
||||
with:
|
||||
forgejo: "${{ env.GITHUB_SERVER_URL }}"
|
||||
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
|
||||
|
@ -201,7 +201,7 @@ jobs:
|
|||
|
||||
- name: end-to-end tests
|
||||
if: ${{ secrets.TOKEN != '' && vars.ROLE == 'forgejo-integration' && vars.SKIP_END_TO_END != 'true' }}
|
||||
uses: https://data.forgejo.org/actions/cascading-pr@v2.2.0
|
||||
uses: https://data.forgejo.org/actions/cascading-pr@v2.3.0
|
||||
with:
|
||||
origin-url: ${{ env.GITHUB_SERVER_URL }}
|
||||
origin-repo: ${{ github.repository }}
|
||||
|
@ -212,6 +212,7 @@ jobs:
|
|||
destination-repo: forgejo/end-to-end
|
||||
destination-branch: main
|
||||
destination-token: ${{ secrets.CASCADE_DESTINATION_TOKEN }}
|
||||
close: true
|
||||
update: .forgejo/cascading-release-end-to-end
|
||||
|
||||
- name: copy to experimental
|
||||
|
|
|
@ -37,11 +37,11 @@ jobs:
|
|||
container:
|
||||
image: data.forgejo.org/oci/node:22-bookworm
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: '0'
|
||||
show-progress: 'false'
|
||||
- uses: https://data.forgejo.org/actions/cascading-pr@v2.2.0
|
||||
- uses: https://data.forgejo.org/actions/cascading-pr@v2.3.0
|
||||
with:
|
||||
origin-url: ${{ env.GITHUB_SERVER_URL }}
|
||||
origin-repo: ${{ github.repository }}
|
||||
|
@ -53,5 +53,5 @@ jobs:
|
|||
destination-repo: forgejo/end-to-end
|
||||
destination-branch: main
|
||||
destination-token: ${{ secrets.END_TO_END_CASCADING_PR_DESTINATION }}
|
||||
close-merge: true
|
||||
close: true
|
||||
update: .forgejo/cascading-pr-end-to-end
|
||||
|
|
89
.forgejo/workflows/coverage.yml
Normal file
89
.forgejo/workflows/coverage.yml
Normal file
|
@ -0,0 +1,89 @@
|
|||
name: coverage
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
repository:
|
||||
description: 'repository'
|
||||
type: string
|
||||
ref:
|
||||
description: 'ref'
|
||||
type: string
|
||||
unit-tests-env:
|
||||
description: 'COVERAGE_TEST_PACKAGES=forgejo.org/modules/actions'
|
||||
type: string
|
||||
integration-tests-env:
|
||||
description: 'COVERAGE_TEST_ARGS=-run=TestAPIListRepoComments'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
all:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/ci:1'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
services:
|
||||
elasticsearch:
|
||||
image: data.forgejo.org/oci/bitnami/elasticsearch:7
|
||||
options: --tmpfs /bitnami/elasticsearch/data
|
||||
env:
|
||||
discovery.type: single-node
|
||||
ES_JAVA_OPTS: "-Xms512m -Xmx512m"
|
||||
minio:
|
||||
image: data.forgejo.org/oci/bitnami/minio:2024.8.17
|
||||
options: >-
|
||||
--hostname gitea.minio --tmpfs /bitnami/minio/data:noatime
|
||||
env:
|
||||
MINIO_DOMAIN: minio
|
||||
MINIO_ROOT_USER: 123456
|
||||
MINIO_ROOT_PASSWORD: 12345678
|
||||
mysql:
|
||||
image: 'data.forgejo.org/oci/bitnami/mysql:8.4'
|
||||
env:
|
||||
ALLOW_EMPTY_PASSWORD: yes
|
||||
MYSQL_DATABASE: testgitea
|
||||
#
|
||||
# See also https://codeberg.org/forgejo/forgejo/issues/976
|
||||
#
|
||||
MYSQL_EXTRA_FLAGS: --innodb-adaptive-flushing=OFF --innodb-buffer-pool-size=4G --innodb-log-buffer-size=128M --innodb-flush-log-at-trx-commit=0 --innodb-flush-log-at-timeout=30 --innodb-flush-method=nosync --innodb-fsync-threshold=1000000000 --disable-log-bin
|
||||
options: --tmpfs /bitnami/mysql/data:noatime
|
||||
ldap:
|
||||
image: data.forgejo.org/oci/test-openldap:latest
|
||||
pgsql:
|
||||
image: data.forgejo.org/oci/bitnami/postgresql:16
|
||||
env:
|
||||
POSTGRESQL_DATABASE: test
|
||||
POSTGRESQL_PASSWORD: postgres
|
||||
POSTGRESQL_FSYNC: off
|
||||
POSTGRESQL_EXTRA_FLAGS: -c full_page_writes=off
|
||||
options: --tmpfs /bitnami/postgresql
|
||||
cacher:
|
||||
image: registry.redict.io/redict:7.3.6-scratch
|
||||
options: --tmpfs /data:noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
with:
|
||||
repository: ${{ inputs.repository }}
|
||||
ref: ${{ inputs.ref }}
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
with:
|
||||
packages: git
|
||||
- uses: ./.forgejo/workflows-composite/build-backend
|
||||
- run: |
|
||||
su forgejo -c '${{ inputs.unit-tests-env }} make coverage-run'
|
||||
su forgejo -c '${{ inputs.integration-tests-env }} make coverage-run-pgsql'
|
||||
su forgejo -c '${{ inputs.integration-tests-env }} make coverage-run-mysql'
|
||||
su forgejo -c '${{ inputs.integration-tests-env }} make coverage-run-sqlite'
|
||||
su forgejo -c 'make coverage-merge'
|
||||
timeout-minutes: 180
|
||||
env:
|
||||
TEST_ELASTICSEARCH_URL: http://elasticsearch:9200
|
||||
TEST_MINIO_ENDPOINT: minio:9000
|
||||
TEST_LDAP: 1
|
||||
TEST_REDIS_SERVER: cacher:6379
|
||||
- uses: https://code.forgejo.org/forgejo/upload-artifact@v4
|
||||
with:
|
||||
name: coverage
|
||||
path: ${{ forge.workspace }}/coverage/merged
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2024 The Forgejo Authors
|
||||
# Copyright 2025 The Forgejo Authors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
name: requirements
|
||||
|
@ -13,7 +13,8 @@ on:
|
|||
|
||||
jobs:
|
||||
merge-conditions:
|
||||
if: vars.ROLE == 'forgejo-coding'
|
||||
if: >
|
||||
vars.ROLE == 'forgejo-coding' && forge.event.pull_request.head.repo.full_name != 'forgejo-cascading-pr/forgejo'
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
|
@ -26,9 +27,9 @@ jobs:
|
|||
- name: Missing test label
|
||||
if: >
|
||||
!(
|
||||
contains(toJSON(github.event.pull_request.labels), 'test/present')
|
||||
|| contains(toJSON(github.event.pull_request.labels), 'test/not-needed')
|
||||
|| contains(toJSON(github.event.pull_request.labels), 'test/manual')
|
||||
contains(toJSON(forge.event.pull_request.labels), 'test/present')
|
||||
|| contains(toJSON(forge.event.pull_request.labels), 'test/not-needed')
|
||||
|| contains(toJSON(forge.event.pull_request.labels), 'test/manual')
|
||||
)
|
||||
run: |
|
||||
echo "A team member must set the label to either 'present', 'not-needed' or 'manual'."
|
||||
|
@ -36,8 +37,8 @@ jobs:
|
|||
- name: Missing manual test instructions
|
||||
if: >
|
||||
(
|
||||
contains(toJSON(github.event.pull_request.labels), 'test/manual')
|
||||
&& !contains(toJSON(github.event.pull_request.body), '# Test')
|
||||
contains(toJSON(forge.event.pull_request.labels), 'test/manual')
|
||||
&& !contains(toJSON(forge.event.pull_request.body), '# Test')
|
||||
)
|
||||
run: |
|
||||
echo "Manual test label is set. The PR description needs to contain test steps introduced by a heading like:"
|
||||
|
|
|
@ -41,10 +41,10 @@ jobs:
|
|||
runs-on: lxc-bookworm
|
||||
if: vars.DOER != '' && vars.FORGEJO != '' && vars.TO_OWNER != '' && vars.FROM_OWNER != '' && secrets.TOKEN != ''
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
|
||||
- name: copy & sign
|
||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.3.5
|
||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.4.1
|
||||
with:
|
||||
from-forgejo: ${{ vars.FORGEJO }}
|
||||
to-forgejo: ${{ vars.FORGEJO }}
|
||||
|
@ -80,7 +80,7 @@ jobs:
|
|||
label: trigger
|
||||
|
||||
- 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.2.2
|
||||
with:
|
||||
url: https://placeholder:${{ secrets.TOKEN_NEXT_DIGEST }}@invisible.forgejo.org/infrastructure/next-digest
|
||||
ref_name: '${{ github.ref_name }}'
|
||||
|
|
|
@ -5,32 +5,34 @@ on:
|
|||
- cron: '@daily'
|
||||
|
||||
env:
|
||||
RNA_VERSION: v1.2.5 # renovate: datasource=gitea-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org
|
||||
RNA_WORKDIR: /srv/rna
|
||||
RNA_VERSION: v1.4.1 # renovate: datasource=gitea-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org
|
||||
|
||||
jobs:
|
||||
release-notes:
|
||||
if: vars.ROLE == 'forgejo-coding'
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
image: 'data.forgejo.org/oci/ci:1'
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
|
||||
- uses: https://data.forgejo.org/actions/setup-go@v5
|
||||
- uses: https://data.forgejo.org/actions/cache@v4
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
cache: false
|
||||
key: rna-${{ env.RNA_VERSION }}
|
||||
path: ${{ env.RNA_WORKDIR }}
|
||||
|
||||
- name: apt install jq
|
||||
- name: install release-notes-assistant
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -qq
|
||||
apt-get -q install -y -qq jq
|
||||
set -x
|
||||
wget -O /usr/local/bin/rna https://code.forgejo.org/forgejo/release-notes-assistant/releases/download/${{ env.RNA_VERSION}}/release-notes-assistant
|
||||
chmod +x /usr/local/bin/rna
|
||||
|
||||
- name: update open milestones
|
||||
run: |
|
||||
set -x
|
||||
curl -sS $GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY/milestones?state=open | jq -r '.[] | .title' | while read forgejo version ; do
|
||||
mkdir -p ${{ env.RNA_WORKDIR }}
|
||||
curl -sS $FORGEJO_SERVER_URL/api/v1/repos/$FORGEJO_REPOSITORY/milestones?state=open | jq -r '.[] | .title' | while read forgejo version ; do
|
||||
milestone="$forgejo $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
|
||||
rna --workdir ${{ env.RNA_WORKDIR }} --config .release-notes-assistant.yaml --storage milestone --storage-location "$milestone" --forgejo-url $FORGEJO_SERVER_URL --repository $FORGEJO_REPOSITORY --token ${{ secrets.RELEASE_NOTES_ASSISTANT_TOKEN }} release $version
|
||||
done
|
||||
|
|
|
@ -8,7 +8,7 @@ on:
|
|||
- labeled
|
||||
|
||||
env:
|
||||
RNA_VERSION: v1.2.5 # renovate: datasource=gitea-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org
|
||||
RNA_VERSION: v1.4.1 # renovate: datasource=gitea-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org
|
||||
|
||||
jobs:
|
||||
release-notes:
|
||||
|
@ -17,7 +17,7 @@ jobs:
|
|||
container:
|
||||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
|
||||
- name: event
|
||||
run: |
|
||||
|
@ -28,7 +28,7 @@ jobs:
|
|||
${{ toJSON(github.event) }}
|
||||
EOF
|
||||
|
||||
- uses: https://data.forgejo.org/actions/setup-go@v5
|
||||
- uses: https://data.forgejo.org/actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
cache: false
|
||||
|
|
|
@ -28,7 +28,7 @@ jobs:
|
|||
|
||||
runs-on: docker
|
||||
container:
|
||||
image: data.forgejo.org/renovate/renovate:41.1.4
|
||||
image: data.forgejo.org/renovate/renovate:41.146.0
|
||||
|
||||
steps:
|
||||
- name: Load renovate repo cache
|
||||
|
@ -49,7 +49,7 @@ jobs:
|
|||
LOG_LEVEL: debug
|
||||
RENOVATE_BASE_DIR: ${{ github.workspace }}/.tmp
|
||||
RENOVATE_ENDPOINT: ${{ github.server_url }}
|
||||
RENOVATE_PLATFORM: gitea
|
||||
RENOVATE_PLATFORM: forgejo
|
||||
RENOVATE_REPOSITORY_CACHE: 'enabled'
|
||||
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
|
||||
RENOVATE_GIT_AUTHOR: 'Renovate Bot <forgejo-renovate-action@forgejo.org>'
|
||||
|
@ -63,6 +63,10 @@ jobs:
|
|||
|
||||
OSV_OFFLINE_ROOT_DIR: ${{ github.workspace }}/.tmp/osv
|
||||
|
||||
# use direct connection for these domains for renovate go datasource instead of the go proxy
|
||||
# allows faster lookups
|
||||
GONOPROXY: code.forgejo.org
|
||||
|
||||
- name: Save renovate repo cache
|
||||
if: always() && env.RENOVATE_DRY_RUN != 'full'
|
||||
uses: https://data.forgejo.org/actions/cache/save@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
#
|
||||
# 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.
|
||||
# and send a notification via email to the contact email of the
|
||||
# organization should they fail.
|
||||
#
|
||||
# For debug purposes:
|
||||
#
|
||||
|
@ -22,6 +23,8 @@ on:
|
|||
- 'forgejo'
|
||||
- 'v*/forgejo'
|
||||
|
||||
enable-email-notifications: true
|
||||
|
||||
jobs:
|
||||
test-unit:
|
||||
# if: vars.ROLE == 'forgejo-coding'
|
||||
|
@ -31,13 +34,10 @@ jobs:
|
|||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install git 2.30
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
with:
|
||||
packages: git/bullseye git-lfs/bullseye
|
||||
release: bullseye
|
||||
- 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'
|
||||
|
@ -53,13 +53,10 @@ jobs:
|
|||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install git 2.30
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
with:
|
||||
packages: git/bullseye git-lfs/bullseye
|
||||
release: bullseye
|
||||
- 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'
|
||||
|
@ -69,3 +66,34 @@ jobs:
|
|||
RACE_ENABLED: true
|
||||
TEST_TAGS: sqlite sqlite_unlock_notify
|
||||
USE_REPO_TEST_DIR: 1
|
||||
test-mariadb:
|
||||
# if: vars.ROLE == 'forgejo-coding'
|
||||
if: vars.ROLE == 'forgejo-integration'
|
||||
runs-on: docker
|
||||
name: ${{ format('test-mariadb (v{0})', matrix.version) }}
|
||||
strategy:
|
||||
matrix:
|
||||
version: ['10.6', '11.8']
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
services:
|
||||
mysql:
|
||||
image: ${{ format('data.forgejo.org/oci/mariadb:{0}', matrix.version) }}
|
||||
env:
|
||||
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: yes
|
||||
MARIADB_DATABASE: testgitea
|
||||
options: --tmpfs /var/lib/mysql:noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install dependencies & git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
with:
|
||||
packages: git git-lfs
|
||||
- uses: ./.forgejo/workflows-composite/build-backend
|
||||
- run: |
|
||||
su forgejo -c 'make test-mysql-migration test-mysql'
|
||||
timeout-minutes: 120
|
||||
env:
|
||||
USE_REPO_TEST_DIR: 1
|
||||
|
|
|
@ -21,7 +21,7 @@ jobs:
|
|||
cat <<'EOF'
|
||||
${{ toJSON(github) }}
|
||||
EOF
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- run: su forgejo -c 'make deps-backend deps-tools'
|
||||
- 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
|
||||
|
@ -33,11 +33,15 @@ jobs:
|
|||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- run: make deps-frontend
|
||||
- run: make lint-frontend
|
||||
- run: make checks-frontend
|
||||
- run: make test-frontend-coverage
|
||||
- run: |
|
||||
# Usage of `dayjs` can be impacted by local system timezone and can be sensitive to DST differences; since
|
||||
# frontend tests are very short they're run twice with varying DST rules to reduce regression risk.
|
||||
TZ=Europe/Berlin make test-frontend-coverage
|
||||
TZ=America/Edmonton make test-frontend-coverage
|
||||
- run: make frontend
|
||||
- name: Install zstd for cache saving
|
||||
# works around https://github.com/actions/cache/issues/1169, because the
|
||||
|
@ -73,7 +77,7 @@ jobs:
|
|||
MINIO_ROOT_USER: 123456
|
||||
MINIO_ROOT_PASSWORD: 12345678
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
|
@ -100,7 +104,7 @@ jobs:
|
|||
image: 'data.forgejo.org/oci/playwright:latest'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 20
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
|
@ -122,7 +126,7 @@ jobs:
|
|||
echo "all=1" >> "$GITHUB_OUTPUT"
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: https://data.forgejo.org/tj-actions/changed-files@v46
|
||||
uses: https://data.forgejo.org/tj-actions/changed-files@v47
|
||||
with:
|
||||
separator: '\n'
|
||||
- run: |
|
||||
|
@ -168,7 +172,7 @@ jobs:
|
|||
image: ${{ matrix.cacher.image }}
|
||||
options: ${{ matrix.cacher.options }}
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
|
@ -201,7 +205,7 @@ jobs:
|
|||
MYSQL_EXTRA_FLAGS: --innodb-adaptive-flushing=OFF --innodb-buffer-pool-size=4G --innodb-log-buffer-size=128M --innodb-flush-log-at-trx-commit=0 --innodb-flush-log-at-timeout=30 --innodb-flush-method=nosync --innodb-fsync-threshold=1000000000 --disable-log-bin
|
||||
options: --tmpfs /bitnami/mysql/data:noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install dependencies & git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
|
@ -228,7 +232,8 @@ jobs:
|
|||
MINIO_ROOT_PASSWORD: 12345678
|
||||
options: --tmpfs /bitnami/minio/data
|
||||
ldap:
|
||||
image: data.forgejo.org/oci/test-openldap:latest
|
||||
image: data.forgejo.org/oci/forgejo-test-openldap:1
|
||||
options: --memory 500M
|
||||
pgsql:
|
||||
image: data.forgejo.org/oci/bitnami/postgresql:16
|
||||
env:
|
||||
|
@ -238,7 +243,7 @@ jobs:
|
|||
POSTGRESQL_EXTRA_FLAGS: -c full_page_writes=off
|
||||
options: --tmpfs /bitnami/postgresql
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install dependencies & git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
|
@ -260,7 +265,7 @@ jobs:
|
|||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install dependencies & git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
|
@ -288,7 +293,7 @@ jobs:
|
|||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- run: su forgejo -c 'make deps-backend deps-tools'
|
||||
- run: su forgejo -c 'make security-check'
|
||||
|
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -37,6 +37,8 @@ _testmain.go
|
|||
|
||||
*coverage.out
|
||||
coverage.all
|
||||
coverage.html
|
||||
coverage.html.gz
|
||||
coverage/
|
||||
cpu.out
|
||||
|
||||
|
@ -53,6 +55,8 @@ cpu.out
|
|||
*.log
|
||||
*.log.*.gz
|
||||
|
||||
/build/lint-locale/lint-locale
|
||||
/build/lint-locale-usage/lint-locale-usage
|
||||
/gitea
|
||||
/gitea-vet
|
||||
/debug
|
||||
|
@ -129,3 +133,4 @@ prime/
|
|||
|
||||
# Manpage
|
||||
/man
|
||||
tests/integration/api_activitypub_person_inbox_useractivity_test.go
|
||||
|
|
|
@ -42,6 +42,10 @@ linters:
|
|||
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
|
||||
- pkg: github.com/go-git/go-git
|
||||
desc: use forgejo.org/modules/git instead, see https://codeberg.org/forgejo/forgejo/pulls/4941
|
||||
- pkg: gopkg.in/yaml.v3
|
||||
desc: use go.yaml.in/yaml instead, see https://codeberg.org/forgejo/forgejo/pulls/8956
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- ifElseChain
|
||||
|
@ -79,6 +83,10 @@ linters:
|
|||
- name: unreachable-code
|
||||
- name: var-declaration
|
||||
- name: var-naming
|
||||
arguments:
|
||||
- []
|
||||
- []
|
||||
- - skip-package-name-checks: true
|
||||
- name: redefines-builtin-id
|
||||
disabled: true
|
||||
staticcheck:
|
||||
|
@ -86,6 +94,7 @@ linters:
|
|||
- all
|
||||
testifylint:
|
||||
disable:
|
||||
- error-is-as
|
||||
- go-require
|
||||
exclusions:
|
||||
generated: lax
|
||||
|
@ -111,7 +120,7 @@ linters:
|
|||
- errcheck
|
||||
- gocyclo
|
||||
- gosec
|
||||
path: models/migrations/v
|
||||
path: models/gitea_migrations/v
|
||||
- linters:
|
||||
- forbidigo
|
||||
path: cmd
|
||||
|
|
|
@ -5,6 +5,7 @@ branch-find-version: 'v(?P<version>\d+\.\d+)/forgejo'
|
|||
branch-to-version: '${version}.0'
|
||||
branch-from-version: 'v%[1]d.%[2]d/forgejo'
|
||||
tag-from-version: 'v%[1]d.%[2]d.%[3]d'
|
||||
supported-release-count: 3
|
||||
branch-known:
|
||||
- 'v7.0/forgejo'
|
||||
cleanup-line: 'sed -Ee "s/^(feat|fix):\s*//g" -e "s/^\[WIP\] //" -e "s/^WIP: //" -e "s;\[(UI|BUG|FEAT|v.*?/forgejo)\]\s*;;g"'
|
||||
|
|
|
@ -38,5 +38,10 @@ routers/.* @gusted
|
|||
options/locale/.* @0ko
|
||||
options/locale_next/.* @0ko
|
||||
|
||||
# lint-locale-usage
|
||||
build/lint-locale-usage/.* @fogti
|
||||
models/unit/.* @fogti
|
||||
services/migrations/lint-locale-usage/.* @fogti
|
||||
|
||||
# Personal interest
|
||||
.*/webhook.* @oliverpool
|
||||
|
|
128
Makefile
128
Makefile
|
@ -37,17 +37,17 @@ endif
|
|||
XGO_VERSION := go-1.21.x
|
||||
|
||||
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.3.0 # renovate: datasource=go
|
||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.8.0 # 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
|
||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0 # renovate: datasource=go
|
||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.4.1 # renovate: datasource=go
|
||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.9.1 # renovate: datasource=go
|
||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.5.0 # renovate: datasource=go
|
||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15 # renovate: datasource=go
|
||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.1 # renovate: datasource=go
|
||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
||||
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
|
||||
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.34.0 # renovate: datasource=go
|
||||
GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.5.2 # renovate: datasource=go
|
||||
RENOVATE_NPM_PACKAGE ?= renovate@41.1.4 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
|
||||
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.38.0 # renovate: datasource=go
|
||||
GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.6.0 # renovate: datasource=go
|
||||
RENOVATE_NPM_PACKAGE ?= renovate@41.146.0 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
|
||||
|
||||
# https://github.com/disposable-email-domains/disposable-email-domains/commits/main/
|
||||
DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ...
|
||||
|
@ -115,9 +115,6 @@ LDFLAGS := $(LDFLAGS) -X "main.ReleaseVersion=$(RELEASE_VERSION)" -X "main.MakeV
|
|||
|
||||
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
|
||||
|
||||
ifeq ($(HAS_GO), yes)
|
||||
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
|
||||
REMOTE_CACHER_MODULES ?= cache nosql session queue
|
||||
GO_TEST_REMOTE_CACHER_PACKAGES ?= $(addprefix forgejo.org/modules/,$(REMOTE_CACHER_MODULES))
|
||||
|
||||
|
@ -159,9 +156,6 @@ GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" ! -path modules/optio
|
|||
GO_SOURCES += $(GENERATED_GO_DEST)
|
||||
GO_SOURCES_NO_BINDATA := $(GO_SOURCES)
|
||||
|
||||
ifeq ($(HAS_GO), yes)
|
||||
MIGRATION_PACKAGES := $(shell $(GO) list forgejo.org/models/migrations/... forgejo.org/models/forgejo_migrations/...)
|
||||
endif
|
||||
|
||||
ifeq ($(filter $(TAGS_SPLIT),bindata),bindata)
|
||||
GO_SOURCES += $(BINDATA_DEST)
|
||||
|
@ -238,6 +232,9 @@ help:
|
|||
@echo " - test-frontend-coverage test frontend files and display code coverage"
|
||||
@echo " - test-backend test backend files"
|
||||
@echo " - test-remote-cacher test backend files that use a remote cache"
|
||||
@echo " - coverage-run* test and collect coverages in the coverage/data directory"
|
||||
@echo " - coverage-show-html display coverage-run results in an HTML page"
|
||||
@echo " - coverage-show-percent display coverage-run per package coverage percentage"
|
||||
@echo " - test-e2e-sqlite[\#name.test.e2e] test end to end using playwright and sqlite"
|
||||
@echo " - webpack build webpack files"
|
||||
@echo " - svg build svg files"
|
||||
|
@ -282,6 +279,24 @@ show-version-minor: verify-version
|
|||
show-version-api: verify-version
|
||||
@echo ${FORGEJO_VERSION_API}
|
||||
|
||||
###
|
||||
# Package computation targets
|
||||
###
|
||||
|
||||
# Target to compute GO_TEST_PACKAGES - only runs when needed
|
||||
.PHONY: compute-go-test-packages
|
||||
compute-go-test-packages:
|
||||
ifeq ($(HAS_GO), yes)
|
||||
$(eval GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list forgejo.org/models/gitea_migrations/...) $(shell $(GO) list forgejo.org/models/forgejo_migrations_legacy/...) $(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
|
||||
|
||||
# Target to compute MIGRATION_PACKAGES - only runs when needed
|
||||
.PHONY: compute-migration-packages
|
||||
compute-migration-packages:
|
||||
ifeq ($(HAS_GO), yes)
|
||||
$(eval MIGRATION_PACKAGES := $(shell $(GO) list forgejo.org/models/gitea_migrations/... forgejo.org/models/forgejo_migrations_legacy/... forgejo.org/models/forgejo_migrations/...))
|
||||
endif
|
||||
|
||||
###
|
||||
# Check system and environment requirements
|
||||
###
|
||||
|
@ -417,7 +432,7 @@ lint: lint-frontend lint-backend
|
|||
lint-fix: lint-frontend-fix lint-backend-fix
|
||||
|
||||
.PHONY: lint-frontend
|
||||
lint-frontend: lint-js lint-css
|
||||
lint-frontend: lint-js tsc lint-css
|
||||
|
||||
.PHONY: lint-frontend-fix
|
||||
lint-frontend-fix: lint-js-fix lint-css-fix
|
||||
|
@ -460,7 +475,7 @@ lint-locale:
|
|||
|
||||
.PHONY: lint-locale-usage
|
||||
lint-locale-usage:
|
||||
$(GO) run build/lint-locale-usage/lint-locale-usage.go
|
||||
$(GO) run ./build/lint-locale-usage/bin --allow-masked-usages-from=build/lint-locale-usage/allowed-masked-usage.txt
|
||||
|
||||
.PHONY: lint-md
|
||||
lint-md: node_modules
|
||||
|
@ -499,7 +514,11 @@ lint-disposable-emails-fix:
|
|||
|
||||
.PHONY: security-check
|
||||
security-check:
|
||||
go run $(GOVULNCHECK_PACKAGE) -show color ./...
|
||||
$(GO) run $(GOVULNCHECK_PACKAGE) -show color ./...
|
||||
|
||||
.PHONY: tsc
|
||||
tsc: node_modules
|
||||
npx tsc --noEmit
|
||||
|
||||
###
|
||||
# Development and testing targets
|
||||
|
@ -522,9 +541,9 @@ watch-backend: go-check
|
|||
test: test-frontend test-backend
|
||||
|
||||
.PHONY: test-backend
|
||||
test-backend:
|
||||
test-backend: | compute-go-test-packages
|
||||
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
|
||||
@$(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_PACKAGES)
|
||||
@TZ=UTC $(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_PACKAGES)
|
||||
|
||||
.PHONY: test-remote-cacher
|
||||
test-remote-cacher:
|
||||
|
@ -552,20 +571,39 @@ test-check:
|
|||
fi
|
||||
|
||||
.PHONY: test\#%
|
||||
test\#%:
|
||||
test\#%: | compute-go-test-packages
|
||||
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
|
||||
@$(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES)
|
||||
@TZ=UTC $(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES)
|
||||
|
||||
.PHONY: coverage
|
||||
coverage:
|
||||
grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' coverage.out > coverage-bodged.out
|
||||
grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' integration.coverage.out > integration.coverage-bodged.out
|
||||
$(GO) run build/gocovmerge.go integration.coverage-bodged.out coverage-bodged.out > coverage.all
|
||||
coverage-merge:
|
||||
rm -fr coverage/merged ; mkdir -p coverage/merged
|
||||
$(GO) tool covdata merge -i `find coverage/data -name 'covmeta.*' | sed -e 's|/covmeta.*|,|' | tr -d '\n' | sed -e 's/,$$//'` -o coverage/merged
|
||||
|
||||
.PHONY: unit-test-coverage
|
||||
unit-test-coverage:
|
||||
@echo "Running unit-test-coverage $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
|
||||
@$(GOTEST) $(GOTESTFLAGS) -timeout=20m -tags='$(TEST_TAGS)' -cover -coverprofile coverage.out $(GO_TEST_PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1
|
||||
coverage-convert: coverage-merge
|
||||
$(GO) tool covdata textfmt -i=coverage/merged -o=coverage/textfmt.out
|
||||
|
||||
coverage-show-html: coverage-convert
|
||||
( cd coverage ; $(GO) tool cover -html=textfmt.out -o coverage.html )
|
||||
xdg-open coverage/coverage.html
|
||||
|
||||
coverage-show-percentage: coverage-convert
|
||||
go tool cover -func=coverage/textfmt.out
|
||||
|
||||
coverage-run: | compute-go-test-packages
|
||||
contrib/coverage-helper.sh test_packages $(COVERAGE_TEST_PACKAGES)
|
||||
|
||||
coverage-run-%: generate-ini-% | compute-migration-packages
|
||||
#
|
||||
# Migration tests go first
|
||||
#
|
||||
$(MAKE) GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/$*.ini COVERAGE_TEST_ARGS= COVERAGE_TEST_PACKAGES=forgejo.org/tests/integration/migration-test coverage-run
|
||||
for pkg in $(MIGRATION_PACKAGES); do \
|
||||
$(MAKE) GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/$*.ini COVERAGE_TEST_DATABASE=$* COVERAGE_TEST_ARGS= COVERAGE_TEST_PACKAGES=$$pkg coverage-run ; \
|
||||
done
|
||||
#
|
||||
# All other integration tests follow
|
||||
#
|
||||
$(MAKE) GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/$*.ini COVERAGE_TEST_DATABASE=$* COVERAGE_TEST_PACKAGES=forgejo.org/tests/integration coverage-run
|
||||
|
||||
.PHONY: tidy
|
||||
tidy:
|
||||
|
@ -638,6 +676,7 @@ generate-ini-pgsql:
|
|||
-e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \
|
||||
-e 's|{{TEST_TYPE}}|$(or $(TEST_TYPE),integration)|g' \
|
||||
-e 's|{{TEST_STORAGE_TYPE}}|$(or $(TEST_STORAGE_TYPE),minio)|g' \
|
||||
-e 's|{{TEST_S3_HOST}}|$(or $(TEST_S3_HOST),minio:9000)|g' \
|
||||
tests/pgsql.ini.tmpl > tests/pgsql.ini
|
||||
|
||||
.PHONY: test-pgsql
|
||||
|
@ -684,7 +723,7 @@ test-e2e-mysql\#%: playwright e2e.mysql.test generate-ini-mysql
|
|||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GOTESTCOMPILEDRUNPREFIX) ./e2e.mysql.test $(GOTESTCOMPILEDRUNSUFFIX) -test.run TestE2e/$*
|
||||
|
||||
.PHONY: test-e2e-pgsql
|
||||
test-e2e-pgsql: playwright e2e.pgsql.test generate-ini-pgsql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.initest-e2e-pgsql: playwright e2e.pgsql.test generate-ini-pgsql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTESTCOMPILEDRUNPREFIX) ./e2e.pgsql.test $(GOTESTCOMPILEDRUNSUFFIX) -test.run TestE2e
|
||||
|
||||
.PHONY: test-e2e-pgsql\#%
|
||||
|
@ -707,14 +746,6 @@ bench-mysql: integrations.mysql.test generate-ini-mysql
|
|||
bench-pgsql: integrations.pgsql.test generate-ini-pgsql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./integrations.pgsql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
|
||||
|
||||
.PHONY: integration-test-coverage
|
||||
integration-test-coverage: integrations.cover.test generate-ini-mysql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.cover.test -test.coverprofile=integration.coverage.out
|
||||
|
||||
.PHONY: integration-test-coverage-sqlite
|
||||
integration-test-coverage-sqlite: integrations.cover.sqlite.test generate-ini-sqlite
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.cover.sqlite.test -test.coverprofile=integration.coverage.out
|
||||
|
||||
integrations.mysql.test: git-check $(GO_SOURCES)
|
||||
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration -o integrations.mysql.test
|
||||
|
||||
|
@ -724,12 +755,6 @@ integrations.pgsql.test: git-check $(GO_SOURCES)
|
|||
integrations.sqlite.test: git-check $(GO_SOURCES)
|
||||
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration -o integrations.sqlite.test -tags '$(TEST_TAGS)'
|
||||
|
||||
integrations.cover.test: git-check $(GO_SOURCES)
|
||||
$(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)
|
||||
$(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
|
||||
migrations.mysql.test: $(GO_SOURCES) generate-ini-mysql
|
||||
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration/migration-test -o migrations.mysql.test
|
||||
|
@ -746,34 +771,34 @@ migrations.sqlite.test: $(GO_SOURCES) generate-ini-sqlite
|
|||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTESTCOMPILEDRUNPREFIX) ./migrations.sqlite.test $(GOTESTCOMPILEDRUNSUFFIX)
|
||||
|
||||
.PHONY: migrations.individual.mysql.test
|
||||
migrations.individual.mysql.test: $(GO_SOURCES)
|
||||
migrations.individual.mysql.test: $(GO_SOURCES) | compute-migration-packages
|
||||
for pkg in $(MIGRATION_PACKAGES); do \
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg || exit 1; \
|
||||
done
|
||||
|
||||
.PHONY: migrations.individual.sqlite.test\#%
|
||||
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/migrations/$*
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/gitea_migrations/$*
|
||||
|
||||
.PHONY: migrations.individual.pgsql.test
|
||||
migrations.individual.pgsql.test: $(GO_SOURCES)
|
||||
migrations.individual.pgsql.test: $(GO_SOURCES) | compute-migration-packages
|
||||
for pkg in $(MIGRATION_PACKAGES); do \
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg || exit 1;\
|
||||
done
|
||||
|
||||
.PHONY: migrations.individual.pgsql.test\#%
|
||||
migrations.individual.pgsql.test\#%: $(GO_SOURCES) generate-ini-pgsql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/migrations/$*
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/gitea_migrations/$*
|
||||
|
||||
.PHONY: migrations.individual.sqlite.test
|
||||
migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite
|
||||
migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite | compute-migration-packages
|
||||
for pkg in $(MIGRATION_PACKAGES); do \
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg || exit 1; \
|
||||
done
|
||||
|
||||
.PHONY: migrations.individual.sqlite.test\#%
|
||||
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/migrations/$*
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/gitea_migrations/$*
|
||||
|
||||
e2e.mysql.test: $(GO_SOURCES)
|
||||
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/e2e -o e2e.mysql.test
|
||||
|
@ -940,6 +965,7 @@ fomantic:
|
|||
cd $(FOMANTIC_WORK_DIR) && npm install --no-save
|
||||
cp -f $(FOMANTIC_WORK_DIR)/theme.config.less $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/theme.config
|
||||
cp -rf $(FOMANTIC_WORK_DIR)/_site $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/
|
||||
rm -rf $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/themes/default/modules/dropdown.overrides
|
||||
$(SED_INPLACE) -e 's/ overrideBrowserslist\r/ overrideBrowserslist: ["defaults"]\r/g' $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/tasks/config/tasks.js
|
||||
cd $(FOMANTIC_WORK_DIR) && npx gulp -f node_modules/fomantic-ui/gulpfile.js build
|
||||
# fomantic uses "touchstart" as click event for some browsers, it's not ideal, so we force fomantic to always use "click" as click event
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
Hi there! Tired of big platforms playing monopoly?
|
||||
Providing Git hosting for your project, friends, company or community?
|
||||
**Forgejo** (/for'd͡ʒe.jo/ inspired by forĝejo – the Esperanto word for *forge*) has you covered with its intuitive interface,
|
||||
light and easy hosting and a lot of builtin functionality.
|
||||
light and easy hosting and a lot of built-in functionality.
|
||||
|
||||
Forgejo was [created in 2022](https://forgejo.org/2022-12-15-hello-forgejo/)
|
||||
because we think that the project should be owned by an independent community.
|
||||
|
|
|
@ -4,7 +4,7 @@ A minor or major Forgejo release is published every [three months](https://forge
|
|||
|
||||
A [patch or minor release](https://semver.org/spec/v2.0.0.html) (e.g. upgrading from v7.0.0 to v7.0.1 or v7.1.0) does not require manual intervention. But [major releases](https://semver.org/spec/v2.0.0.html#spec-item-8) where the first version number changes (e.g. upgrading from v1.21 to v7.0) contain breaking changes and the release notes explain how to deal with them.
|
||||
|
||||
The release notes of each release [are available in the release-notes-published directory of this repository](release-notes-published), starting with [Forgejo 7.0.7](release-notes-published/7.0.7.md) and [Forgejo 8.0.1](release-notes-published/8.0.1.md).
|
||||
The release notes of each release [are available in the release-notes-published directory of this repository](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/release-notes-published), starting with [Forgejo 7.0.7](release-notes-published/7.0.7.md) and [Forgejo 8.0.1](release-notes-published/8.0.1.md).
|
||||
|
||||
## 9.0.2
|
||||
|
||||
|
@ -130,6 +130,10 @@ A [companion blog post](https://forgejo.org/2024-07-release-v8-0/) provides addi
|
|||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/3334): <!--number 3334 --><!--number--><!--description Added support for the `workflow_dispatch` workflow trigger-->added support for the [`workflow_dispatch` trigger](https://forgejo.org/docs/v8.0/user/actions/#onworkflow_dispatch) in Forgejo Actions.<!--description-->
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/3307): <!--number 3307 --><!--number--><!--description Support [Proof Key for Code Exchange (PKCE - RFC7636)](https://www.rfc-editor.org/rfc/rfc7636) for external login using the OpenID Connect authentication source.-->support [Proof Key for Code Exchange (PKCE - RFC7636)](https://www.rfc-editor.org/rfc/rfc7636) for external login using the OpenID Connect authentication source.<!--description-->
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/3139): <!--number 3139 --><!--number--><!--description Allow hiding auto generated release archives-->allow hiding auto generated release archives.<!--description-->
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/3952): Update of Chroma from v2.13.0: to v2.14.0:
|
||||
- [`1e983e7`](https://github.com/alecthomas/chroma/commit/1e983e7) lexers/cue: support CUE attributes ([#​961](https://github.com/alecthomas/chroma/issues/961))
|
||||
- [`9347b55`](https://github.com/alecthomas/chroma/commit/9347b55) Add Gleam syntax highlighting ([#​959](https://github.com/alecthomas/chroma/issues/959))
|
||||
- [`2580aaa`](https://github.com/alecthomas/chroma/commit/2580aaa) Add Bazel bzlmod support into Python lexer ([#​947](https://github.com/alecthomas/chroma/issues/947))
|
||||
- **Bug fixes**
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/4732) ([backported from](https://codeberg.org/forgejo/forgejo/pulls/4715)): <!--number 4732 --><!--number--><!--description -->Show the AGit label on merged pull requests.<!--description-->
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/4689) ([backported from](https://codeberg.org/forgejo/forgejo/pulls/4687)): <!--number 4689 --><!--number--><!--description -->Fixed: issue state change via the API is not idempotent.<!--description-->
|
||||
|
@ -146,6 +150,9 @@ A [companion blog post](https://forgejo.org/2024-07-release-v8-0/) provides addi
|
|||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/3442): <!--number 3442 --><!--number--><!--description Save updated empty comments instead of skipping the update silently, [which prevented the removal of attachments of such comments](https://codeberg.org/forgejo/forgejo/issues/3424).-->Fixed: it is not possible to remove attachments from an empty comment.<!--description-->
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/3430): <!--number 3430 --><!--number--><!--description Fixed a bug where the `/api/v1/repos/{owner}/{repo}/wiki` API endpoints were using a hardcoded "master" branch for the wiki, rather than the branch they really use.-->Fixed: the `/api/v1/repos/{owner}/{repo}/wiki` API endpoints is using a hardcoded "master" branch for the wiki, rather than the branch they really use.<!--description-->
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/3379): <!--number 3379 --><!--number--><!--description -->Fixed: using the API to search for users, the results are not paged by default an the default paging limits are not respected.<!--description-->
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/3952): Update of Chroma from v2.13.0: to v2.14.0:
|
||||
- [`736c0ea`](https://github.com/alecthomas/chroma/commit/736c0ea) Typescript: Several fixes ([#​952](https://github.com/alecthomas/chroma/issues/952))
|
||||
- [`e5c25d0`](https://github.com/alecthomas/chroma/commit/e5c25d0) Org: Keep all newlines ([#​951](https://github.com/alecthomas/chroma/issues/951))
|
||||
- **Localization**
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/4661) ([backported from](https://codeberg.org/forgejo/forgejo/pulls/4568)): <!--number 4661 --><!--number--><!--description -->24 July updates<!--description-->
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/4565) ([backported from](https://codeberg.org/forgejo/forgejo/pulls/4451)): <!--number 4565 --><!--number--><!--description -->19 July updates<!--description-->
|
||||
|
|
284
assets/go-licenses.json
generated
284
assets/go-licenses.json
generated
File diff suppressed because one or more lines are too long
|
@ -74,7 +74,7 @@ func newFileCollector(fileFilter string, batchSize int) (*fileCollector, error)
|
|||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`tests/integration/migration-test`))
|
||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`modules/git/tests`))
|
||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`models/fixtures`))
|
||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`models/migrations/fixtures`))
|
||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`models/gitea_migrations/fixtures`))
|
||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`services/gitdiff/testdata`))
|
||||
}
|
||||
|
||||
|
@ -181,7 +181,7 @@ func parseArgs() (mainOptions map[string]string, subCmd string, subArgs []string
|
|||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
return mainOptions, subCmd, subArgs
|
||||
}
|
||||
|
||||
func showUsage() {
|
||||
|
|
44
build/lint-locale-usage/allowed-masked-usage.txt
Normal file
44
build/lint-locale-usage/allowed-masked-usage.txt
Normal file
|
@ -0,0 +1,44 @@
|
|||
# translation tooling test keys
|
||||
meta.last_line
|
||||
translation_meta.test
|
||||
|
||||
# models/admin/task.go: instances of $TranslatableMessage.Format
|
||||
# this also gets instantiated as a Messenger once
|
||||
repo.migrate.migrating_failed.error
|
||||
|
||||
# models/asymkey/gpg_key_object_verification.go: $ObjectVerification.Reason
|
||||
# unfortunately, it is non-trivial to parse all the occurences
|
||||
gpg.error.extract_sign
|
||||
gpg.error.failed_retrieval_gpg_keys
|
||||
gpg.error.generate_hash
|
||||
gpg.error.no_committer_account
|
||||
|
||||
# models/system/notice.go: func (n *Notice) TrStr() string
|
||||
admin.notices.type_1
|
||||
admin.notices.type_2
|
||||
|
||||
# modules/setting/ui.go
|
||||
themes.names.
|
||||
|
||||
# services/context/context.go
|
||||
relativetime.
|
||||
|
||||
# templates/repo/issue/view_content.tmpl: indirection via $closeTranslationKey
|
||||
repo.issues.close
|
||||
repo.pulls.close
|
||||
|
||||
# templates/repo/issue/view_content/comments.tmpl: indirection via $refTr
|
||||
repo.issues.ref_closing_from
|
||||
repo.issues.ref_issue_from
|
||||
repo.issues.ref_pull_from
|
||||
repo.issues.ref_reopening_from
|
||||
|
||||
# templates/repo/issue/view_content/comments.tmpl: ctx.Locale.Tr (printf "projects.type-%d.display_name" .OldProject.Type)
|
||||
projects.
|
||||
projects.type-1.display_name
|
||||
projects.type-2.display_name
|
||||
projects.type-3.display_name
|
||||
|
||||
# templates/repo/settings/webhook/link_menu.tmpl, templates/webhook/new.tmpl: repo.settings.web_hook_name_
|
||||
# tests/integration/repo_archive_text_test.go
|
||||
repo.settings.
|
177
build/lint-locale-usage/bin/handle-go.go
Normal file
177
build/lint-locale-usage/bin/handle-go.go
Normal file
|
@ -0,0 +1,177 @@
|
|||
// 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"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
llu "forgejo.org/build/lint-locale-usage"
|
||||
lluUnit "forgejo.org/models/unit/lint-locale-usage"
|
||||
lluMigrate "forgejo.org/services/migrations/lint-locale-usage"
|
||||
)
|
||||
|
||||
// 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 HandleGoFile(handler llu.Handler, fname string, src any) error {
|
||||
fset := token.NewFileSet()
|
||||
node, err := goParser.ParseFile(fset, fname, src, goParser.SkipObjectResolution|goParser.ParseComments)
|
||||
if err != nil {
|
||||
return llu.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, ...)`
|
||||
|
||||
switch n2 := n.(type) {
|
||||
case *ast.CallExpr:
|
||||
if len(n2.Args) == 0 {
|
||||
return true
|
||||
}
|
||||
funSel, ok := n2.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(n2.Args) <= int(argNum) {
|
||||
argc := len(n2.Args)
|
||||
gotUnexpectedInvoke = &argc
|
||||
} else {
|
||||
handler.HandleGoTrArgument(fset, n2.Args[int(argNum)], "")
|
||||
}
|
||||
}
|
||||
|
||||
if gotUnexpectedInvoke != nil {
|
||||
handler.OnUnexpectedInvoke(fset, funSel.Sel.NamePos, funSel.Sel.Name, *gotUnexpectedInvoke)
|
||||
}
|
||||
|
||||
case *ast.CompositeLit:
|
||||
if strings.HasSuffix(fname, "models/unit/unit.go") {
|
||||
lluUnit.HandleCompositeUnit(handler, fset, n2)
|
||||
}
|
||||
|
||||
case *ast.FuncDecl:
|
||||
matchInsPrefix := handler.HandleGoCommentGroup(fset, n2.Doc, "llu:returnsTrKey")
|
||||
if matchInsPrefix != nil {
|
||||
results := n2.Type.Results.List
|
||||
if len(results) != 1 {
|
||||
handler.OnWarning(fset, n2.Type.Func, fmt.Sprintf("function %s has unexpected return type; expected single return value", n2.Name.Name))
|
||||
return true
|
||||
}
|
||||
|
||||
ast.Inspect(n2.Body, func(n ast.Node) bool {
|
||||
// search for return stmts
|
||||
// TODO: what about nested functions?
|
||||
if ret, ok := n.(*ast.ReturnStmt); ok {
|
||||
for _, res := range ret.Results {
|
||||
ast.Inspect(res, func(n ast.Node) bool {
|
||||
if expr, ok := n.(ast.Expr); ok {
|
||||
handler.HandleGoTrArgument(fset, expr, *matchInsPrefix)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
if strings.HasSuffix(fname, "services/migrations/migrate.go") {
|
||||
lluMigrate.HandleMessengerInFunc(handler, fset, n2)
|
||||
}
|
||||
return true
|
||||
case *ast.GenDecl:
|
||||
switch n2.Tok {
|
||||
case token.CONST, token.VAR:
|
||||
matchInsPrefix := handler.HandleGoCommentGroup(fset, n2.Doc, " llu:TrKeys")
|
||||
if matchInsPrefix == nil {
|
||||
return true
|
||||
}
|
||||
for _, spec := range n2.Specs {
|
||||
// interpret all contained strings as message IDs
|
||||
ast.Inspect(spec, func(n ast.Node) bool {
|
||||
if argLit, ok := n.(*ast.BasicLit); ok {
|
||||
handler.HandleGoTrBasicLit(fset, argLit, *matchInsPrefix)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
case token.TYPE:
|
||||
// modules/web/middleware/binding.go:Validate uses the convention that structs
|
||||
// entries can have tags.
|
||||
// In particular, `locale:$msgid` should be handled; any fields with `form:-` shouldn't.
|
||||
// Problem: we don't know which structs are forms, actually.
|
||||
|
||||
for _, spec := range n2.Specs {
|
||||
tspec := spec.(*ast.TypeSpec)
|
||||
structNode, ok := tspec.Type.(*ast.StructType)
|
||||
if !ok || !(strings.HasSuffix(tspec.Name.Name, "Form") ||
|
||||
(tspec.Doc != nil &&
|
||||
slices.ContainsFunc(tspec.Doc.List, func(c *ast.Comment) bool {
|
||||
return c.Text == "// swagger:model"
|
||||
}))) {
|
||||
continue
|
||||
}
|
||||
for _, field := range structNode.Fields.List {
|
||||
if field.Names == nil {
|
||||
continue
|
||||
}
|
||||
if len(field.Names) != 1 {
|
||||
handler.OnWarning(fset, field.Type.Pos(), "unsupported multiple field names")
|
||||
continue
|
||||
}
|
||||
msgidPos := field.Names[0].NamePos
|
||||
msgid := "form." + field.Names[0].Name
|
||||
if field.Tag != nil && field.Tag.Kind == token.STRING {
|
||||
rawTag, err := strconv.Unquote(field.Tag.Value)
|
||||
if err != nil {
|
||||
handler.OnWarning(fset, field.Tag.ValuePos, "invalid tag value encountered")
|
||||
continue
|
||||
}
|
||||
tag := reflect.StructTag(rawTag)
|
||||
if tag.Get("form") == "-" {
|
||||
continue
|
||||
}
|
||||
tmp := tag.Get("locale")
|
||||
if len(tmp) != 0 {
|
||||
msgidPos = field.Tag.ValuePos
|
||||
msgid = tmp
|
||||
}
|
||||
}
|
||||
handler.OnMsgid(fset, msgidPos, msgid, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
345
build/lint-locale-usage/bin/lint-locale-usage.go
Normal file
345
build/lint-locale-usage/bin/lint-locale-usage.go
Normal file
|
@ -0,0 +1,345 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
llu "forgejo.org/build/lint-locale-usage"
|
||||
"forgejo.org/modules/container"
|
||||
"forgejo.org/modules/translation/localeiter"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
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 StringTrie interface {
|
||||
Matches(key []string) bool
|
||||
}
|
||||
|
||||
type StringTrieMap map[string]StringTrie
|
||||
|
||||
func (m StringTrieMap) Matches(key []string) bool {
|
||||
if len(key) == 0 || m == nil {
|
||||
return true
|
||||
}
|
||||
value, ok := m[key[0]]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if value == nil {
|
||||
return true
|
||||
}
|
||||
return value.Matches(key[1:])
|
||||
}
|
||||
|
||||
func (m StringTrieMap) Insert(key []string) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch len(key) {
|
||||
case 0:
|
||||
return
|
||||
|
||||
case 1:
|
||||
m[key[0]] = nil
|
||||
|
||||
default:
|
||||
if value, ok := m[key[0]]; ok {
|
||||
if value == nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
m[key[0]] = make(StringTrieMap)
|
||||
}
|
||||
m[key[0]].(StringTrieMap).Insert(key[1:])
|
||||
}
|
||||
}
|
||||
|
||||
func ParseAllowedMaskedUsages(fname string, usedMsgids container.Set[string], allowedMaskedPrefixes StringTrieMap, chkMsgid func(msgid string) bool) error {
|
||||
file, err := os.Open(fname)
|
||||
if err != nil {
|
||||
return llu.LocatedError{
|
||||
Location: fname,
|
||||
Kind: "Open",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
lno := 0
|
||||
for scanner.Scan() {
|
||||
lno++
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
if linePrefix, found := strings.CutSuffix(line, "."); found {
|
||||
allowedMaskedPrefixes.Insert(strings.Split(linePrefix, "."))
|
||||
} else {
|
||||
if !chkMsgid(line) {
|
||||
return llu.LocatedError{
|
||||
Location: fmt.Sprintf("%s: line %d", fname, lno),
|
||||
Kind: "undefined msgid",
|
||||
Err: errors.New(line),
|
||||
}
|
||||
}
|
||||
usedMsgids.Add(line)
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return llu.LocatedError{
|
||||
Location: fname,
|
||||
Kind: "Scanner",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Usage() {
|
||||
outp := flag.CommandLine.Output()
|
||||
fmt.Fprintf(outp, "Usage of %s:\n", os.Args[0])
|
||||
flag.PrintDefaults()
|
||||
|
||||
fmt.Fprintf(outp, "\nThis command assumes that it gets started from the project root directory.\n")
|
||||
|
||||
fmt.Fprintf(outp, "\nExit codes:\n")
|
||||
for _, i := range []string{
|
||||
"0\tsuccess, no issues found",
|
||||
"1\tunable to walk directory tree",
|
||||
"2\tunable to parse locale ini/json files",
|
||||
"3\tunable to parse go or text/template files",
|
||||
"4\tfound missing message IDs",
|
||||
"5\tfound unused message IDs",
|
||||
} {
|
||||
fmt.Fprintf(outp, "\t%s\n", i)
|
||||
}
|
||||
|
||||
fmt.Fprintf(outp, "\nSpecial Go doc comments:\n")
|
||||
for _, i := range []string{
|
||||
"//llu:returnsTrKey",
|
||||
"\tcan be used in front of functions to indicate",
|
||||
"\tthat the function returns message IDs",
|
||||
"\tWARNING: this currently doesn't support nested functions properly",
|
||||
"",
|
||||
"//llu:returnsTrKeySuffix prefix.",
|
||||
"\tsimilar to llu:returnsTrKey, but the given prefix is prepended",
|
||||
"\tto the found strings before interpreting them as msgids",
|
||||
"",
|
||||
"// llu:TrKeys",
|
||||
"\tcan be used in front of 'const' and 'var' blocks",
|
||||
"\tin order to mark all contained strings as message IDs",
|
||||
"",
|
||||
"// llu:TrKeysSuffix prefix.",
|
||||
"\tlike llu:returnsTrKeySuffix, but for 'const' and 'var' blocks",
|
||||
} {
|
||||
if i == "" {
|
||||
fmt.Fprintf(outp, "\n")
|
||||
} else {
|
||||
fmt.Fprintf(outp, "\t%s\n", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:forbidigo
|
||||
func main() {
|
||||
allowMissingMsgids := false
|
||||
allowUnusedMsgids := false
|
||||
allowWeakMissingMsgids := true
|
||||
usedMsgids := make(container.Set[string])
|
||||
allowedMaskedPrefixes := make(StringTrieMap)
|
||||
|
||||
// It's possible for execl to hand us an empty os.Args.
|
||||
if len(os.Args) == 0 {
|
||||
flag.CommandLine = flag.NewFlagSet("lint-locale-usage", flag.ExitOnError)
|
||||
} else {
|
||||
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
|
||||
}
|
||||
flag.CommandLine.Usage = Usage
|
||||
flag.Usage = Usage
|
||||
|
||||
flag.BoolVar(
|
||||
&allowMissingMsgids,
|
||||
"allow-missing-msgids",
|
||||
false,
|
||||
"don't return an error code if missing message IDs are found",
|
||||
)
|
||||
flag.BoolVar(
|
||||
&allowWeakMissingMsgids,
|
||||
"allow-weak-missing-msgids",
|
||||
true,
|
||||
"Don't return an error code if missing 'weak' (e.g. \"form.$msgid\") message IDs are found",
|
||||
)
|
||||
flag.BoolVar(
|
||||
&allowUnusedMsgids,
|
||||
"allow-unused-msgids",
|
||||
false,
|
||||
"don't return an error code if unused message IDs are found",
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
flag.Func(
|
||||
"allow-masked-usages-from",
|
||||
"supply a file containing a newline-separated list of allowed masked usages",
|
||||
func(argval string) error {
|
||||
return ParseAllowedMaskedUsages(argval, usedMsgids, allowedMaskedPrefixes, func(msgid string) bool {
|
||||
return msgids.Contains(msgid)
|
||||
})
|
||||
},
|
||||
)
|
||||
flag.Parse()
|
||||
|
||||
onError := func(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(3)
|
||||
}
|
||||
|
||||
handler := llu.Handler{
|
||||
OnMsgidPrefix: func(fset *token.FileSet, pos token.Pos, msgidPrefix string, truncated bool) {
|
||||
msgidPrefixSplit := strings.Split(msgidPrefix, ".")
|
||||
if !truncated {
|
||||
allowedMaskedPrefixes.Insert(msgidPrefixSplit)
|
||||
} else if !allowedMaskedPrefixes.Matches(msgidPrefixSplit) {
|
||||
gotAnyMsgidError = true
|
||||
fmt.Printf("%s:\tmissing msgid prefix: %s\n", fset.Position(pos).String(), msgidPrefix)
|
||||
}
|
||||
},
|
||||
OnMsgid: func(fset *token.FileSet, pos token.Pos, msgid string, weak bool) {
|
||||
if !msgids.Contains(msgid) {
|
||||
if weak && allowWeakMissingMsgids {
|
||||
return
|
||||
}
|
||||
gotAnyMsgidError = true
|
||||
fmt.Printf("%s:\tmissing msgid: %s\n", fset.Position(pos).String(), msgid)
|
||||
} else {
|
||||
usedMsgids.Add(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)
|
||||
},
|
||||
OnWarning: func(fset *token.FileSet, pos token.Pos, msg string) {
|
||||
fmt.Printf("%s:\tWARNING: %s\n", fset.Position(pos).String(), msg)
|
||||
},
|
||||
LocaleTrFunctions: llu.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(HandleGoFile(handler, 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)
|
||||
}
|
||||
|
||||
unusedMsgids := []string{}
|
||||
|
||||
for msgid := range msgids {
|
||||
if !usedMsgids.Contains(msgid) && !allowedMaskedPrefixes.Matches(strings.Split(msgid, ".")) {
|
||||
unusedMsgids = append(unusedMsgids, msgid)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(unusedMsgids)
|
||||
|
||||
if len(unusedMsgids) != 0 {
|
||||
fmt.Printf("=== unused msgids (%d): ===\n", len(unusedMsgids))
|
||||
for _, msgid := range unusedMsgids {
|
||||
fmt.Printf("- %s\n", msgid)
|
||||
}
|
||||
}
|
||||
|
||||
if !allowMissingMsgids && gotAnyMsgidError {
|
||||
os.Exit(4)
|
||||
}
|
||||
if !allowUnusedMsgids && len(unusedMsgids) != 0 {
|
||||
os.Exit(5)
|
||||
}
|
||||
}
|
|
@ -7,24 +7,26 @@ import (
|
|||
"go/token"
|
||||
"testing"
|
||||
|
||||
llu "forgejo.org/build/lint-locale-usage"
|
||||
|
||||
"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) {
|
||||
func buildHandler(ret *[]string) llu.Handler {
|
||||
return llu.Handler{
|
||||
OnMsgid: func(fset *token.FileSet, pos token.Pos, msgid string, weak bool) {
|
||||
*ret = append(*ret, msgid)
|
||||
},
|
||||
OnUnexpectedInvoke: func(fset *token.FileSet, pos token.Pos, funcname string, argc int) {},
|
||||
LocaleTrFunctions: InitLocaleTrFunctions(),
|
||||
LocaleTrFunctions: llu.InitLocaleTrFunctions(),
|
||||
}
|
||||
}
|
||||
|
||||
func HandleGoFileWrapped(t *testing.T, fname, src string) []string {
|
||||
var ret []string
|
||||
handler := buildHandler(&ret)
|
||||
require.NoError(t, handler.HandleGoFile(fname, src))
|
||||
require.NoError(t, HandleGoFile(handler, fname, src))
|
||||
return ret
|
||||
}
|
||||
|
88
build/lint-locale-usage/handle-go.go
Normal file
88
build/lint-locale-usage/handle-go.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package lintLocaleUsage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (handler Handler) HandleGoTrBasicLit(fset *token.FileSet, argLit *ast.BasicLit, prefix string) {
|
||||
if argLit.Kind == token.STRING {
|
||||
// extract string content
|
||||
arg, err := strconv.Unquote(argLit.Value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// found interesting strings
|
||||
arg = prefix + arg
|
||||
if strings.HasSuffix(arg, ".") || strings.HasSuffix(arg, "_") {
|
||||
prep, trunc := PrepareMsgidPrefix(arg)
|
||||
if trunc {
|
||||
handler.OnWarning(fset, argLit.ValuePos, fmt.Sprintf("needed to truncate message id prefix: %s", arg))
|
||||
}
|
||||
handler.OnMsgidPrefix(fset, argLit.ValuePos, prep, trunc)
|
||||
} else {
|
||||
handler.OnMsgid(fset, argLit.ValuePos, arg, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (handler Handler) HandleGoTrArgument(fset *token.FileSet, n ast.Expr, prefix string) {
|
||||
if argLit, ok := n.(*ast.BasicLit); ok {
|
||||
handler.HandleGoTrBasicLit(fset, argLit, prefix)
|
||||
} else if argBinExpr, ok := n.(*ast.BinaryExpr); ok {
|
||||
if argBinExpr.Op != token.ADD {
|
||||
// pass
|
||||
} else if argLit, ok := argBinExpr.X.(*ast.BasicLit); ok && argLit.Kind == token.STRING {
|
||||
// extract string content
|
||||
arg, err := strconv.Unquote(argLit.Value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// found interesting strings
|
||||
arg = prefix + arg
|
||||
prep, trunc := PrepareMsgidPrefix(arg)
|
||||
if trunc {
|
||||
handler.OnWarning(fset, argLit.ValuePos, fmt.Sprintf("needed to truncate message id prefix: %s", arg))
|
||||
}
|
||||
handler.OnMsgidPrefix(fset, argLit.ValuePos, prep, trunc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (handler Handler) HandleGoCommentGroup(fset *token.FileSet, cg *ast.CommentGroup, commentPrefix string) *string {
|
||||
if cg == nil {
|
||||
return nil
|
||||
}
|
||||
var matches []token.Pos
|
||||
matchInsPrefix := ""
|
||||
commentPrefix = "//" + commentPrefix
|
||||
for _, comment := range cg.List {
|
||||
ctxt := strings.TrimSpace(comment.Text)
|
||||
if ctxt == commentPrefix {
|
||||
matches = append(matches, comment.Slash)
|
||||
} else if after, found := strings.CutPrefix(ctxt, commentPrefix+"Suffix "); found {
|
||||
matches = append(matches, comment.Slash)
|
||||
matchInsPrefix = strings.TrimSpace(after)
|
||||
}
|
||||
}
|
||||
switch len(matches) {
|
||||
case 0:
|
||||
return nil
|
||||
case 1:
|
||||
return &matchInsPrefix
|
||||
default:
|
||||
handler.OnWarning(
|
||||
fset,
|
||||
matches[0],
|
||||
fmt.Sprintf("encountered multiple %s... directives, ignoring", strings.TrimSpace(commentPrefix)),
|
||||
)
|
||||
return &matchInsPrefix
|
||||
}
|
||||
}
|
244
build/lint-locale-usage/handle-tmpl.go
Normal file
244
build/lint-locale-usage/handle-tmpl.go
Normal file
|
@ -0,0 +1,244 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package lintLocaleUsage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
tmplParser "text/template/parse"
|
||||
|
||||
fjTemplates "forgejo.org/modules/templates"
|
||||
"forgejo.org/modules/util"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
funcname := ""
|
||||
switch nodeCommand.Args[0].Type() {
|
||||
case tmplParser.NodeChain:
|
||||
nodeChain := nodeCommand.Args[0].(*tmplParser.ChainNode)
|
||||
if nodeIdent, ok := nodeChain.Node.(*tmplParser.IdentifierNode); ok {
|
||||
if nodeIdent.Ident != "ctx" || len(nodeChain.Field) != 2 || nodeChain.Field[0] != "Locale" {
|
||||
return
|
||||
}
|
||||
funcname = nodeChain.Field[1]
|
||||
}
|
||||
|
||||
case tmplParser.NodeField:
|
||||
nodeField := nodeCommand.Args[0].(*tmplParser.FieldNode)
|
||||
if len(nodeField.Ident) != 2 || !(nodeField.Ident[0] == "locale" || nodeField.Ident[0] == "Locale") {
|
||||
return
|
||||
}
|
||||
funcname = nodeField.Ident[1]
|
||||
|
||||
case tmplParser.NodeVariable:
|
||||
nodeVar := nodeCommand.Args[0].(*tmplParser.VariableNode)
|
||||
if len(nodeVar.Ident) != 3 || !(nodeVar.Ident[0] == "$" && nodeVar.Ident[1] == "locale") {
|
||||
return
|
||||
}
|
||||
funcname = nodeVar.Ident[2]
|
||||
}
|
||||
|
||||
var gotUnexpectedInvoke *int
|
||||
ltf, ok := handler.LocaleTrFunctions[funcname]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
for _, argNum := range ltf {
|
||||
if len(nodeCommand.Args) >= int(argNum+2) {
|
||||
handler.handleTemplateMsgid(fset, nodeCommand.Args[int(argNum+1)])
|
||||
} else {
|
||||
argc := len(nodeCommand.Args) - 1
|
||||
gotUnexpectedInvoke = &argc
|
||||
}
|
||||
}
|
||||
|
||||
if gotUnexpectedInvoke != nil {
|
||||
handler.OnUnexpectedInvoke(fset, token.Pos(nodeCommand.Pos), funcname, *gotUnexpectedInvoke)
|
||||
}
|
||||
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (handler Handler) handleTemplateMsgid(fset *token.FileSet, node tmplParser.Node) {
|
||||
// the column numbers are a bit "off", but much better than nothing
|
||||
pos := token.Pos(node.Position())
|
||||
|
||||
switch node.Type() {
|
||||
case tmplParser.NodeString:
|
||||
nodeString := node.(*tmplParser.StringNode)
|
||||
// found interesting strings
|
||||
handler.OnMsgid(fset, pos, nodeString.Text, false)
|
||||
|
||||
case tmplParser.NodePipe:
|
||||
nodePipe := node.(*tmplParser.PipeNode)
|
||||
handler.handleTemplatePipeNode(fset, nodePipe)
|
||||
|
||||
if len(nodePipe.Cmds) == 0 {
|
||||
handler.OnWarning(fset, pos, fmt.Sprintf("unsupported invocation of locate function (no commands): %s", node.String()))
|
||||
} else if len(nodePipe.Cmds) != 1 {
|
||||
handler.OnWarning(fset, pos, fmt.Sprintf("unsupported invocation of locate function (too many commands): %s", node.String()))
|
||||
return
|
||||
}
|
||||
nodeCommand := nodePipe.Cmds[0]
|
||||
if len(nodeCommand.Args) < 2 {
|
||||
handler.OnWarning(fset, pos, fmt.Sprintf("unsupported invocation of locate function (not enough arguments): %s", node.String()))
|
||||
return
|
||||
}
|
||||
|
||||
nodeIdent, ok := nodeCommand.Args[0].(*tmplParser.IdentifierNode)
|
||||
if !ok || (nodeIdent.Ident != "print" && nodeIdent.Ident != "printf") {
|
||||
// handler.OnWarning(fset, pos, fmt.Sprintf("unsupported invocation of locate function (bad command): %s", node.String()))
|
||||
return
|
||||
}
|
||||
|
||||
nodeString, ok := nodeCommand.Args[1].(*tmplParser.StringNode)
|
||||
if !ok {
|
||||
//handler.OnWarning(
|
||||
// fset,
|
||||
// pos,
|
||||
// fmt.Sprintf("unsupported invocation of locate function (string should be first argument to %s): %s", nodeIdent.Ident, node.String()),
|
||||
//)
|
||||
return
|
||||
}
|
||||
|
||||
msgidPrefix := nodeString.Text
|
||||
stringPos := token.Pos(nodeString.Pos)
|
||||
|
||||
if len(nodeCommand.Args) == 2 {
|
||||
// found interesting strings
|
||||
handler.OnMsgid(fset, stringPos, msgidPrefix, false)
|
||||
} else {
|
||||
if nodeIdent.Ident == "printf" {
|
||||
parts := strings.SplitN(msgidPrefix, "%", 2)
|
||||
if len(parts) != 2 {
|
||||
handler.OnWarning(
|
||||
fset,
|
||||
stringPos,
|
||||
fmt.Sprintf("unsupported invocation of locate function (format string doesn't match \"prefix%%smth\" pattern): %s", nodeString.String()),
|
||||
)
|
||||
return
|
||||
}
|
||||
msgidPrefix = parts[0]
|
||||
}
|
||||
|
||||
msgidPrefixFin, truncated := PrepareMsgidPrefix(msgidPrefix)
|
||||
if truncated {
|
||||
handler.OnWarning(fset, stringPos, fmt.Sprintf("needed to truncate message id prefix: %s", msgidPrefix))
|
||||
}
|
||||
|
||||
// found interesting strings
|
||||
handler.OnMsgidPrefix(fset, stringPos, msgidPrefixFin, truncated)
|
||||
}
|
||||
|
||||
default:
|
||||
// handler.OnWarning(fset, pos, fmt.Sprintf("unknown invocation of locate function: %s", node.String()))
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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) 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
|
||||
}
|
62
build/lint-locale-usage/handler.go
Normal file
62
build/lint-locale-usage/handler.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package lintLocaleUsage
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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, weak bool)
|
||||
OnMsgidPrefix func(fset *token.FileSet, pos token.Pos, msgidPrefix string, truncated bool)
|
||||
OnUnexpectedInvoke func(fset *token.FileSet, pos token.Pos, funcname string, argc int)
|
||||
OnWarning func(fset *token.FileSet, pos token.Pos, msg string)
|
||||
LocaleTrFunctions map[string][]uint
|
||||
}
|
||||
|
||||
// Truncating a message id prefix to the last dot
|
||||
func PrepareMsgidPrefix(s string) (string, bool) {
|
||||
index := strings.LastIndexByte(s, 0x2e)
|
||||
if index == -1 {
|
||||
return "", true
|
||||
}
|
||||
return s[:index], index != len(s)-1
|
||||
}
|
|
@ -1,383 +0,0 @@
|
|||
// 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)
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ func subcmdActionsGenRunnerToken() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "generate-runner-token",
|
||||
Usage: "Generate a new token for a runner to use to register with the server",
|
||||
Before: noDanglingArgs,
|
||||
Action: runGenerateActionsRunnerToken,
|
||||
Aliases: []string{"grt"},
|
||||
Flags: []cli.Flag{
|
||||
|
|
|
@ -37,6 +37,7 @@ func subcmdRepoSyncReleases() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "repo-sync-releases",
|
||||
Usage: "Synchronize repository releases with tags",
|
||||
Before: noDanglingArgs,
|
||||
Action: runRepoSyncReleases,
|
||||
}
|
||||
}
|
||||
|
@ -75,6 +76,7 @@ func subcmdSendMail() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "sendmail",
|
||||
Usage: "Send a message to all users",
|
||||
Before: noDanglingArgs,
|
||||
Action: runSendMail,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
@ -103,7 +105,7 @@ func idFlag() *cli.Int64Flag {
|
|||
}
|
||||
}
|
||||
|
||||
func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error {
|
||||
func runRepoSyncReleases(ctx context.Context, c *cli.Command) error {
|
||||
ctx, cancel := installSignals(ctx)
|
||||
defer cancel()
|
||||
|
||||
|
|
|
@ -17,11 +17,21 @@ import (
|
|||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
type (
|
||||
authService struct {
|
||||
initDB func(ctx context.Context) error
|
||||
createAuthSource func(context.Context, *auth_model.Source) error
|
||||
updateAuthSource func(context.Context, *auth_model.Source) error
|
||||
getAuthSourceByID func(ctx context.Context, id int64) (*auth_model.Source, error)
|
||||
}
|
||||
)
|
||||
|
||||
func microcmdAuthDelete() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "delete",
|
||||
Usage: "Delete specific auth source",
|
||||
Flags: []cli.Flag{idFlag()},
|
||||
Before: noDanglingArgs,
|
||||
Action: runDeleteAuth,
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +40,7 @@ func microcmdAuthList() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "list",
|
||||
Usage: "List auth sources",
|
||||
Before: noDanglingArgs,
|
||||
Action: runListAuth,
|
||||
Flags: []cli.Flag{
|
||||
&cli.IntFlag{
|
||||
|
@ -60,6 +71,16 @@ func microcmdAuthList() *cli.Command {
|
|||
}
|
||||
}
|
||||
|
||||
// newAuthService creates a service with default functions.
|
||||
func newAuthService() *authService {
|
||||
return &authService{
|
||||
initDB: initDB,
|
||||
createAuthSource: auth_model.CreateSource,
|
||||
updateAuthSource: auth_model.UpdateSource,
|
||||
getAuthSourceByID: auth_model.GetSourceByID,
|
||||
}
|
||||
}
|
||||
|
||||
func runListAuth(ctx context.Context, c *cli.Command) error {
|
||||
ctx, cancel := installSignals(ctx)
|
||||
defer cancel()
|
||||
|
|
|
@ -14,15 +14,6 @@ import (
|
|||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
type (
|
||||
authService struct {
|
||||
initDB func(ctx context.Context) error
|
||||
createAuthSource func(context.Context, *auth.Source) error
|
||||
updateAuthSource func(context.Context, *auth.Source) error
|
||||
getAuthSourceByID func(ctx context.Context, id int64) (*auth.Source, error)
|
||||
}
|
||||
)
|
||||
|
||||
func commonLdapCLIFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
@ -142,8 +133,9 @@ func ldapSimpleAuthCLIFlags() []cli.Flag {
|
|||
|
||||
func microcmdAuthAddLdapBindDn() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "add-ldap",
|
||||
Usage: "Add new LDAP (via Bind DN) authentication source",
|
||||
Name: "add-ldap",
|
||||
Usage: "Add new LDAP (via Bind DN) authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Action: func(ctx context.Context, cli *cli.Command) error {
|
||||
return newAuthService().addLdapBindDn(ctx, cli)
|
||||
},
|
||||
|
@ -153,8 +145,9 @@ func microcmdAuthAddLdapBindDn() *cli.Command {
|
|||
|
||||
func microcmdAuthUpdateLdapBindDn() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "update-ldap",
|
||||
Usage: "Update existing LDAP (via Bind DN) authentication source",
|
||||
Name: "update-ldap",
|
||||
Usage: "Update existing LDAP (via Bind DN) authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Action: func(ctx context.Context, cli *cli.Command) error {
|
||||
return newAuthService().updateLdapBindDn(ctx, cli)
|
||||
},
|
||||
|
@ -164,8 +157,9 @@ func microcmdAuthUpdateLdapBindDn() *cli.Command {
|
|||
|
||||
func microcmdAuthAddLdapSimpleAuth() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "add-ldap-simple",
|
||||
Usage: "Add new LDAP (simple auth) authentication source",
|
||||
Name: "add-ldap-simple",
|
||||
Usage: "Add new LDAP (simple auth) authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Action: func(ctx context.Context, cli *cli.Command) error {
|
||||
return newAuthService().addLdapSimpleAuth(ctx, cli)
|
||||
},
|
||||
|
@ -175,8 +169,9 @@ func microcmdAuthAddLdapSimpleAuth() *cli.Command {
|
|||
|
||||
func microcmdAuthUpdateLdapSimpleAuth() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "update-ldap-simple",
|
||||
Usage: "Update existing LDAP (simple auth) authentication source",
|
||||
Name: "update-ldap-simple",
|
||||
Usage: "Update existing LDAP (simple auth) authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Action: func(ctx context.Context, cli *cli.Command) error {
|
||||
return newAuthService().updateLdapSimpleAuth(ctx, cli)
|
||||
},
|
||||
|
@ -184,16 +179,6 @@ func microcmdAuthUpdateLdapSimpleAuth() *cli.Command {
|
|||
}
|
||||
}
|
||||
|
||||
// newAuthService creates a service with default functions.
|
||||
func newAuthService() *authService {
|
||||
return &authService{
|
||||
initDB: initDB,
|
||||
createAuthSource: auth.CreateSource,
|
||||
updateAuthSource: auth.UpdateSource,
|
||||
getAuthSourceByID: auth.GetSourceByID,
|
||||
}
|
||||
}
|
||||
|
||||
// parseAuthSource assigns values on authSource according to command line flags.
|
||||
func parseAuthSource(c *cli.Command, authSource *auth.Source) {
|
||||
if c.IsSet("name") {
|
||||
|
|
|
@ -86,6 +86,11 @@ func oauthCLIFlags() []cli.Flag {
|
|||
Value: nil,
|
||||
Usage: "Scopes to request when to authenticate against this OAuth2 source",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "attribute-ssh-public-key",
|
||||
Value: "",
|
||||
Usage: "Claim name providing SSH public keys for this source",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "required-claim-name",
|
||||
Value: "",
|
||||
|
@ -120,6 +125,10 @@ func oauthCLIFlags() []cli.Flag {
|
|||
Name: "group-team-map-removal",
|
||||
Usage: "Activate automatic team membership removal depending on groups",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "allow-username-change",
|
||||
Usage: "Allow users to change their username",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,7 +136,8 @@ func microcmdAuthAddOauth() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "add-oauth",
|
||||
Usage: "Add new Oauth authentication source",
|
||||
Action: runAddOauth,
|
||||
Before: noDanglingArgs,
|
||||
Action: newAuthService().addOauth,
|
||||
Flags: oauthCLIFlags(),
|
||||
}
|
||||
}
|
||||
|
@ -136,7 +146,8 @@ func microcmdAuthUpdateOauth() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "update-oauth",
|
||||
Usage: "Update existing Oauth authentication source",
|
||||
Action: runUpdateOauth,
|
||||
Before: noDanglingArgs,
|
||||
Action: newAuthService().updateOauth,
|
||||
Flags: append(oauthCLIFlags()[:1], append([]cli.Flag{idFlag()}, oauthCLIFlags()[1:]...)...),
|
||||
}
|
||||
}
|
||||
|
@ -163,6 +174,7 @@ func parseOAuth2Config(_ context.Context, c *cli.Command) *oauth2.Source {
|
|||
IconURL: c.String("icon-url"),
|
||||
SkipLocalTwoFA: c.Bool("skip-local-2fa"),
|
||||
Scopes: c.StringSlice("scopes"),
|
||||
AttributeSSHPublicKey: c.String("attribute-ssh-public-key"),
|
||||
RequiredClaimName: c.String("required-claim-name"),
|
||||
RequiredClaimValue: c.String("required-claim-value"),
|
||||
GroupClaimName: c.String("group-claim-name"),
|
||||
|
@ -170,14 +182,15 @@ func parseOAuth2Config(_ context.Context, c *cli.Command) *oauth2.Source {
|
|||
RestrictedGroup: c.String("restricted-group"),
|
||||
GroupTeamMap: c.String("group-team-map"),
|
||||
GroupTeamMapRemoval: c.Bool("group-team-map-removal"),
|
||||
AllowUsernameChange: c.Bool("allow-username-change"),
|
||||
}
|
||||
}
|
||||
|
||||
func runAddOauth(ctx context.Context, c *cli.Command) error {
|
||||
func (a *authService) addOauth(ctx context.Context, c *cli.Command) error {
|
||||
ctx, cancel := installSignals(ctx)
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
if err := a.initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -189,7 +202,7 @@ func runAddOauth(ctx context.Context, c *cli.Command) error {
|
|||
}
|
||||
}
|
||||
|
||||
return auth_model.CreateSource(ctx, &auth_model.Source{
|
||||
return a.createAuthSource(ctx, &auth_model.Source{
|
||||
Type: auth_model.OAuth2,
|
||||
Name: c.String("name"),
|
||||
IsActive: true,
|
||||
|
@ -197,7 +210,7 @@ func runAddOauth(ctx context.Context, c *cli.Command) error {
|
|||
})
|
||||
}
|
||||
|
||||
func runUpdateOauth(ctx context.Context, c *cli.Command) error {
|
||||
func (a *authService) updateOauth(ctx context.Context, c *cli.Command) error {
|
||||
if !c.IsSet("id") {
|
||||
return errors.New("--id flag is missing")
|
||||
}
|
||||
|
@ -205,11 +218,11 @@ func runUpdateOauth(ctx context.Context, c *cli.Command) error {
|
|||
ctx, cancel := installSignals(ctx)
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
if err := a.initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
source, err := auth_model.GetSourceByID(ctx, c.Int64("id"))
|
||||
source, err := a.getAuthSourceByID(ctx, c.Int64("id"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -244,6 +257,10 @@ func runUpdateOauth(ctx context.Context, c *cli.Command) error {
|
|||
oAuth2Config.Scopes = c.StringSlice("scopes")
|
||||
}
|
||||
|
||||
if c.IsSet("attribute-ssh-public-key") {
|
||||
oAuth2Config.AttributeSSHPublicKey = c.String("attribute-ssh-public-key")
|
||||
}
|
||||
|
||||
if c.IsSet("required-claim-name") {
|
||||
oAuth2Config.RequiredClaimName = c.String("required-claim-name")
|
||||
}
|
||||
|
@ -267,6 +284,10 @@ func runUpdateOauth(ctx context.Context, c *cli.Command) error {
|
|||
oAuth2Config.GroupTeamMapRemoval = c.Bool("group-team-map-removal")
|
||||
}
|
||||
|
||||
if c.IsSet("allow-username-change") {
|
||||
oAuth2Config.AllowUsernameChange = c.Bool("allow-username-change")
|
||||
}
|
||||
|
||||
// update custom URL mapping
|
||||
customURLMapping := &oauth2.CustomURLMapping{}
|
||||
|
||||
|
@ -300,5 +321,5 @@ func runUpdateOauth(ctx context.Context, c *cli.Command) error {
|
|||
oAuth2Config.CustomURLMapping = customURLMapping
|
||||
source.Cfg = oAuth2Config
|
||||
|
||||
return auth_model.UpdateSource(ctx, source)
|
||||
return a.updateAuthSource(ctx, source)
|
||||
}
|
||||
|
|
706
cmd/admin_auth_oauth_test.go
Normal file
706
cmd/admin_auth_oauth_test.go
Normal file
|
@ -0,0 +1,706 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"forgejo.org/models/auth"
|
||||
"forgejo.org/modules/test"
|
||||
"forgejo.org/services/auth/source/oauth2"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
func TestAddOauth(t *testing.T) {
|
||||
// Mock cli functions to do not exit on error
|
||||
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
|
||||
|
||||
// Test cases
|
||||
cases := []struct {
|
||||
args []string
|
||||
source *auth.Source
|
||||
errMsg string
|
||||
}{
|
||||
// case 0
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--name", "oauth2 (via openidConnect) source full",
|
||||
"--provider", "openidConnect",
|
||||
"--key", "client id",
|
||||
"--secret", "client secret",
|
||||
"--auto-discover-url", "https://example.com/.well-known/openid-configuration",
|
||||
"--use-custom-urls", "",
|
||||
"--custom-tenant-id", "tenant id",
|
||||
"--custom-auth-url", "https://example.com/auth",
|
||||
"--custom-token-url", "https://example.com/token",
|
||||
"--custom-profile-url", "https://example.com/profile",
|
||||
"--custom-email-url", "https://example.com/email",
|
||||
"--icon-url", "https://example.com/icon.svg",
|
||||
"--skip-local-2fa",
|
||||
"--scopes", "address",
|
||||
"--scopes", "email",
|
||||
"--scopes", "phone",
|
||||
"--scopes", "profile",
|
||||
"--attribute-ssh-public-key", "ssh_public_key",
|
||||
"--required-claim-name", "can_access",
|
||||
"--required-claim-value", "yes",
|
||||
"--group-claim-name", "groups",
|
||||
"--admin-group", "admin",
|
||||
"--restricted-group", "restricted",
|
||||
"--group-team-map", `{"org_a_team_1": {"organization-a": ["Team 1"]}, "org_a_all_teams": {"organization-a": ["Team 1", "Team 2", "Team 3"]}}`,
|
||||
"--group-team-map-removal",
|
||||
"--allow-username-change",
|
||||
},
|
||||
source: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Name: "oauth2 (via openidConnect) source full",
|
||||
IsActive: true,
|
||||
Cfg: &oauth2.Source{
|
||||
Provider: "openidConnect",
|
||||
ClientID: "client id",
|
||||
ClientSecret: "client secret",
|
||||
OpenIDConnectAutoDiscoveryURL: "https://example.com/.well-known/openid-configuration",
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{
|
||||
AuthURL: "https://example.com/auth",
|
||||
TokenURL: "https://example.com/token",
|
||||
ProfileURL: "https://example.com/profile",
|
||||
EmailURL: "https://example.com/email",
|
||||
Tenant: "tenant id",
|
||||
},
|
||||
IconURL: "https://example.com/icon.svg",
|
||||
Scopes: []string{"address", "email", "phone", "profile"},
|
||||
AttributeSSHPublicKey: "ssh_public_key",
|
||||
RequiredClaimName: "can_access",
|
||||
RequiredClaimValue: "yes",
|
||||
GroupClaimName: "groups",
|
||||
AdminGroup: "admin",
|
||||
GroupTeamMap: `{"org_a_team_1": {"organization-a": ["Team 1"]}, "org_a_all_teams": {"organization-a": ["Team 1", "Team 2", "Team 3"]}}`,
|
||||
GroupTeamMapRemoval: true,
|
||||
RestrictedGroup: "restricted",
|
||||
SkipLocalTwoFA: true,
|
||||
AllowUsernameChange: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 1
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--name", "oauth2 (via openidConnect) source min",
|
||||
"--provider", "openidConnect",
|
||||
"--auto-discover-url", "https://example.com/.well-known/openid-configuration",
|
||||
},
|
||||
source: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Name: "oauth2 (via openidConnect) source min",
|
||||
IsActive: true,
|
||||
Cfg: &oauth2.Source{
|
||||
Provider: "openidConnect",
|
||||
OpenIDConnectAutoDiscoveryURL: "https://example.com/.well-known/openid-configuration",
|
||||
Scopes: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 2
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--name", "oauth2 (via openidConnect) source `--use-custom-urls` required for `--custom-*` flags",
|
||||
"--custom-tenant-id", "tenant id",
|
||||
"--custom-auth-url", "https://example.com/auth",
|
||||
"--custom-token-url", "https://example.com/token",
|
||||
"--custom-profile-url", "https://example.com/profile",
|
||||
"--custom-email-url", "https://example.com/email",
|
||||
},
|
||||
source: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Name: "oauth2 (via openidConnect) source `--use-custom-urls` required for `--custom-*` flags",
|
||||
IsActive: true,
|
||||
Cfg: &oauth2.Source{
|
||||
Scopes: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 3
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--name", "oauth2 (via openidConnect) source `--scopes` aggregates multiple uses",
|
||||
"--provider", "openidConnect",
|
||||
"--auto-discover-url", "https://example.com/.well-known/openid-configuration",
|
||||
"--scopes", "address",
|
||||
"--scopes", "email",
|
||||
"--scopes", "phone",
|
||||
"--scopes", "profile",
|
||||
},
|
||||
source: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Name: "oauth2 (via openidConnect) source `--scopes` aggregates multiple uses",
|
||||
IsActive: true,
|
||||
Cfg: &oauth2.Source{
|
||||
Provider: "openidConnect",
|
||||
OpenIDConnectAutoDiscoveryURL: "https://example.com/.well-known/openid-configuration",
|
||||
Scopes: []string{"address", "email", "phone", "profile"},
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 4
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--name", "oauth2 (via openidConnect) source `--scopes` supports commas as separators",
|
||||
"--provider", "openidConnect",
|
||||
"--auto-discover-url", "https://example.com/.well-known/openid-configuration",
|
||||
"--scopes", "address,email,phone,profile",
|
||||
},
|
||||
source: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Name: "oauth2 (via openidConnect) source `--scopes` supports commas as separators",
|
||||
IsActive: true,
|
||||
Cfg: &oauth2.Source{
|
||||
Provider: "openidConnect",
|
||||
OpenIDConnectAutoDiscoveryURL: "https://example.com/.well-known/openid-configuration",
|
||||
Scopes: []string{"address", "email", "phone", "profile"},
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 5
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--name", "oauth2 (via openidConnect) source",
|
||||
"--provider", "openidConnect",
|
||||
},
|
||||
errMsg: "invalid Auto Discovery URL: (this must be a valid URL starting with http:// or https://)",
|
||||
},
|
||||
// case 6
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--name", "oauth2 (via openidConnect) source",
|
||||
"--provider", "openidConnect",
|
||||
"--auto-discover-url", "example.com",
|
||||
},
|
||||
errMsg: "invalid Auto Discovery URL: example.com (this must be a valid URL starting with http:// or https://)",
|
||||
},
|
||||
}
|
||||
|
||||
for n, c := range cases {
|
||||
// Mock functions.
|
||||
var createdAuthSource *auth.Source
|
||||
service := &authService{
|
||||
initDB: func(context.Context) error {
|
||||
return nil
|
||||
},
|
||||
createAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
||||
createdAuthSource = authSource
|
||||
return nil
|
||||
},
|
||||
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
||||
assert.FailNow(t, "should not call updateAuthSource", "case: %d", n)
|
||||
return nil
|
||||
},
|
||||
getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) {
|
||||
assert.FailNow(t, "should not call getAuthSourceByID", "case: %d", n)
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
|
||||
// Create a copy of command to test
|
||||
app := cli.Command{}
|
||||
app.Flags = microcmdAuthAddOauth().Flags
|
||||
app.Action = service.addOauth
|
||||
|
||||
// Run it
|
||||
err := app.Run(t.Context(), c.args)
|
||||
if c.errMsg != "" {
|
||||
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
|
||||
} else {
|
||||
require.NoError(t, err, "case %d: should have no errors", n)
|
||||
assert.Equal(t, c.source, createdAuthSource, "case %d: wrong authSource", n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateOauth(t *testing.T) {
|
||||
// Mock cli functions to do not exit on error
|
||||
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
|
||||
|
||||
// Test cases
|
||||
cases := []struct {
|
||||
args []string
|
||||
id int64
|
||||
existingAuthSource *auth.Source
|
||||
authSource *auth.Source
|
||||
errMsg string
|
||||
}{
|
||||
// case 0
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "23",
|
||||
"--name", "oauth2 (via openidConnect) source full",
|
||||
"--provider", "openidConnect",
|
||||
"--key", "client id",
|
||||
"--secret", "client secret",
|
||||
"--auto-discover-url", "https://example.com/.well-known/openid-configuration",
|
||||
"--use-custom-urls", "",
|
||||
"--custom-tenant-id", "tenant id",
|
||||
"--custom-auth-url", "https://example.com/auth",
|
||||
"--custom-token-url", "https://example.com/token",
|
||||
"--custom-profile-url", "https://example.com/profile",
|
||||
"--custom-email-url", "https://example.com/email",
|
||||
"--icon-url", "https://example.com/icon.svg",
|
||||
"--skip-local-2fa",
|
||||
"--scopes", "address",
|
||||
"--scopes", "email",
|
||||
"--scopes", "phone",
|
||||
"--scopes", "profile",
|
||||
"--attribute-ssh-public-key", "ssh_public_key",
|
||||
"--required-claim-name", "can_access",
|
||||
"--required-claim-value", "yes",
|
||||
"--group-claim-name", "groups",
|
||||
"--admin-group", "admin",
|
||||
"--restricted-group", "restricted",
|
||||
"--group-team-map", `{"org_a_team_1": {"organization-a": ["Team 1"]}, "org_a_all_teams": {"organization-a": ["Team 1", "Team 2", "Team 3"]}}`,
|
||||
"--group-team-map-removal",
|
||||
},
|
||||
id: 23,
|
||||
existingAuthSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Cfg: &oauth2.Source{},
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Name: "oauth2 (via openidConnect) source full",
|
||||
Cfg: &oauth2.Source{
|
||||
Provider: "openidConnect",
|
||||
ClientID: "client id",
|
||||
ClientSecret: "client secret",
|
||||
OpenIDConnectAutoDiscoveryURL: "https://example.com/.well-known/openid-configuration",
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{
|
||||
AuthURL: "https://example.com/auth",
|
||||
TokenURL: "https://example.com/token",
|
||||
ProfileURL: "https://example.com/profile",
|
||||
EmailURL: "https://example.com/email",
|
||||
Tenant: "tenant id",
|
||||
},
|
||||
IconURL: "https://example.com/icon.svg",
|
||||
Scopes: []string{"address", "email", "phone", "profile"},
|
||||
AttributeSSHPublicKey: "ssh_public_key",
|
||||
RequiredClaimName: "can_access",
|
||||
RequiredClaimValue: "yes",
|
||||
GroupClaimName: "groups",
|
||||
AdminGroup: "admin",
|
||||
GroupTeamMap: `{"org_a_team_1": {"organization-a": ["Team 1"]}, "org_a_all_teams": {"organization-a": ["Team 1", "Team 2", "Team 3"]}}`,
|
||||
GroupTeamMapRemoval: true,
|
||||
RestrictedGroup: "restricted",
|
||||
// `--skip-local-2fa` is currently ignored.
|
||||
// SkipLocalTwoFA: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 1
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "1",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Cfg: &oauth2.Source{
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 2
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "1",
|
||||
"--name", "oauth2 (via openidConnect) source full",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Name: "oauth2 (via openidConnect) source full",
|
||||
Cfg: &oauth2.Source{
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 3
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "1",
|
||||
"--provider", "openidConnect",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Cfg: &oauth2.Source{
|
||||
Provider: "openidConnect",
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 4
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "1",
|
||||
"--key", "client id",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Cfg: &oauth2.Source{
|
||||
ClientID: "client id",
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 5
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "1",
|
||||
"--secret", "client secret",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Cfg: &oauth2.Source{
|
||||
ClientSecret: "client secret",
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 6
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "1",
|
||||
"--auto-discover-url", "https://example.com/.well-known/openid-configuration",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Cfg: &oauth2.Source{
|
||||
OpenIDConnectAutoDiscoveryURL: "https://example.com/.well-known/openid-configuration",
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 7
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "1",
|
||||
"--use-custom-urls", "",
|
||||
"--custom-tenant-id", "tenant id",
|
||||
"--custom-auth-url", "https://example.com/auth",
|
||||
"--custom-token-url", "https://example.com/token",
|
||||
"--custom-profile-url", "https://example.com/profile",
|
||||
"--custom-email-url", "https://example.com/email",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Cfg: &oauth2.Source{
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{
|
||||
AuthURL: "https://example.com/auth",
|
||||
TokenURL: "https://example.com/token",
|
||||
ProfileURL: "https://example.com/profile",
|
||||
EmailURL: "https://example.com/email",
|
||||
Tenant: "tenant id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 8
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "1",
|
||||
"--name", "oauth2 (via openidConnect) source `--use-custom-urls` required for `--custom-*` flags",
|
||||
"--custom-tenant-id", "tenant id",
|
||||
"--custom-auth-url", "https://example.com/auth",
|
||||
"--custom-token-url", "https://example.com/token",
|
||||
"--custom-profile-url", "https://example.com/profile",
|
||||
"--custom-email-url", "https://example.com/email",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Name: "oauth2 (via openidConnect) source `--use-custom-urls` required for `--custom-*` flags",
|
||||
Cfg: &oauth2.Source{
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 9
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "1",
|
||||
"--icon-url", "https://example.com/icon.svg",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Cfg: &oauth2.Source{
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||
IconURL: "https://example.com/icon.svg",
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 10
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "1",
|
||||
"--name", "oauth2 (via openidConnect) source `--skip-local-2fa` is currently ignored",
|
||||
"--skip-local-2fa",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Name: "oauth2 (via openidConnect) source `--skip-local-2fa` is currently ignored",
|
||||
Cfg: &oauth2.Source{
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||
// `--skip-local-2fa` is currently ignored.
|
||||
// SkipLocalTwoFA: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 11
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "1",
|
||||
"--name", "oauth2 (via openidConnect) source `--scopes` aggregates multiple uses",
|
||||
"--scopes", "address",
|
||||
"--scopes", "email",
|
||||
"--scopes", "phone",
|
||||
"--scopes", "profile",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Name: "oauth2 (via openidConnect) source `--scopes` aggregates multiple uses",
|
||||
Cfg: &oauth2.Source{
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||
Scopes: []string{"address", "email", "phone", "profile"},
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 12
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "1",
|
||||
"--name", "oauth2 (via openidConnect) source `--scopes` supports commas as separators",
|
||||
"--scopes", "address,email,phone,profile",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Name: "oauth2 (via openidConnect) source `--scopes` supports commas as separators",
|
||||
Cfg: &oauth2.Source{
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||
Scopes: []string{"address", "email", "phone", "profile"},
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 13
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "1",
|
||||
"--attribute-ssh-public-key", "ssh_public_key",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Cfg: &oauth2.Source{
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||
AttributeSSHPublicKey: "ssh_public_key",
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 14
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "1",
|
||||
"--required-claim-name", "can_access",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Cfg: &oauth2.Source{
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||
RequiredClaimName: "can_access",
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 15
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "1",
|
||||
"--required-claim-value", "yes",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Cfg: &oauth2.Source{
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||
RequiredClaimValue: "yes",
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 16
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "1",
|
||||
"--group-claim-name", "groups",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Cfg: &oauth2.Source{
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||
GroupClaimName: "groups",
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 17
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "1",
|
||||
"--admin-group", "admin",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Cfg: &oauth2.Source{
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||
AdminGroup: "admin",
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 18
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "1",
|
||||
"--restricted-group", "restricted",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Cfg: &oauth2.Source{
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||
RestrictedGroup: "restricted",
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 19
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "1",
|
||||
"--group-team-map", `{"org_a_team_1": {"organization-a": ["Team 1"]}, "org_a_all_teams": {"organization-a": ["Team 1", "Team 2", "Team 3"]}}`,
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Cfg: &oauth2.Source{
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||
GroupTeamMap: `{"org_a_team_1": {"organization-a": ["Team 1"]}, "org_a_all_teams": {"organization-a": ["Team 1", "Team 2", "Team 3"]}}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 20
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "1",
|
||||
"--group-team-map-removal",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Cfg: &oauth2.Source{
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||
GroupTeamMapRemoval: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 21
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "23",
|
||||
"--group-team-map-removal=false",
|
||||
},
|
||||
id: 23,
|
||||
existingAuthSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Cfg: &oauth2.Source{
|
||||
GroupTeamMapRemoval: true,
|
||||
},
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Cfg: &oauth2.Source{
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||
GroupTeamMapRemoval: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 22
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
},
|
||||
errMsg: "--id flag is missing",
|
||||
},
|
||||
}
|
||||
|
||||
for n, c := range cases {
|
||||
// Mock functions.
|
||||
var updatedAuthSource *auth.Source
|
||||
service := &authService{
|
||||
initDB: func(context.Context) error {
|
||||
return nil
|
||||
},
|
||||
createAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
||||
assert.FailNow(t, "should not call createAuthSource", "case: %d", n)
|
||||
return nil
|
||||
},
|
||||
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
||||
updatedAuthSource = authSource
|
||||
return nil
|
||||
},
|
||||
getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) {
|
||||
if c.id != 0 {
|
||||
assert.Equal(t, c.id, id, "case %d: wrong id", n)
|
||||
}
|
||||
if c.existingAuthSource != nil {
|
||||
return c.existingAuthSource, nil
|
||||
}
|
||||
return &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Cfg: &oauth2.Source{},
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
// Create a copy of command to test
|
||||
app := cli.Command{}
|
||||
app.Flags = microcmdAuthUpdateOauth().Flags
|
||||
app.Action = service.updateOauth
|
||||
|
||||
// Run it
|
||||
err := app.Run(t.Context(), c.args)
|
||||
if c.errMsg != "" {
|
||||
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
|
||||
} else {
|
||||
require.NoError(t, err, "case %d: should have no errors", n)
|
||||
assert.Equal(t, c.authSource, updatedAuthSource, "case %d: wrong authSource", n)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -78,6 +78,7 @@ func microcmdAuthAddSMTP() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "add-smtp",
|
||||
Usage: "Add new SMTP authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Action: runAddSMTP,
|
||||
Flags: smtpCLIFlags(),
|
||||
}
|
||||
|
@ -87,6 +88,7 @@ func microcmdAuthUpdateSMTP() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "update-smtp",
|
||||
Usage: "Update existing SMTP authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Action: runUpdateSMTP,
|
||||
Flags: append(smtpCLIFlags()[:1], append([]cli.Flag{idFlag()}, smtpCLIFlags()[1:]...)...),
|
||||
}
|
||||
|
|
|
@ -17,17 +17,19 @@ var (
|
|||
microcmdRegenHooks = &cli.Command{
|
||||
Name: "hooks",
|
||||
Usage: "Regenerate git-hooks",
|
||||
Before: noDanglingArgs,
|
||||
Action: runRegenerateHooks,
|
||||
}
|
||||
|
||||
microcmdRegenKeys = &cli.Command{
|
||||
Name: "keys",
|
||||
Usage: "Regenerate authorized_keys file",
|
||||
Before: noDanglingArgs,
|
||||
Action: runRegenerateKeys,
|
||||
}
|
||||
)
|
||||
|
||||
func runRegenerateHooks(ctx context.Context, _ *cli.Command) error {
|
||||
func runRegenerateHooks(ctx context.Context, c *cli.Command) error {
|
||||
ctx, cancel := installSignals(ctx)
|
||||
defer cancel()
|
||||
|
||||
|
@ -37,7 +39,7 @@ func runRegenerateHooks(ctx context.Context, _ *cli.Command) error {
|
|||
return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext())
|
||||
}
|
||||
|
||||
func runRegenerateKeys(ctx context.Context, _ *cli.Command) error {
|
||||
func runRegenerateKeys(ctx context.Context, c *cli.Command) error {
|
||||
ctx, cancel := installSignals(ctx)
|
||||
defer cancel()
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ func microcmdUserChangePassword() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "change-password",
|
||||
Usage: "Change a user's password",
|
||||
Before: noDanglingArgs,
|
||||
Action: runChangePassword,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
|
@ -23,6 +23,7 @@ func microcmdUserCreate() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "create",
|
||||
Usage: "Create a new user in database",
|
||||
Before: noDanglingArgs,
|
||||
Action: runCreateUser,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
|
@ -40,6 +40,7 @@ func microcmdUserDelete() *cli.Command {
|
|||
Usage: "Purge user, all their repositories, organizations and comments",
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runDeleteUser,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ func microcmdUserGenerateAccessToken() *cli.Command {
|
|||
Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`,
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runGenerateAccessToken,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ func microcmdUserList() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "list",
|
||||
Usage: "List users",
|
||||
Before: noDanglingArgs,
|
||||
Action: runListUsers,
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
|
|
|
@ -17,6 +17,7 @@ func microcmdUserResetMFA() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "reset-mfa",
|
||||
Usage: "Remove all two-factor authentication configurations for a user",
|
||||
Before: noDanglingArgs,
|
||||
Action: runResetMFA,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
|
@ -31,6 +31,7 @@ func cmdCert() *cli.Command {
|
|||
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.`,
|
||||
Before: noDanglingArgs,
|
||||
Action: runCert,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
29
cmd/cmd.go
29
cmd/cmd.go
|
@ -12,6 +12,7 @@ import (
|
|||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"slices"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
|
@ -40,6 +41,19 @@ func argsSet(c *cli.Command, args ...string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// When a CLI command is intended to be used only with flags and no other arbitrary args, noDanglingArgs will validate
|
||||
// the end-user's usage.
|
||||
func noDanglingArgs(ctx context.Context, c *cli.Command) (context.Context, error) {
|
||||
if c.Args().Len() != 0 {
|
||||
args := c.Args().Slice()
|
||||
if slices.Contains(args, "false") {
|
||||
println("Hint: boolean false must be specified as a single arg, eg. '--restricted=false', not '--restricted false'")
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected arguments: %s", strings.Join(c.Args().Slice(), ", "))
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// confirm waits for user input which confirms an action
|
||||
func confirm() (bool, error) {
|
||||
var response string
|
||||
|
@ -135,3 +149,18 @@ func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(ctx context.Context,
|
|||
return ctx, nil
|
||||
}
|
||||
}
|
||||
|
||||
func multipleBefore(beforeFuncs ...cli.BeforeFunc) cli.BeforeFunc {
|
||||
return func(ctx context.Context, cli *cli.Command) (context.Context, error) {
|
||||
for _, beforeFunc := range beforeFuncs {
|
||||
bctx, err := beforeFunc(ctx, cli)
|
||||
if err != nil {
|
||||
return bctx, err
|
||||
}
|
||||
if bctx != nil {
|
||||
ctx = bctx
|
||||
}
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ package cmd
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"image"
|
||||
"io"
|
||||
golog "log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -13,13 +15,17 @@ import (
|
|||
"text/tabwriter"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/models/migrations"
|
||||
migrate_base "forgejo.org/models/migrations/base"
|
||||
"forgejo.org/models/gitea_migrations"
|
||||
migrate_base "forgejo.org/models/gitea_migrations/base"
|
||||
repo_model "forgejo.org/models/repo"
|
||||
user_model "forgejo.org/models/user"
|
||||
"forgejo.org/modules/container"
|
||||
"forgejo.org/modules/log"
|
||||
"forgejo.org/modules/setting"
|
||||
"forgejo.org/modules/storage"
|
||||
"forgejo.org/services/doctor"
|
||||
|
||||
exif_terminator "code.superseriousbusiness.org/exif-terminator"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
|
@ -34,6 +40,7 @@ func cmdDoctor() *cli.Command {
|
|||
cmdDoctorCheck(),
|
||||
cmdRecreateTable(),
|
||||
cmdDoctorConvert(),
|
||||
cmdAvatarStripExif(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +50,7 @@ func cmdDoctorCheck() *cli.Command {
|
|||
Name: "check",
|
||||
Usage: "Diagnose and optionally fix problems",
|
||||
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.",
|
||||
Before: noDanglingArgs,
|
||||
Action: runDoctorCheck,
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
|
@ -98,6 +106,15 @@ You should back-up your database before doing this and ensure that your database
|
|||
}
|
||||
}
|
||||
|
||||
func cmdAvatarStripExif() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "avatar-strip-exif",
|
||||
Usage: "Strip EXIF metadata from all images in the avatar storage",
|
||||
Before: noDanglingArgs,
|
||||
Action: runAvatarStripExif,
|
||||
}
|
||||
}
|
||||
|
||||
func runRecreateTable(stdCtx context.Context, ctx *cli.Command) error {
|
||||
stdCtx, cancel := installSignals(stdCtx)
|
||||
defer cancel()
|
||||
|
@ -142,7 +159,7 @@ func runRecreateTable(stdCtx context.Context, ctx *cli.Command) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := migrations.EnsureUpToDate(engine); err != nil {
|
||||
if err := gitea_migrations.EnsureUpToDate(engine); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -230,3 +247,78 @@ func runDoctorCheck(stdCtx context.Context, ctx *cli.Command) error {
|
|||
}
|
||||
return doctor.RunChecks(stdCtx, colorize, ctx.Bool("fix"), checks)
|
||||
}
|
||||
|
||||
func runAvatarStripExif(ctx context.Context, c *cli.Command) error {
|
||||
ctx, cancel := installSignals(ctx)
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := storage.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
type HasCustomAvatarRelativePath interface {
|
||||
CustomAvatarRelativePath() string
|
||||
}
|
||||
|
||||
doExifStrip := func(obj HasCustomAvatarRelativePath, name string, target_storage storage.ObjectStorage) error {
|
||||
if obj.CustomAvatarRelativePath() == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Info("Stripping avatar for %s...", name)
|
||||
|
||||
avatarFile, err := target_storage.Open(obj.CustomAvatarRelativePath())
|
||||
if err != nil {
|
||||
return fmt.Errorf("storage.Avatars.Open: %w", err)
|
||||
}
|
||||
_, imgType, err := image.DecodeConfig(avatarFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("image.DecodeConfig: %w", err)
|
||||
}
|
||||
|
||||
// reset io.Reader for exif termination scan
|
||||
_, err = avatarFile.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return fmt.Errorf("avatarFile.Seek: %w", err)
|
||||
}
|
||||
|
||||
cleanedData, err := exif_terminator.Terminate(avatarFile, imgType)
|
||||
if err != nil && strings.Contains(err.Error(), "cannot be processed") {
|
||||
// expected error for an image type that isn't supported by exif_terminator
|
||||
log.Info("... image type %s is not supported by exif_terminator, skipping.", imgType)
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("error cleaning exif data: %w", err)
|
||||
}
|
||||
|
||||
if err := storage.SaveFrom(target_storage, obj.CustomAvatarRelativePath(), func(w io.Writer) error {
|
||||
_, err := io.Copy(w, cleanedData)
|
||||
return err
|
||||
}); err != nil {
|
||||
return fmt.Errorf("Failed to create dir %s: %w", obj.CustomAvatarRelativePath(), err)
|
||||
}
|
||||
|
||||
log.Info("... completed %s.", name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err := db.Iterate(ctx, nil, func(ctx context.Context, user *user_model.User) error {
|
||||
return doExifStrip(user, fmt.Sprintf("user %s", user.Name), storage.Avatars)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = db.Iterate(ctx, nil, func(ctx context.Context, repo *repo_model.Repository) error {
|
||||
return doExifStrip(repo, fmt.Sprintf("repo %s", repo.Name), storage.RepoAvatars)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ func cmdDoctorConvert() *cli.Command {
|
|||
Name: "convert",
|
||||
Usage: "Convert the database",
|
||||
Description: "A command to convert an existing MySQL database from utf8 to utf8mb4",
|
||||
Before: noDanglingArgs,
|
||||
Action: runDoctorConvert,
|
||||
}
|
||||
}
|
||||
|
|
428
cmd/dump.go
428
cmd/dump.go
|
@ -8,11 +8,12 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
|
@ -23,36 +24,43 @@ import (
|
|||
"forgejo.org/modules/util"
|
||||
|
||||
"code.forgejo.org/go-chi/session"
|
||||
"github.com/mholt/archiver/v3"
|
||||
"github.com/mholt/archives"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
func addReader(w archiver.Writer, r io.ReadCloser, info os.FileInfo, customName string, verbose bool) error {
|
||||
func addObject(archiveJobs chan archives.ArchiveAsyncJob, object fs.File, customName string, verbose bool) error {
|
||||
if verbose {
|
||||
log.Info("Adding file %s", customName)
|
||||
}
|
||||
|
||||
return w.Write(archiver.File{
|
||||
FileInfo: archiver.FileInfo{
|
||||
FileInfo: info,
|
||||
CustomName: customName,
|
||||
info, err := object.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ch := make(chan error)
|
||||
|
||||
archiveJobs <- archives.ArchiveAsyncJob{
|
||||
File: archives.FileInfo{
|
||||
FileInfo: info,
|
||||
NameInArchive: customName,
|
||||
Open: func() (fs.File, error) {
|
||||
return object, nil
|
||||
},
|
||||
},
|
||||
ReadCloser: r,
|
||||
})
|
||||
Result: ch,
|
||||
}
|
||||
|
||||
return <-ch
|
||||
}
|
||||
|
||||
func addFile(w archiver.Writer, filePath, absPath string, verbose bool) error {
|
||||
file, err := os.Open(absPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
fileInfo, err := file.Stat()
|
||||
func addFile(archiveJobs chan archives.ArchiveAsyncJob, filePath, absPath string, verbose bool) error {
|
||||
file, err := os.Open(absPath) // Closed by archiver
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return addReader(w, file, fileInfo, filePath, verbose)
|
||||
return addObject(archiveJobs, file, filePath, verbose)
|
||||
}
|
||||
|
||||
func isSubdir(upper, lower string) (bool, error) {
|
||||
|
@ -101,6 +109,54 @@ var outputTypeEnum = &outputType{
|
|||
Default: "zip",
|
||||
}
|
||||
|
||||
func getArchiverByType(outType string) (archives.ArchiverAsync, error) {
|
||||
var archiver archives.ArchiverAsync
|
||||
switch outType {
|
||||
case "zip":
|
||||
archiver = archives.Zip{}
|
||||
case "tar":
|
||||
archiver = archives.Tar{}
|
||||
case "tar.sz":
|
||||
archiver = archives.CompressedArchive{
|
||||
Archival: archives.Tar{},
|
||||
Compression: archives.Sz{},
|
||||
}
|
||||
case "tar.gz":
|
||||
archiver = archives.CompressedArchive{
|
||||
Archival: archives.Tar{},
|
||||
Compression: archives.Gz{},
|
||||
}
|
||||
case "tar.xz":
|
||||
archiver = archives.CompressedArchive{
|
||||
Archival: archives.Tar{},
|
||||
Compression: archives.Xz{},
|
||||
}
|
||||
case "tar.bz2":
|
||||
archiver = archives.CompressedArchive{
|
||||
Archival: archives.Tar{},
|
||||
Compression: archives.Bz2{},
|
||||
}
|
||||
case "tar.br":
|
||||
archiver = archives.CompressedArchive{
|
||||
Archival: archives.Tar{},
|
||||
Compression: archives.Brotli{},
|
||||
}
|
||||
case "tar.lz4":
|
||||
archiver = archives.CompressedArchive{
|
||||
Archival: archives.Tar{},
|
||||
Compression: archives.Lz4{},
|
||||
}
|
||||
case "tar.zst":
|
||||
archiver = archives.CompressedArchive{
|
||||
Archival: archives.Tar{},
|
||||
Compression: archives.Zstd{},
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported output type: %s", outType)
|
||||
}
|
||||
return archiver, nil
|
||||
}
|
||||
|
||||
// CmdDump represents the available dump sub-command.
|
||||
func cmdDump() *cli.Command {
|
||||
return &cli.Command{
|
||||
|
@ -108,6 +164,7 @@ func cmdDump() *cli.Command {
|
|||
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`,
|
||||
Before: noDanglingArgs,
|
||||
Action: runDump,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
@ -254,46 +311,185 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
|
|||
return err
|
||||
}
|
||||
|
||||
var iface any
|
||||
if fileName == "-" {
|
||||
iface, err = archiver.ByExtension(fmt.Sprintf(".%s", outType))
|
||||
} else {
|
||||
iface, err = archiver.ByExtension(fileName)
|
||||
}
|
||||
archiveJobs := make(chan archives.ArchiveAsyncJob)
|
||||
wg := sync.WaitGroup{}
|
||||
archiver, err := getArchiverByType(outType)
|
||||
if err != nil {
|
||||
fatal("Failed to get archiver for extension: %v", err)
|
||||
}
|
||||
|
||||
w, _ := iface.(archiver.Writer)
|
||||
if err := w.Create(file); err != nil {
|
||||
fatal("Creating archiver.Writer failed: %v", err)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
if ctx.IsSet("skip-repository") && ctx.Bool("skip-repository") {
|
||||
log.Info("Skipping local repositories")
|
||||
} else {
|
||||
log.Info("Dumping local repositories... %s", setting.RepoRootPath)
|
||||
if err := addRecursiveExclude(w, "repos", setting.RepoRootPath, []string{absFileName}, verbose); err != nil {
|
||||
fatal("Failed to include repositories: %v", err)
|
||||
}
|
||||
wg.Add(1)
|
||||
go dumpRepos(ctx, archiveJobs, &wg, absFileName, verbose)
|
||||
}
|
||||
|
||||
if ctx.IsSet("skip-lfs-data") && ctx.Bool("skip-lfs-data") {
|
||||
log.Info("Skipping LFS data")
|
||||
} else if !setting.LFS.StartServer {
|
||||
log.Info("LFS not enabled - skipping")
|
||||
} else if err := storage.LFS.IterateObjects("", func(objPath string, object storage.Object) error {
|
||||
info, err := object.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
wg.Add(1)
|
||||
go dumpDatabase(ctx, archiveJobs, &wg, verbose)
|
||||
|
||||
if len(setting.CustomConf) > 0 {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
log.Info("Adding custom configuration file from %s", setting.CustomConf)
|
||||
if err := addFile(archiveJobs, "app.ini", setting.CustomConf, verbose); err != nil {
|
||||
fatal("Failed to include specified app.ini: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return addReader(w, object, info, path.Join("data", "lfs", objPath), verbose)
|
||||
}); err != nil {
|
||||
fatal("Failed to dump LFS objects: %v", err)
|
||||
if ctx.IsSet("skip-custom-dir") && ctx.Bool("skip-custom-dir") {
|
||||
log.Info("Skipping custom directory")
|
||||
} else {
|
||||
wg.Add(1)
|
||||
go dumpCustom(archiveJobs, &wg, absFileName, verbose)
|
||||
}
|
||||
|
||||
isExist, err := util.IsExist(setting.AppDataPath)
|
||||
if err != nil {
|
||||
log.Error("Failed to check if %s exists: %v", setting.AppDataPath, err)
|
||||
}
|
||||
if isExist {
|
||||
log.Info("Packing data directory...%s", setting.AppDataPath)
|
||||
|
||||
wg.Add(1)
|
||||
go dumpData(ctx, archiveJobs, &wg, absFileName, verbose)
|
||||
}
|
||||
|
||||
if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") {
|
||||
log.Info("Skipping attachment data")
|
||||
} else {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := storage.Attachments.IterateObjects("", func(objPath string, object storage.Object) error {
|
||||
return addObject(archiveJobs, object, path.Join("data", "attachments", objPath), verbose)
|
||||
}); err != nil {
|
||||
fatal("Failed to dump attachments: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if ctx.IsSet("skip-package-data") && ctx.Bool("skip-package-data") {
|
||||
log.Info("Skipping package data")
|
||||
} else if !setting.Packages.Enabled {
|
||||
log.Info("Package registry not enabled - skipping")
|
||||
} else {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := storage.Packages.IterateObjects("", func(objPath string, object storage.Object) error {
|
||||
return addObject(archiveJobs, object, path.Join("data", "packages", objPath), verbose)
|
||||
}); err != nil {
|
||||
fatal("Failed to dump packages: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Doesn't check if LogRootPath exists before processing --skip-log intentionally,
|
||||
// ensuring that it's clear the dump is skipped whether the directory's initialized
|
||||
// yet or not.
|
||||
if ctx.IsSet("skip-log") && ctx.Bool("skip-log") {
|
||||
log.Info("Skipping log files")
|
||||
} else {
|
||||
isExist, err := util.IsExist(setting.Log.RootPath)
|
||||
if err != nil {
|
||||
log.Error("Failed to check if %s exists: %v", setting.Log.RootPath, err)
|
||||
}
|
||||
if isExist {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := addRecursiveExclude(archiveJobs, "log", setting.Log.RootPath, []string{absFileName}, verbose); err != nil {
|
||||
fatal("Failed to include log: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for all jobs to finish before closing the channel
|
||||
// ArchiveAsync will only return after the channel is closed
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(archiveJobs)
|
||||
}()
|
||||
|
||||
if err := archiver.ArchiveAsync(stdCtx, file, archiveJobs); err != nil {
|
||||
_ = util.Remove(fileName)
|
||||
|
||||
fatal("Archiving failed: %v", err)
|
||||
}
|
||||
|
||||
if fileName != "-" {
|
||||
if err := os.Chmod(fileName, 0o600); err != nil {
|
||||
log.Info("Can't change file access permissions mask to 0600: %v", err)
|
||||
}
|
||||
|
||||
log.Info("Finished dumping in file %s", fileName)
|
||||
} else {
|
||||
log.Info("Finished dumping to stdout")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func dumpData(ctx *cli.Command, archiveJobs chan archives.ArchiveAsyncJob, wg *sync.WaitGroup, absFileName string, verbose bool) {
|
||||
defer wg.Done()
|
||||
|
||||
var excludes []string
|
||||
if setting.SessionConfig.OriginalProvider == "file" {
|
||||
var opts session.Options
|
||||
if err := json.Unmarshal([]byte(setting.SessionConfig.ProviderConfig), &opts); err != nil {
|
||||
fatal("Failed to parse session config: %v", err)
|
||||
}
|
||||
excludes = append(excludes, opts.ProviderConfig)
|
||||
}
|
||||
|
||||
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.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.LFS.Storage.Path)
|
||||
excludes = append(excludes, setting.Attachment.Storage.Path)
|
||||
excludes = append(excludes, setting.Packages.Storage.Path)
|
||||
excludes = append(excludes, setting.Log.RootPath)
|
||||
excludes = append(excludes, absFileName)
|
||||
if err := addRecursiveExclude(archiveJobs, "data", setting.AppDataPath, excludes, verbose); err != nil {
|
||||
fatal("Failed to include data directory: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func dumpCustom(archiveJobs chan archives.ArchiveAsyncJob, wg *sync.WaitGroup, absFileName string, verbose bool) {
|
||||
defer wg.Done()
|
||||
|
||||
customDir, err := os.Stat(setting.CustomPath)
|
||||
if err == nil && customDir.IsDir() {
|
||||
if is, _ := isSubdir(setting.AppDataPath, setting.CustomPath); !is {
|
||||
if err := addRecursiveExclude(archiveJobs, "custom", setting.CustomPath, []string{absFileName}, verbose); err != nil {
|
||||
fatal("Failed to include custom: %v", err)
|
||||
}
|
||||
} else {
|
||||
log.Info("Custom dir %s is inside data dir %s, skipping", setting.CustomPath, setting.AppDataPath)
|
||||
}
|
||||
} else {
|
||||
log.Info("Custom dir %s does not exist, skipping", setting.CustomPath)
|
||||
}
|
||||
}
|
||||
|
||||
func dumpDatabase(ctx *cli.Command, archiveJobs chan archives.ArchiveAsyncJob, wg *sync.WaitGroup, verbose bool) {
|
||||
defer wg.Done()
|
||||
|
||||
var err error
|
||||
tmpDir := ctx.String("tempdir")
|
||||
if tmpDir == "" {
|
||||
tmpDir, err = os.MkdirTemp("", "forgejo-dump-*")
|
||||
|
@ -334,139 +530,32 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
|
|||
fatal("Failed to dump database: %v", err)
|
||||
}
|
||||
|
||||
if err := addFile(w, "forgejo-db.sql", dbDump.Name(), verbose); err != nil {
|
||||
if err := addFile(archiveJobs, "forgejo-db.sql", dbDump.Name(), verbose); err != nil {
|
||||
fatal("Failed to include forgejo-db.sql: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(setting.CustomConf) > 0 {
|
||||
log.Info("Adding custom configuration file from %s", setting.CustomConf)
|
||||
if err := addFile(w, "app.ini", setting.CustomConf, verbose); err != nil {
|
||||
fatal("Failed to include specified app.ini: %v", err)
|
||||
}
|
||||
func dumpRepos(ctx *cli.Command, archiveJobs chan archives.ArchiveAsyncJob, wg *sync.WaitGroup, absFileName string, verbose bool) {
|
||||
defer wg.Done()
|
||||
|
||||
if err := addRecursiveExclude(archiveJobs, "repos", setting.RepoRootPath, []string{absFileName}, verbose); err != nil {
|
||||
fatal("Failed to include repositories: %v", err)
|
||||
}
|
||||
|
||||
if ctx.IsSet("skip-custom-dir") && ctx.Bool("skip-custom-dir") {
|
||||
log.Info("Skipping custom directory")
|
||||
} else {
|
||||
customDir, err := os.Stat(setting.CustomPath)
|
||||
if err == nil && customDir.IsDir() {
|
||||
if is, _ := isSubdir(setting.AppDataPath, setting.CustomPath); !is {
|
||||
if err := addRecursiveExclude(w, "custom", setting.CustomPath, []string{absFileName}, verbose); err != nil {
|
||||
fatal("Failed to include custom: %v", err)
|
||||
}
|
||||
} else {
|
||||
log.Info("Custom dir %s is inside data dir %s, skipping", setting.CustomPath, setting.AppDataPath)
|
||||
}
|
||||
} else {
|
||||
log.Info("Custom dir %s does not exist, skipping", setting.CustomPath)
|
||||
}
|
||||
}
|
||||
|
||||
isExist, err := util.IsExist(setting.AppDataPath)
|
||||
if err != nil {
|
||||
log.Error("Failed to check if %s exists: %v", setting.AppDataPath, err)
|
||||
}
|
||||
if isExist {
|
||||
log.Info("Packing data directory...%s", setting.AppDataPath)
|
||||
|
||||
var excludes []string
|
||||
if setting.SessionConfig.OriginalProvider == "file" {
|
||||
var opts session.Options
|
||||
if err = json.Unmarshal([]byte(setting.SessionConfig.ProviderConfig), &opts); err != nil {
|
||||
return err
|
||||
}
|
||||
excludes = append(excludes, opts.ProviderConfig)
|
||||
}
|
||||
|
||||
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.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.LFS.Storage.Path)
|
||||
excludes = append(excludes, setting.Attachment.Storage.Path)
|
||||
excludes = append(excludes, setting.Packages.Storage.Path)
|
||||
excludes = append(excludes, setting.Log.RootPath)
|
||||
excludes = append(excludes, absFileName)
|
||||
if err := addRecursiveExclude(w, "data", setting.AppDataPath, excludes, verbose); err != nil {
|
||||
fatal("Failed to include data directory: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") {
|
||||
log.Info("Skipping attachment data")
|
||||
} else if err := storage.Attachments.IterateObjects("", func(objPath string, object storage.Object) error {
|
||||
info, err := object.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return addReader(w, object, info, path.Join("data", "attachments", objPath), verbose)
|
||||
if ctx.IsSet("skip-lfs-data") && ctx.Bool("skip-lfs-data") {
|
||||
log.Info("Skipping LFS data")
|
||||
} else if !setting.LFS.StartServer {
|
||||
log.Info("LFS not enabled - skipping")
|
||||
} else if err := storage.LFS.IterateObjects("", func(objPath string, object storage.Object) error {
|
||||
return addObject(archiveJobs, object, path.Join("data", "lfs", objPath), verbose)
|
||||
}); err != nil {
|
||||
fatal("Failed to dump attachments: %v", err)
|
||||
fatal("Failed to dump LFS objects: %v", err)
|
||||
}
|
||||
|
||||
if ctx.IsSet("skip-package-data") && ctx.Bool("skip-package-data") {
|
||||
log.Info("Skipping package data")
|
||||
} else if !setting.Packages.Enabled {
|
||||
log.Info("Package registry not enabled - skipping")
|
||||
} else if err := storage.Packages.IterateObjects("", func(objPath string, object storage.Object) error {
|
||||
info, err := object.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return addReader(w, object, info, path.Join("data", "packages", objPath), verbose)
|
||||
}); err != nil {
|
||||
fatal("Failed to dump packages: %v", err)
|
||||
}
|
||||
|
||||
// Doesn't check if LogRootPath exists before processing --skip-log intentionally,
|
||||
// ensuring that it's clear the dump is skipped whether the directory's initialized
|
||||
// yet or not.
|
||||
if ctx.IsSet("skip-log") && ctx.Bool("skip-log") {
|
||||
log.Info("Skipping log files")
|
||||
} else {
|
||||
isExist, err := util.IsExist(setting.Log.RootPath)
|
||||
if err != nil {
|
||||
log.Error("Failed to check if %s exists: %v", setting.Log.RootPath, err)
|
||||
}
|
||||
if isExist {
|
||||
if err := addRecursiveExclude(w, "log", setting.Log.RootPath, []string{absFileName}, verbose); err != nil {
|
||||
fatal("Failed to include log: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if fileName != "-" {
|
||||
if err = w.Close(); err != nil {
|
||||
_ = util.Remove(fileName)
|
||||
fatal("Failed to save %s: %v", fileName, err)
|
||||
}
|
||||
|
||||
if err := os.Chmod(fileName, 0o600); err != nil {
|
||||
log.Info("Can't change file access permissions mask to 0600: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if fileName != "-" {
|
||||
log.Info("Finish dumping in file %s", fileName)
|
||||
} else {
|
||||
log.Info("Finish dumping to stdout")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// addRecursiveExclude zips absPath to specified insidePath inside writer excluding excludeAbsPath
|
||||
func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeAbsPath []string, verbose bool) error {
|
||||
// archives.FilesFromDisk doesn't support excluding files, so we have to do it manually
|
||||
func addRecursiveExclude(archiveJobs chan archives.ArchiveAsyncJob, insidePath, absPath string, excludeAbsPath []string, verbose bool) error {
|
||||
absPath, err := filepath.Abs(absPath)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -491,10 +580,11 @@ func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeA
|
|||
}
|
||||
|
||||
if file.IsDir() {
|
||||
if err := addFile(w, currentInsidePath, currentAbsPath, false); err != nil {
|
||||
if err := addFile(archiveJobs, currentInsidePath, currentAbsPath, false); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = addRecursiveExclude(w, currentInsidePath, currentAbsPath, excludeAbsPath, verbose); err != nil {
|
||||
|
||||
if err := addRecursiveExclude(archiveJobs, currentInsidePath, currentAbsPath, excludeAbsPath, verbose); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
|
@ -512,7 +602,7 @@ func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeA
|
|||
shouldAdd = targetStat.Mode().IsRegular()
|
||||
}
|
||||
if shouldAdd {
|
||||
if err = addFile(w, currentInsidePath, currentAbsPath, verbose); err != nil {
|
||||
if err := addFile(archiveJobs, currentInsidePath, currentAbsPath, verbose); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ func cmdDumpRepository() *cli.Command {
|
|||
Name: "dump-repo",
|
||||
Usage: "Dump the repository from git/github/gitea/gitlab",
|
||||
Description: "This is a command for dumping the repository data.",
|
||||
Before: noDanglingArgs,
|
||||
Action: runDumpRepository,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
@ -82,6 +83,11 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme
|
|||
}
|
||||
|
||||
func runDumpRepository(stdCtx context.Context, ctx *cli.Command) error {
|
||||
setupConsoleLogger(log.INFO, log.CanColorStderr, os.Stderr)
|
||||
|
||||
// setting.DisableLoggerInit()
|
||||
setting.LoadSettings() // cannot access skip_tls_verify settings otherwise
|
||||
|
||||
stdCtx, cancel := installSignals(stdCtx)
|
||||
defer cancel()
|
||||
|
||||
|
|
110
cmd/dump_test.go
110
cmd/dump_test.go
|
@ -4,40 +4,32 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/mholt/archiver/v3"
|
||||
"github.com/mholt/archives"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type mockArchiver struct {
|
||||
addedFiles []string
|
||||
}
|
||||
|
||||
func (mockArchiver) Create(out io.Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockArchiver) Write(f archiver.File) error {
|
||||
m.addedFiles = append(m.addedFiles, f.Name())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mockArchiver) Close() error {
|
||||
return nil
|
||||
func mockArchiverAsync(ch chan archives.ArchiveAsyncJob, files *[]string) {
|
||||
for job := range ch {
|
||||
*files = append(*files, job.File.NameInArchive)
|
||||
job.Result <- nil
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddRecursiveExclude(t *testing.T) {
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
archiver := &mockArchiver{}
|
||||
ch := make(chan archives.ArchiveAsyncJob)
|
||||
var files []string
|
||||
go mockArchiverAsync(ch, &files)
|
||||
|
||||
err := addRecursiveExclude(archiver, "", dir, []string{}, false)
|
||||
dir := t.TempDir()
|
||||
|
||||
err := addRecursiveExclude(ch, "", dir, []string{}, false)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, archiver.addedFiles)
|
||||
assert.Empty(t, files)
|
||||
})
|
||||
|
||||
t.Run("Single file", func(t *testing.T) {
|
||||
|
@ -46,20 +38,25 @@ func TestAddRecursiveExclude(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
t.Run("No exclude", func(t *testing.T) {
|
||||
archiver := &mockArchiver{}
|
||||
ch := make(chan archives.ArchiveAsyncJob)
|
||||
var files []string
|
||||
go mockArchiverAsync(ch, &files)
|
||||
|
||||
err = addRecursiveExclude(archiver, "", dir, nil, false)
|
||||
err := addRecursiveExclude(ch, "", dir, nil, false)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, archiver.addedFiles, 1)
|
||||
assert.Contains(t, archiver.addedFiles, "example")
|
||||
|
||||
assert.Len(t, files, 1)
|
||||
assert.Contains(t, files, "example")
|
||||
})
|
||||
|
||||
t.Run("With exclude", func(t *testing.T) {
|
||||
archiver := &mockArchiver{}
|
||||
ch := make(chan archives.ArchiveAsyncJob)
|
||||
var files []string
|
||||
go mockArchiverAsync(ch, &files)
|
||||
|
||||
err = addRecursiveExclude(archiver, "", dir, []string{dir + "/example"}, false)
|
||||
err := addRecursiveExclude(ch, "", dir, []string{dir + "/example"}, false)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, archiver.addedFiles)
|
||||
assert.Empty(t, files)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -73,46 +70,57 @@ func TestAddRecursiveExclude(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
t.Run("No exclude", func(t *testing.T) {
|
||||
archiver := &mockArchiver{}
|
||||
ch := make(chan archives.ArchiveAsyncJob)
|
||||
var files []string
|
||||
go mockArchiverAsync(ch, &files)
|
||||
|
||||
err = addRecursiveExclude(archiver, "", dir, nil, false)
|
||||
err := addRecursiveExclude(ch, "", dir, nil, false)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, archiver.addedFiles, 5)
|
||||
assert.Contains(t, archiver.addedFiles, "deep")
|
||||
assert.Contains(t, archiver.addedFiles, "deep/nested")
|
||||
assert.Contains(t, archiver.addedFiles, "deep/nested/folder")
|
||||
assert.Contains(t, archiver.addedFiles, "deep/nested/folder/example")
|
||||
assert.Contains(t, archiver.addedFiles, "deep/nested/folder/another-file")
|
||||
assert.Len(t, files, 5)
|
||||
|
||||
assert.Contains(t, files, "deep")
|
||||
assert.Contains(t, files, "deep/nested")
|
||||
assert.Contains(t, files, "deep/nested/folder")
|
||||
assert.Contains(t, files, "deep/nested/folder/example")
|
||||
assert.Contains(t, files, "deep/nested/folder/another-file")
|
||||
})
|
||||
|
||||
t.Run("Exclude first directory", func(t *testing.T) {
|
||||
archiver := &mockArchiver{}
|
||||
ch := make(chan archives.ArchiveAsyncJob)
|
||||
var files []string
|
||||
go mockArchiverAsync(ch, &files)
|
||||
|
||||
err = addRecursiveExclude(archiver, "", dir, []string{dir + "/deep"}, false)
|
||||
err := addRecursiveExclude(ch, "", dir, []string{dir + "/deep"}, false)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, archiver.addedFiles)
|
||||
assert.Empty(t, files)
|
||||
})
|
||||
|
||||
t.Run("Exclude nested directory", func(t *testing.T) {
|
||||
archiver := &mockArchiver{}
|
||||
ch := make(chan archives.ArchiveAsyncJob)
|
||||
var files []string
|
||||
go mockArchiverAsync(ch, &files)
|
||||
|
||||
err = addRecursiveExclude(archiver, "", dir, []string{dir + "/deep/nested/folder"}, false)
|
||||
err := addRecursiveExclude(ch, "", dir, []string{dir + "/deep/nested/folder"}, false)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, archiver.addedFiles, 2)
|
||||
assert.Contains(t, archiver.addedFiles, "deep")
|
||||
assert.Contains(t, archiver.addedFiles, "deep/nested")
|
||||
assert.Len(t, files, 2)
|
||||
|
||||
assert.Contains(t, files, "deep")
|
||||
assert.Contains(t, files, "deep/nested")
|
||||
})
|
||||
|
||||
t.Run("Exclude file", func(t *testing.T) {
|
||||
archiver := &mockArchiver{}
|
||||
ch := make(chan archives.ArchiveAsyncJob)
|
||||
var files []string
|
||||
go mockArchiverAsync(ch, &files)
|
||||
|
||||
err = addRecursiveExclude(archiver, "", dir, []string{dir + "/deep/nested/folder/example"}, false)
|
||||
err := addRecursiveExclude(ch, "", dir, []string{dir + "/deep/nested/folder/example"}, false)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, archiver.addedFiles, 4)
|
||||
assert.Contains(t, archiver.addedFiles, "deep")
|
||||
assert.Contains(t, archiver.addedFiles, "deep/nested")
|
||||
assert.Contains(t, archiver.addedFiles, "deep/nested/folder")
|
||||
assert.Contains(t, archiver.addedFiles, "deep/nested/folder/another-file")
|
||||
assert.Len(t, files, 4)
|
||||
|
||||
assert.Contains(t, files, "deep")
|
||||
assert.Contains(t, files, "deep/nested")
|
||||
assert.Contains(t, files, "deep/nested/folder")
|
||||
assert.Contains(t, files, "deep/nested/folder/another-file")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ func microcmdGenerateInternalToken() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "INTERNAL_TOKEN",
|
||||
Usage: "Generate a new INTERNAL_TOKEN",
|
||||
Before: noDanglingArgs,
|
||||
Action: runGenerateInternalToken,
|
||||
}
|
||||
}
|
||||
|
@ -51,6 +52,7 @@ func microcmdGenerateLfsJwtSecret() *cli.Command {
|
|||
Name: "JWT_SECRET",
|
||||
Aliases: []string{"LFS_JWT_SECRET"},
|
||||
Usage: "Generate a new JWT_SECRET",
|
||||
Before: noDanglingArgs,
|
||||
Action: runGenerateLfsJwtSecret,
|
||||
}
|
||||
}
|
||||
|
@ -59,6 +61,7 @@ func microcmdGenerateSecretKey() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "SECRET_KEY",
|
||||
Usage: "Generate a new SECRET_KEY",
|
||||
Before: noDanglingArgs,
|
||||
Action: runGenerateSecretKey,
|
||||
}
|
||||
}
|
||||
|
|
44
cmd/hook.go
44
cmd/hook.go
|
@ -231,8 +231,6 @@ Forgejo or set your environment appropriately.`, "")
|
|||
}
|
||||
}
|
||||
|
||||
supportProcReceive := git.CheckGitVersionAtLeast("2.29") == nil
|
||||
|
||||
for scanner.Scan() {
|
||||
// TODO: support news feeds for wiki
|
||||
if isWiki {
|
||||
|
@ -250,31 +248,25 @@ Forgejo or set your environment appropriately.`, "")
|
|||
total++
|
||||
lastline++
|
||||
|
||||
// If the ref is a branch or tag, check if it's protected
|
||||
// if supportProcReceive all ref should be checked because
|
||||
// permission check was delayed
|
||||
if supportProcReceive || refFullName.IsBranch() || refFullName.IsTag() {
|
||||
oldCommitIDs[count] = oldCommitID
|
||||
newCommitIDs[count] = newCommitID
|
||||
refFullNames[count] = refFullName
|
||||
count++
|
||||
fmt.Fprint(out, "*")
|
||||
// All references should be checked because permission check was delayed.
|
||||
oldCommitIDs[count] = oldCommitID
|
||||
newCommitIDs[count] = newCommitID
|
||||
refFullNames[count] = refFullName
|
||||
count++
|
||||
fmt.Fprint(out, "*")
|
||||
|
||||
if count >= hookBatchSize {
|
||||
fmt.Fprintf(out, " Checking %d references\n", count)
|
||||
if count >= hookBatchSize {
|
||||
fmt.Fprintf(out, " Checking %d references\n", count)
|
||||
|
||||
hookOptions.OldCommitIDs = oldCommitIDs
|
||||
hookOptions.NewCommitIDs = newCommitIDs
|
||||
hookOptions.RefFullNames = refFullNames
|
||||
extra := private.HookPreReceive(ctx, username, reponame, hookOptions)
|
||||
if extra.HasError() {
|
||||
return fail(ctx, extra.UserMsg, "HookPreReceive(batch) failed: %v", extra.Error)
|
||||
}
|
||||
count = 0
|
||||
lastline = 0
|
||||
hookOptions.OldCommitIDs = oldCommitIDs
|
||||
hookOptions.NewCommitIDs = newCommitIDs
|
||||
hookOptions.RefFullNames = refFullNames
|
||||
extra := private.HookPreReceive(ctx, username, reponame, hookOptions)
|
||||
if extra.HasError() {
|
||||
return fail(ctx, extra.UserMsg, "HookPreReceive(batch) failed: %v", extra.Error)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprint(out, ".")
|
||||
count = 0
|
||||
lastline = 0
|
||||
}
|
||||
if lastline >= hookBatchSize {
|
||||
fmt.Fprint(out, "\n")
|
||||
|
@ -513,10 +505,6 @@ Forgejo or set your environment appropriately.`, "")
|
|||
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)
|
||||
repoUser := os.Getenv(repo_module.EnvRepoUsername)
|
||||
repoName := os.Getenv(repo_module.EnvRepoName)
|
||||
|
|
|
@ -21,7 +21,7 @@ func cmdKeys() *cli.Command {
|
|||
Name: "keys",
|
||||
Usage: "(internal) Should only be called by SSH server",
|
||||
Description: "Queries the Forgejo database to get the authorized command for a given ssh key fingerprint",
|
||||
Before: PrepareConsoleLoggerLevel(log.FATAL),
|
||||
Before: multipleBefore(noDanglingArgs, PrepareConsoleLoggerLevel(log.FATAL)),
|
||||
Action: runKeys,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
|
@ -39,6 +39,7 @@ func subcmdShutdown() *cli.Command {
|
|||
Name: "debug",
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runShutdown,
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +53,7 @@ func subcmdRestart() *cli.Command {
|
|||
Name: "debug",
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runRestart,
|
||||
}
|
||||
}
|
||||
|
@ -65,6 +67,7 @@ func subcmdReloadTemplates() *cli.Command {
|
|||
Name: "debug",
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runReloadTemplates,
|
||||
}
|
||||
}
|
||||
|
@ -73,6 +76,7 @@ func subcmdFlushQueues() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "flush-queues",
|
||||
Usage: "Flush queues in the running process",
|
||||
Before: noDanglingArgs,
|
||||
Action: runFlushQueues,
|
||||
Flags: []cli.Flag{
|
||||
&cli.DurationFlag{
|
||||
|
@ -95,6 +99,7 @@ func subCmdProcesses() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "processes",
|
||||
Usage: "Display running processes within the current process",
|
||||
Before: noDanglingArgs,
|
||||
Action: runProcesses,
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
|
|
|
@ -44,6 +44,11 @@ func defaultLoggingFlags() []cli.Flag {
|
|||
Aliases: []string{"e"},
|
||||
Usage: "Matching expression for the logger",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "exclusion",
|
||||
Aliases: []string{"x"},
|
||||
Usage: "Exclusion for the logger",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "prefix",
|
||||
Aliases: []string{"p"},
|
||||
|
@ -72,6 +77,7 @@ func subcmdLogging() *cli.Command {
|
|||
Name: "debug",
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runPauseLogging,
|
||||
}, {
|
||||
Name: "resume",
|
||||
|
@ -81,6 +87,7 @@ func subcmdLogging() *cli.Command {
|
|||
Name: "debug",
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runResumeLogging,
|
||||
}, {
|
||||
Name: "release-and-reopen",
|
||||
|
@ -90,6 +97,7 @@ func subcmdLogging() *cli.Command {
|
|||
Name: "debug",
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runReleaseReopenLogging,
|
||||
}, {
|
||||
Name: "remove",
|
||||
|
@ -151,6 +159,7 @@ func subcmdLogging() *cli.Command {
|
|||
Usage: "Compression level to use",
|
||||
},
|
||||
}...),
|
||||
Before: noDanglingArgs,
|
||||
Action: runAddFileLogger,
|
||||
}, {
|
||||
Name: "conn",
|
||||
|
@ -177,6 +186,7 @@ func subcmdLogging() *cli.Command {
|
|||
Usage: "Host address and port to connect to (defaults to :7020)",
|
||||
},
|
||||
}...),
|
||||
Before: noDanglingArgs,
|
||||
Action: runAddConnLogger,
|
||||
},
|
||||
},
|
||||
|
@ -192,6 +202,7 @@ func subcmdLogging() *cli.Command {
|
|||
Usage: "Switch off SQL logging",
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runSetLogSQL,
|
||||
},
|
||||
},
|
||||
|
@ -286,6 +297,9 @@ func commonAddLogger(ctx context.Context, c *cli.Command, mode string, vals map[
|
|||
if len(c.String("expression")) > 0 {
|
||||
vals["expression"] = c.String("expression")
|
||||
}
|
||||
if len(c.String("exclusion")) > 0 {
|
||||
vals["exclusion"] = c.String("exclusion")
|
||||
}
|
||||
if len(c.String("prefix")) > 0 {
|
||||
vals["prefix"] = c.String("prefix")
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"context"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/models/migrations"
|
||||
"forgejo.org/models/gitea_migrations"
|
||||
"forgejo.org/modules/log"
|
||||
"forgejo.org/modules/setting"
|
||||
|
||||
|
@ -20,6 +20,7 @@ func cmdMigrate() *cli.Command {
|
|||
Name: "migrate",
|
||||
Usage: "Migrate the database",
|
||||
Description: "This is a command for migrating the database, so that you can run 'forgejo admin user create' before starting the server.",
|
||||
Before: noDanglingArgs,
|
||||
Action: runMigrate,
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +44,7 @@ func runMigrate(stdCtx context.Context, ctx *cli.Command) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return migrations.Migrate(masterEngine)
|
||||
return gitea_migrations.Migrate(masterEngine)
|
||||
}); err != nil {
|
||||
log.Fatal("Failed to initialize ORM engine: %v", err)
|
||||
return err
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
actions_model "forgejo.org/models/actions"
|
||||
"forgejo.org/models/db"
|
||||
git_model "forgejo.org/models/git"
|
||||
"forgejo.org/models/migrations"
|
||||
"forgejo.org/models/gitea_migrations"
|
||||
packages_model "forgejo.org/models/packages"
|
||||
repo_model "forgejo.org/models/repo"
|
||||
user_model "forgejo.org/models/user"
|
||||
|
@ -32,6 +32,7 @@ func cmdMigrateStorage() *cli.Command {
|
|||
Name: "migrate-storage",
|
||||
Usage: "Migrate the storage",
|
||||
Description: "Copies stored files from storage configured in app.ini to parameter-configured storage",
|
||||
Before: noDanglingArgs,
|
||||
Action: runMigrateStorage,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
@ -199,7 +200,7 @@ func runMigrateStorage(stdCtx context.Context, ctx *cli.Command) error {
|
|||
log.Info("Configuration file: %s", setting.CustomConf)
|
||||
|
||||
if err := db.InitEngineWithMigration(context.Background(), func(e db.Engine) error {
|
||||
return migrations.Migrate(e.(*xorm.Engine))
|
||||
return gitea_migrations.Migrate(e.(*xorm.Engine))
|
||||
}); err != nil {
|
||||
log.Fatal("Failed to initialize ORM engine: %v", err)
|
||||
return err
|
||||
|
|
|
@ -19,6 +19,7 @@ func cmdRestoreRepository() *cli.Command {
|
|||
Name: "restore-repo",
|
||||
Usage: "Restore the repository from disk",
|
||||
Description: "This is a command for restoring the repository data.",
|
||||
Before: noDanglingArgs,
|
||||
Action: runRestoreRepository,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
23
cmd/serv.go
23
cmd/serv.go
|
@ -88,6 +88,14 @@ var (
|
|||
alphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`)
|
||||
)
|
||||
|
||||
func sshLog(ctx context.Context, level log.Level, message string) error {
|
||||
if testing.Testing() || setting.InternalToken == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return private.SSHLog(ctx, level, message)
|
||||
}
|
||||
|
||||
// fail prints message to stdout, it's mainly used for git serv and git hook commands.
|
||||
// The output will be passed to git client and shown to user.
|
||||
func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error {
|
||||
|
@ -112,10 +120,7 @@ func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error
|
|||
logMsg = userMessage + ". " + logMsg
|
||||
}
|
||||
}
|
||||
// Don't send an log if this is done in a test and no InternalToken is set.
|
||||
if !testing.Testing() || setting.InternalToken != "" {
|
||||
_ = private.SSHLog(ctx, true, logMsg)
|
||||
}
|
||||
_ = sshLog(ctx, log.ERROR, logMsg)
|
||||
}
|
||||
return cli.Exit("", 1)
|
||||
}
|
||||
|
@ -193,12 +198,10 @@ func runServ(ctx context.Context, c *cli.Command) error {
|
|||
}
|
||||
|
||||
if len(words) < 2 {
|
||||
if git.CheckGitVersionAtLeast("2.29") == nil {
|
||||
// for AGit Flow
|
||||
if cmd == "ssh_info" {
|
||||
fmt.Print(`{"type":"agit","version":1}`)
|
||||
return nil
|
||||
}
|
||||
// for AGit Flow
|
||||
if cmd == "ssh_info" {
|
||||
fmt.Print(`{"type":"agit","version":1}`)
|
||||
return nil
|
||||
}
|
||||
return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd)
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ func cmdWeb() *cli.Command {
|
|||
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`,
|
||||
Before: PrepareConsoleLoggerLevel(log.INFO),
|
||||
Before: multipleBefore(noDanglingArgs, PrepareConsoleLoggerLevel(log.INFO)),
|
||||
Action: runWeb,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"forgejo.org/modules/graceful"
|
||||
"forgejo.org/modules/log"
|
||||
"forgejo.org/modules/process"
|
||||
"forgejo.org/modules/proxy"
|
||||
"forgejo.org/modules/setting"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
|
@ -76,6 +77,12 @@ func runACME(listenAddr string, m http.Handler) error {
|
|||
ListenHost: setting.HTTPAddr,
|
||||
AltTLSALPNPort: altTLSALPNPort,
|
||||
AltHTTPPort: altHTTPPort,
|
||||
HTTPProxy: proxy.Proxy(),
|
||||
}
|
||||
|
||||
// Preserve behavior to use Let's encrypt test CA when Let's encrypt is CA.
|
||||
if certmagic.DefaultACME.CA == certmagic.LetsEncryptProductionCA {
|
||||
certmagic.DefaultACME.TestCA = certmagic.LetsEncryptStagingCA
|
||||
}
|
||||
|
||||
magic := certmagic.NewDefault()
|
||||
|
|
50
contrib/coverage-helper.sh
Executable file
50
contrib/coverage-helper.sh
Executable file
|
@ -0,0 +1,50 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
#set -x
|
||||
PS4='${BASH_SOURCE[0]}:$LINENO: ${FUNCNAME[0]}: '
|
||||
|
||||
#
|
||||
# Those must be explicitly required and are excluded from the full list of packages because they
|
||||
# would interfere with the testing fixtures.
|
||||
#
|
||||
excluded+='forgejo.org/models/gitea_migrations|' # must be run before database specific tests
|
||||
excluded+='forgejo.org/models/forgejo_migrations_legacy|' # must be run before database specific tests
|
||||
excluded+='forgejo.org/tests/integration/migration-test|' # must be run before database specific tests
|
||||
excluded+='forgejo.org/tests|' # only tests, no coverage to get there
|
||||
excluded+='forgejo.org/tests/e2e|' # JavaScript is not in scope here and if it adds coverage it should not be counted
|
||||
excluded+='FAKETERMINATOR' # do not modify
|
||||
|
||||
: ${COVERAGEDIR:=$(pwd)/coverage/data}
|
||||
: ${GO:=$(go env GOROOT)/bin/go}
|
||||
|
||||
DEFAULT_TEST_PACKAGES=$($GO list ./... | grep -E -v "$excluded")
|
||||
|
||||
COVERED_PACKAGES=$($GO list ./...)
|
||||
COVERED_PACKAGES=$(echo $COVERED_PACKAGES | sed -e 's/ /,/g')
|
||||
|
||||
function run_test() {
|
||||
local package="$1"
|
||||
if echo "$package" | grep --quiet --fixed-string ".."; then
|
||||
echo "$package contains a suspicious .."
|
||||
return 1
|
||||
fi
|
||||
|
||||
local coverage="$COVERAGEDIR/$COVERAGE_TEST_DATABASE/$package"
|
||||
rm -fr $coverage
|
||||
mkdir -p $coverage
|
||||
|
||||
#
|
||||
# -race cannot be used because it requires -covermode atomic which is
|
||||
# different from the end-to-end tests and would cause issues wen merging
|
||||
#
|
||||
$GO test -timeout=20m -tags='sqlite sqlite_unlock_notify' -cover $package -coverpkg $COVERED_PACKAGES $COVERAGE_TEST_ARGS -args -test.gocoverdir=$coverage |& grep -v 'warning: no packages being tested depend on matches for pattern'
|
||||
}
|
||||
|
||||
function test_packages() {
|
||||
for package in ${@:-$DEFAULT_TEST_PACKAGES}; do
|
||||
run_test $package
|
||||
done
|
||||
}
|
||||
|
||||
"$@"
|
|
@ -12,10 +12,10 @@
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
;; These values are environment-dependent but form the basis of a lot of values. They will be
|
||||
;; reported as part of the default configuration when running `gitea help` or on start-up. The order they are emitted there is slightly different but we will list them here in the order they are set-up.
|
||||
;; reported as part of the default configuration when running `forgejo help` or on start-up. The order they are emitted there is slightly different but we will list them here in the order they are set-up.
|
||||
;;
|
||||
;; - _`AppPath`_: This is the absolute path of the running gitea binary.
|
||||
;; - _`AppWorkPath`_: This refers to "working path" of the `gitea` binary. It is determined by using the first set thing in the following hierarchy:
|
||||
;; - _`AppPath`_: This is the absolute path of the running Forgejo binary.
|
||||
;; - _`AppWorkPath`_: This refers to "working path" of the `forgejo` binary. It is determined by using the first set thing in the following hierarchy:
|
||||
;; - The "WORK_PATH" option in "app.ini" file
|
||||
;; - The `--work-path` flag passed to the binary
|
||||
;; - The environment variable `$GITEA_WORK_DIR`
|
||||
|
@ -54,7 +54,7 @@ APP_NAME = ; Forgejo: Beyond coding. We Forge.
|
|||
RUN_USER = ; git
|
||||
;;
|
||||
;; Application run mode, affects performance and debugging: "dev" or "prod", default is "prod"
|
||||
;; Mode "dev" makes Gitea easier to develop and debug, values other than "dev" are treated as "prod" which is for production use.
|
||||
;; Mode "dev" makes Forgejo easier to develop and debug, values other than "dev" are treated as "prod" which is for production use.
|
||||
;RUN_MODE = prod
|
||||
;;
|
||||
;; The working directory, see the comment of AppWorkPath above
|
||||
|
@ -127,7 +127,7 @@ RUN_USER = ; git
|
|||
;; Permission for unix socket
|
||||
;UNIX_SOCKET_PERMISSION = 666
|
||||
;;
|
||||
;; Local (DMZ) URL for Gitea workers (such as SSH update) accessing web service. In
|
||||
;; Local (DMZ) URL for Forgejo workers (such as SSH update) accessing web service. In
|
||||
;; most cases you do not need to change the default value. Alter it only if
|
||||
;; your SSH server node is not the same as HTTP node. For different protocol, the default
|
||||
;; values are different. If `PROTOCOL` is `http+unix`, the default value is `http://unix/`.
|
||||
|
@ -169,11 +169,11 @@ RUN_USER = ; git
|
|||
;; Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'.
|
||||
;SSH_ROOT_PATH =
|
||||
;;
|
||||
;; Gitea will create a authorized_keys file by default when it is not using the internal ssh server
|
||||
;; Forgejo will create a authorized_keys file by default when it is not using the internal ssh server
|
||||
;; If you intend to use the AuthorizedKeysCommand functionality then you should turn this off.
|
||||
;SSH_CREATE_AUTHORIZED_KEYS_FILE = true
|
||||
;;
|
||||
;; Gitea will create a authorized_principals file by default when it is not using the internal ssh server
|
||||
;; Forgejo will create a authorized_principals file by default when it is not using the internal ssh server
|
||||
;; If you intend to use the AuthorizedPrincipalsCommand functionality then you should turn this off.
|
||||
;SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE = true
|
||||
;;
|
||||
|
@ -198,7 +198,7 @@ RUN_USER = ; git
|
|||
;; default is the system temporary directory.
|
||||
;SSH_KEY_TEST_PATH =
|
||||
;;
|
||||
;; Use `ssh-keygen` to parse public SSH keys. The value is passed to the shell. By default, Gitea does the parsing itself.
|
||||
;; Use `ssh-keygen` to parse public SSH keys. The value is passed to the shell. By default, Forgejo does the parsing itself.
|
||||
;SSH_KEYGEN_PATH =
|
||||
;;
|
||||
;; Enable SSH Authorized Key Backup when rewriting all keys, default is false
|
||||
|
@ -220,9 +220,9 @@ RUN_USER = ; git
|
|||
;; E.g."ssh-<algorithm> <key>". or "ssh-<algorithm> <key1>, ssh-<algorithm> <key2>".
|
||||
;; For more information see "TrustedUserCAKeys" in the sshd config manpages.
|
||||
;SSH_TRUSTED_USER_CA_KEYS =
|
||||
;; Absolute path of the `TrustedUserCaKeys` file gitea will manage.
|
||||
;; Absolute path of the `TrustedUserCaKeys` file Forgejo will manage.
|
||||
;; Default this `RUN_USER`/.ssh/gitea-trusted-user-ca-keys.pem
|
||||
;; If you're running your own ssh server and you want to use the gitea managed file you'll also need to modify your
|
||||
;; If you're running your own ssh server and you want to use the Forgejo managed file you'll also need to modify your
|
||||
;; sshd_config to point to this file. The official docker image will automatically work without further configuration.
|
||||
;SSH_TRUSTED_USER_CA_KEYS_FILENAME =
|
||||
;;
|
||||
|
@ -277,7 +277,7 @@ RUN_USER = ; git
|
|||
;; Manual TLS settings: (Only applicable if ENABLE_ACME=false)
|
||||
;;
|
||||
;; Generate steps:
|
||||
;; $ ./gitea cert -ca=true -duration=8760h0m0s -host=myhost.example.com
|
||||
;; $ ./forgejo cert -ca=true -duration=8760h0m0s -host=myhost.example.com
|
||||
;;
|
||||
;; Or from a .pfx file exported from the Windows certificate store (do
|
||||
;; not forget to export the private key):
|
||||
|
@ -288,7 +288,7 @@ RUN_USER = ; git
|
|||
;KEY_FILE = https/key.pem
|
||||
;;
|
||||
;; Root directory containing templates and static files.
|
||||
;; default is the path where Gitea is executed
|
||||
;; default is the path where Forgejo is executed
|
||||
;STATIC_ROOT_PATH = ; Will default to the built-in value _`StaticRootPath`_
|
||||
;;
|
||||
;; Default path for App data
|
||||
|
@ -302,7 +302,7 @@ RUN_USER = ; git
|
|||
;; For "serve" command it dumps to disk at PPROF_DATA_PATH as (cpuprofile|memprofile)_<username>_<temporary id>
|
||||
;ENABLE_PPROF = false
|
||||
;;
|
||||
;; PPROF_DATA_PATH, use an absolute path when you start gitea as service
|
||||
;; PPROF_DATA_PATH, use an absolute path when you start Forgejo as service
|
||||
;PPROF_DATA_PATH = data/tmp/pprof ; Path is relative to _`AppWorkPath`_
|
||||
;;
|
||||
;; Landing page, can be "home", "explore", "organizations", "login", or any URL such as "/org/repo" or even "https://anotherwebsite.com"
|
||||
|
@ -374,7 +374,7 @@ DB_TYPE = sqlite3
|
|||
;USER = root
|
||||
;PASSWD = ;Use PASSWD = `your password` for quoting if you use special characters in the password.
|
||||
;SSL_MODE = false ; either "false" (default), "true", or "skip-verify"
|
||||
;CHARSET_COLLATION = ; Empty as default, Gitea will try to find a case-sensitive collation. Don't change it unless you clearly know what you need.
|
||||
;CHARSET_COLLATION = ; Empty as default, Forgejo will try to find a case-sensitive collation. Don't change it unless you clearly know what you need.
|
||||
;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
|
@ -440,7 +440,7 @@ SECRET_KEY =
|
|||
;; This key is VERY IMPORTANT. If you lose it, the data encrypted by it (like 2FA secret) can't be decrypted anymore.
|
||||
;SECRET_KEY_URI = file:/etc/gitea/secret_key
|
||||
;;
|
||||
;; Secret used to validate communication within Gitea binary.
|
||||
;; Secret used to validate communication within Forgejo binary.
|
||||
INTERNAL_TOKEN =
|
||||
;;
|
||||
;; Alternative location to specify internal token, instead of this file; you cannot specify both this and INTERNAL_TOKEN, and must pick one
|
||||
|
@ -449,6 +449,9 @@ INTERNAL_TOKEN =
|
|||
;; How long to remember that a user is logged in before requiring relogin (in days)
|
||||
;LOGIN_REMEMBER_DAYS = 31
|
||||
;;
|
||||
;; Require 2FA globally for none|all|admin.
|
||||
;GLOBAL_TWO_FACTOR_REQUIREMENT = none
|
||||
;;
|
||||
;; Name of cookie used to store authentication information.
|
||||
;COOKIE_REMEMBER_NAME = gitea_incredible
|
||||
;;
|
||||
|
@ -471,9 +474,9 @@ INTERNAL_TOKEN =
|
|||
;;
|
||||
;; Set to false to allow users with git hook privileges to create custom git hooks.
|
||||
;; Custom git hooks can be used to perform arbitrary code execution on the host operating system.
|
||||
;; This enables the users to access and modify this config file and the Gitea database and interrupt the Gitea service.
|
||||
;; By modifying the Gitea database, users can gain Gitea administrator privileges.
|
||||
;; It also enables them to access other resources available to the user on the operating system that is running the Gitea instance and perform arbitrary actions in the name of the Gitea OS user.
|
||||
;; This enables the users to access and modify this config file and the Forgejo database and interrupt the Forgejo service.
|
||||
;; By modifying the Forgejo database, users can gain Forgejo administrator privileges.
|
||||
;; It also enables them to access other resources available to the user on the operating system that is running the Forgejo instance and perform arbitrary actions in the name of the Forgejo OS user.
|
||||
;; WARNING: This maybe harmful to you website or your operating system.
|
||||
;; WARNING: Setting this to true does not change existing hooks in git repos; adjust it before if necessary.
|
||||
;DISABLE_GIT_HOOKS = true
|
||||
|
@ -481,7 +484,7 @@ INTERNAL_TOKEN =
|
|||
;; Set to true to disable webhooks feature.
|
||||
;DISABLE_WEBHOOKS = false
|
||||
;;
|
||||
;; Set to false to allow pushes to gitea repositories despite having an incomplete environment - NOT RECOMMENDED
|
||||
;; Set to false to allow pushes to Forgejo repositories despite having an incomplete environment - NOT RECOMMENDED
|
||||
;ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET = true
|
||||
;;
|
||||
;;Comma separated list of character classes required to pass minimum complexity.
|
||||
|
@ -542,7 +545,7 @@ ENABLED = true
|
|||
;; The file must contain a RSA or ECDSA private key in the PKCS8 format. If no key exists a 4096 bit key will be created for you.
|
||||
;JWT_SIGNING_PRIVATE_KEY_FILE = jwt/private.pem
|
||||
;;
|
||||
;; OAuth2 authentication secret for access and refresh tokens, change this yourself to a unique string. CLI generate option is helpful in this case. https://docs.gitea.io/en-us/command-line/#generate
|
||||
;; OAuth2 authentication secret for access and refresh tokens, change this yourself to a unique string. CLI generate option is helpful in this case. https://forgejo.org/docs/latest/admin/command-line/#generate-secret
|
||||
;; This setting is only needed if JWT_SIGNING_ALGORITHM is set to HS256, HS384 or HS512.
|
||||
;JWT_SECRET =
|
||||
;;
|
||||
|
@ -592,13 +595,10 @@ LEVEL = Info
|
|||
;BUFFER_LEN = 10000
|
||||
;;
|
||||
;; Sub logger modes, a single comma means use default MODE above, empty means disable it
|
||||
;logger.access.MODE=
|
||||
;logger.router.MODE=,
|
||||
;logger.xorm.MODE=,
|
||||
;;
|
||||
;; Collect SSH logs (Creates log from ssh git request)
|
||||
;;
|
||||
;ENABLE_SSH_LOG = false
|
||||
;LOGGER_ACCESS_MODE=
|
||||
;LOGGER_ROUTER_MODE=,
|
||||
;LOGGER_XORM_MODE=,
|
||||
;LOGGER_SSH_MODE= ;; SSH logs from ssh git request
|
||||
;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
|
@ -631,6 +631,7 @@ LEVEL = Info
|
|||
;LEVEL=
|
||||
;FLAGS = stdflags or journald
|
||||
;EXPRESSION =
|
||||
;EXCLUSION =
|
||||
;PREFIX =
|
||||
;COLORIZE = false
|
||||
;;
|
||||
|
@ -669,7 +670,7 @@ LEVEL = Info
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
;; The path of git executable. If empty, Gitea searches through the PATH environment.
|
||||
;; The path of git executable. If empty, Forgejo searches through the PATH environment.
|
||||
;PATH =
|
||||
;;
|
||||
;; The HOME directory for Git
|
||||
|
@ -754,17 +755,17 @@ LEVEL = Info
|
|||
;; Whether a new user needs to be confirmed manually after registration. (Requires `REGISTER_EMAIL_CONFIRM` to be disabled.)
|
||||
;REGISTER_MANUAL_CONFIRM = false
|
||||
;;
|
||||
;; List of domain names that are allowed to be used to register on a Gitea instance, wildcard is supported
|
||||
;; eg: gitea.io,example.com,*.mydomain.com
|
||||
;; List of domain names that are allowed to be used to register on a Forgejo instance, wildcard is supported
|
||||
;; eg: forgejo.org,example.com,*.mydomain.com
|
||||
;EMAIL_DOMAIN_ALLOWLIST =
|
||||
;;
|
||||
;; Comma-separated list of domain names that are not allowed to be used to register on a Gitea instance, wildcard is supported
|
||||
;; Comma-separated list of domain names that are not allowed to be used to register on a Forgejo instance, wildcard is supported
|
||||
;EMAIL_DOMAIN_BLOCKLIST =
|
||||
;;
|
||||
;; Disallow registration, only allow admins to create accounts.
|
||||
;DISABLE_REGISTRATION = false
|
||||
;;
|
||||
;; Allow registration only using gitea itself, it works only when DISABLE_REGISTRATION is false
|
||||
;; Allow registration only using Forgejo itself, it works only when DISABLE_REGISTRATION is false
|
||||
;ALLOW_ONLY_INTERNAL_REGISTRATION = false
|
||||
;;
|
||||
;; Allow registration only using third-party services, it works only when DISABLE_REGISTRATION is false
|
||||
|
@ -776,7 +777,7 @@ LEVEL = Info
|
|||
;; Mail notification
|
||||
;ENABLE_NOTIFY_MAIL = false
|
||||
;;
|
||||
;; This setting enables gitea to be signed in with HTTP BASIC Authentication using the user's password
|
||||
;; This setting enables Forgejo to be signed in with HTTP BASIC Authentication using the user's password
|
||||
;; If you set this to false you will not be able to access the tokens endpoints on the API with your password
|
||||
;; Please note that setting this to false will not disable OAuth Basic or Basic authentication using a token
|
||||
;ENABLE_BASIC_AUTHENTICATION = true
|
||||
|
@ -985,7 +986,10 @@ LEVEL = Info
|
|||
;; Default private when using push-to-create
|
||||
;DEFAULT_PUSH_CREATE_PRIVATE = true
|
||||
;;
|
||||
;; Global limit of repositories per user, applied at creation time. -1 means no limit
|
||||
;; Global maximum creation limit of repositories for each user and organization; `-1` means no limit. If regular users
|
||||
;; can create organizations (see `DISABLE_REGULAR_ORG_CREATION`) then they can bypass this by creating new organizations.
|
||||
;; The global limit can be overridden by an administrator on each user and repository through their respective
|
||||
;; configuration UIs.
|
||||
;MAX_CREATION_LIMIT = -1
|
||||
;;
|
||||
;; Preferred Licenses to place at the top of the List
|
||||
|
@ -1008,7 +1012,7 @@ LEVEL = Info
|
|||
;; Close issues as long as a commit on any branch marks it as fixed
|
||||
;DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH = false
|
||||
;;
|
||||
;; Allow users to push local repositories to Gitea and have them automatically created for a user or an org
|
||||
;; Allow users to push local repositories to Forgejo and have them automatically created for a user or an org
|
||||
;ENABLE_PUSH_CREATE_USER = false
|
||||
;ENABLE_PUSH_CREATE_ORG = false
|
||||
;;
|
||||
|
@ -1072,7 +1076,7 @@ LEVEL = Info
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
;; Path for local repository copy. Defaults to `tmp/local-repo` (content gets deleted on gitea restart)
|
||||
;; Path for local repository copy. Defaults to `tmp/local-repo` (content gets deleted on Forgejo restart)
|
||||
;LOCAL_COPY_PATH = tmp/local-repo
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -1084,7 +1088,7 @@ LEVEL = Info
|
|||
;; Whether repository file uploads are enabled. Defaults to `true`
|
||||
;ENABLED = true
|
||||
;;
|
||||
;; Path for uploads. Defaults to `data/tmp/uploads` (content gets deleted on gitea restart)
|
||||
;; Path for uploads. Defaults to `data/tmp/uploads` (content gets deleted on Forgejo restart)
|
||||
;TEMP_PATH = data/tmp/uploads
|
||||
;;
|
||||
;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
|
||||
|
@ -1183,7 +1187,7 @@ LEVEL = Info
|
|||
;; Sets the default trust model for repositories. Options are: collaborator, committer, collaboratorcommitter
|
||||
;DEFAULT_TRUST_MODEL = collaborator
|
||||
;;
|
||||
;; Determines when gitea should sign the initial commit when creating a repository
|
||||
;; Determines when Forgejo should sign the initial commit when creating a repository
|
||||
;; Either:
|
||||
;; - never
|
||||
;; - pubkey: only sign if the user has a pubkey
|
||||
|
@ -1297,7 +1301,7 @@ LEVEL = Info
|
|||
;; Whether the email of the user should be shown in the Explore Users page
|
||||
;SHOW_USER_EMAIL = true
|
||||
;;
|
||||
;; Set the default theme for the Gitea install
|
||||
;; Set the default theme for the Forgejo install
|
||||
;DEFAULT_THEME = forgejo-auto
|
||||
;;
|
||||
;; All available themes. Allow users select personalized themes regardless of the value of `DEFAULT_THEME`.
|
||||
|
@ -1334,10 +1338,6 @@ LEVEL = Info
|
|||
;; Change the sort type of the explore pages.
|
||||
;; Default is "recentupdate", but you also have "alphabetically", "reverselastlogin", "newest", "oldest".
|
||||
;EXPLORE_PAGING_DEFAULT_SORT = recentupdate
|
||||
;;
|
||||
;; The tense all timestamps should be rendered in. Possible values are `absolute` time (i.e. 1970-01-01, 11:59) and `mixed`.
|
||||
;; `mixed` means most timestamps are rendered in relative time (i.e. 2 days ago).
|
||||
;PREFERRED_TIMESTAMP_TENSE = mixed
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -1585,6 +1585,11 @@ LEVEL = Info
|
|||
;; If enabled it will be possible for users to report abusive content (new actions are added in the UI and /report_abuse route will be enabled) and a new Moderation section will be added to Admin settings where the reports can be reviewed.
|
||||
;ENABLED = false
|
||||
|
||||
;; How long to keep resolved abuse reports for.
|
||||
;; Applies to reports that have been marked as ignored or handled
|
||||
;; Can be 1 hour, 7 days etc
|
||||
;KEEP_RESOLVED_REPORTS_FOR = 0
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;[openid]
|
||||
|
@ -1701,10 +1706,10 @@ LEVEL = Info
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
;; NOTICE: this section is for Gitea 1.18 and later. If you are using Gitea 1.17 or older,
|
||||
;; NOTICE: this section is for Forgejo 1.18 and later. If you are using Forgejo 1.17 or older,
|
||||
;; please refer to
|
||||
;; https://github.com/go-gitea/gitea/blob/release/v1.17/custom/conf/app.example.ini
|
||||
;; https://github.com/go-gitea/gitea/blob/release/v1.17/docs/content/doc/advanced/config-cheat-sheet.en-us.md
|
||||
;; https://codeberg.org/forgejo/forgejo/src/commit/8769df117d6cc2f4ab00d6e1d54ef4241d063f11/custom/conf/app.example.ini
|
||||
;; https://codeberg.org/forgejo/forgejo/src/commit/8769df117d6cc2f4ab00d6e1d54ef4241d063f11/docs/content/doc/advanced/config-cheat-sheet.en-us.md
|
||||
;;
|
||||
;ENABLED = false
|
||||
;;
|
||||
|
@ -1757,7 +1762,7 @@ LEVEL = Info
|
|||
;; Sometimes it is helpful to use a different address on the envelope. Set this to use ENVELOPE_FROM as the from on the envelope. Set to `<>` to send an empty address.
|
||||
;ENVELOPE_FROM =
|
||||
;;
|
||||
;; If gitea sends mails on behave of users, it will just use the name also displayed in the WebUI. If you want e.g. `Mister X (by CodeIt) <gitea@codeit.net>`,
|
||||
;; If Forgejo sends mails on behave of users, it will just use the name also displayed in the WebUI. If you want e.g. `John Doe (by AppName) <johndoe@example.com>`,
|
||||
;; set it to `{{ .DisplayName }} (by {{ .AppName }})`. Available Variables: `.DisplayName`, `.AppName` and `.Domain`.
|
||||
;FROM_DISPLAY_NAME_FORMAT = {{ .DisplayName }}
|
||||
;;
|
||||
|
@ -1767,6 +1772,9 @@ LEVEL = Info
|
|||
;; Use PASSWD = `your password` for quoting if you use special characters in the password.
|
||||
;PASSWD =
|
||||
;;
|
||||
;; Alternative location to specify mailer password. You cannot specify both this and PASSWD, and must pick one
|
||||
;PASSWD_URI = file:/etc/forgejo/mailer_passwd
|
||||
;;
|
||||
;; Send mails only in plain text, without HTML alternative
|
||||
;SEND_AS_PLAIN_TEXT = false
|
||||
;;
|
||||
|
@ -1819,6 +1827,9 @@ LEVEL = Info
|
|||
;; Password of the receiving account
|
||||
;PASSWORD =
|
||||
;;
|
||||
;; Alternative location to specify password of the receiving account. You cannot specify both this and PASSWORD, and must pick one
|
||||
;PASSWORD_URI = file:/etc/forgejo/email_incoming_password
|
||||
;;
|
||||
;; Whether the IMAP server uses TLS.
|
||||
;USE_TLS = false
|
||||
;;
|
||||
|
@ -1912,7 +1923,7 @@ LEVEL = Info
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
;; How Gitea deals with missing repository avatars
|
||||
;; How Forgejo deals with missing repository avatars
|
||||
;; none = no avatar will be displayed; random = random avatar will be displayed; image = default image will be used
|
||||
;REPOSITORY_AVATAR_FALLBACK = none
|
||||
;REPOSITORY_AVATAR_FALLBACK_IMAGE = /img/repo_default.png
|
||||
|
@ -2030,7 +2041,7 @@ LEVEL = Info
|
|||
;;
|
||||
;; Setting this to true will enable all cron tasks periodically with default settings.
|
||||
;ENABLED = false
|
||||
;; Setting this to true will run all enabled cron tasks when Gitea starts.
|
||||
;; Setting this to true will run all enabled cron tasks when Forgejo starts.
|
||||
;RUN_AT_START = false
|
||||
;;
|
||||
;; Note: ``SCHEDULE`` accept formats
|
||||
|
@ -2070,7 +2081,7 @@ LEVEL = Info
|
|||
;SCHEDULE = @every 10m
|
||||
;; Enable running Update mirrors task periodically.
|
||||
;ENABLED = true
|
||||
;; Run Update mirrors task when Gitea starts.
|
||||
;; Run Update mirrors task when Forgejo starts.
|
||||
;RUN_AT_START = false
|
||||
;; Notice if not success
|
||||
;NOTICE_ON_SUCCESS = false
|
||||
|
@ -2091,7 +2102,7 @@ LEVEL = Info
|
|||
;SCHEDULE = @midnight
|
||||
;; Enable running Repository health check task periodically.
|
||||
;ENABLED = true
|
||||
;; Run Repository health check task when Gitea starts.
|
||||
;; Run Repository health check task when Forgejo starts.
|
||||
;RUN_AT_START = false
|
||||
;; Notice if not success
|
||||
;NOTICE_ON_SUCCESS = false
|
||||
|
@ -2109,7 +2120,7 @@ LEVEL = Info
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Enable running check repository statistics task periodically.
|
||||
;ENABLED = true
|
||||
;; Run check repository statistics task when Gitea starts.
|
||||
;; Run check repository statistics task when Forgejo starts.
|
||||
;RUN_AT_START = true
|
||||
;; Notice if not success
|
||||
;NOTICE_ON_SUCCESS = false
|
||||
|
@ -2264,7 +2275,7 @@ LEVEL = Info
|
|||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Update the '.ssh/authorized_keys' file with Gitea SSH keys
|
||||
;; Update the '.ssh/authorized_keys' file with Forgejo SSH keys
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;[cron.resync_all_sshkeys]
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -2337,7 +2348,7 @@ LEVEL = Info
|
|||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Check for new Gitea versions
|
||||
;; Check for new Forgejo versions
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;[cron.update_checker]
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -2421,6 +2432,7 @@ LEVEL = Info
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; The first locale will be used as the default if user browser's language doesn't match any locale in the list.
|
||||
;; en-US/English is the base language used for translations and must be in the list, even if not in first position.
|
||||
;LANGS = en-US,zh-CN,zh-HK,zh-TW,da,de-DE,nds,fr-FR,nl-NL,lv-LV,ru-RU,uk-UA,ja-JP,es-ES,pt-BR,pt-PT,pl-PL,bg,it-IT,fi-FI,fil,eo,tr-TR,cs-CZ,sl,sv-SE,ko-KR,el-GR,fa-IR,hu-HU,id-ID
|
||||
;NAMES = English,简体中文,繁體中文(香港),繁體中文(台灣),Dansk,Deutsch,Plattdüütsch,Français,Nederlands,Latviešu,Русский,Українська,日本語,Español,Português do Brasil,Português de Portugal,Polski,Български,Italiano,Suomi,Filipino,Esperanto,Türkçe,Čeština,Slovenščina,Svenska,한국어,Ελληνικά,فارسی,Magyar nyelv,Bahasa Indonesia
|
||||
|
||||
|
@ -2437,7 +2449,7 @@ LEVEL = Info
|
|||
;[other]
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Show version information about Gitea and Go in the footer
|
||||
;; Show version information about Forgejo and Go in the footer
|
||||
;SHOW_FOOTER_VERSION = true
|
||||
;; Show template execution time in the footer
|
||||
;SHOW_FOOTER_TEMPLATE_LOAD_TIME = true
|
||||
|
@ -2684,7 +2696,7 @@ LEVEL = Info
|
|||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; settings for Gitea's LFS client (eg: mirroring an upstream lfs endpoint)
|
||||
;; settings for Forgejo's LFS client (eg: mirroring an upstream lfs endpoint)
|
||||
;;
|
||||
;[lfs_client]
|
||||
;; Limit the number of pointers in each batch request to this number
|
||||
|
@ -2770,6 +2782,10 @@ LEVEL = Info
|
|||
;SKIP_WORKFLOW_STRINGS = [skip ci],[ci skip],[no ci],[skip actions],[actions skip]
|
||||
;; Limit on inputs for manual / workflow_dispatch triggers, default is 10
|
||||
;LIMIT_DISPATCH_INPUTS = 10
|
||||
;; Support queuing workflow jobs, by setting `concurrency.group` & `concurrency.cancel-in-progress: false`, can increase
|
||||
;; server and database workload due to more complex database queries and more frequent server task querying; this
|
||||
;; feature can be disabled to reduce performance impact
|
||||
;CONCURRENCY_GROUP_QUEUE_ENABLED = true
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
|
@ -58,7 +58,14 @@ export default tseslint.config(
|
|||
sourceType: 'module',
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-vars': 'off', // TODO: enable this rule again
|
||||
'@typescript-eslint/no-unused-vars': [2, {
|
||||
args: 'all',
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
caughtErrorsIgnorePattern: '^_',
|
||||
destructuredArrayIgnorePattern: '^_',
|
||||
ignoreRestSiblings: false,
|
||||
}],
|
||||
|
||||
'@eslint-community/eslint-comments/disable-enable-pair': [2],
|
||||
'@eslint-community/eslint-comments/no-aggregating-enable': [2],
|
||||
|
@ -153,7 +160,7 @@ export default tseslint.config(
|
|||
|
||||
'@stylistic/quotes': [2, 'single', {
|
||||
avoidEscape: true,
|
||||
allowTemplateLiterals: true,
|
||||
allowTemplateLiterals: 'always',
|
||||
}],
|
||||
|
||||
'@stylistic/rest-spread-spacing': [2, 'never'],
|
||||
|
@ -539,14 +546,7 @@ export default tseslint.config(
|
|||
'no-unused-labels': [2],
|
||||
'no-unused-private-class-members': [2],
|
||||
|
||||
'no-unused-vars': [2, {
|
||||
args: 'all',
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
caughtErrorsIgnorePattern: '^_',
|
||||
destructuredArrayIgnorePattern: '^_',
|
||||
ignoreRestSiblings: false,
|
||||
}],
|
||||
'no-unused-vars': [0],
|
||||
|
||||
'no-use-before-define': [2, {
|
||||
functions: false,
|
||||
|
@ -703,7 +703,6 @@ export default tseslint.config(
|
|||
'sonarjs/no-inverted-boolean-check': [2],
|
||||
'sonarjs/no-nested-switch': [0],
|
||||
'sonarjs/no-nested-template-literals': [0],
|
||||
'sonarjs/no-one-iteration-loop': [2],
|
||||
'sonarjs/no-redundant-boolean': [2],
|
||||
'sonarjs/no-redundant-jump': [2],
|
||||
'sonarjs/no-same-line-conditional': [2],
|
||||
|
@ -799,6 +798,7 @@ export default tseslint.config(
|
|||
'unicorn/prefer-array-some': [2],
|
||||
'unicorn/prefer-at': [0],
|
||||
'unicorn/prefer-blob-reading-methods': [2],
|
||||
'unicorn/prefer-classlist-toggle': [2],
|
||||
'unicorn/prefer-code-point': [0],
|
||||
'unicorn/prefer-date-now': [2],
|
||||
'unicorn/prefer-default-parameters': [0],
|
||||
|
@ -1082,6 +1082,7 @@ export default tseslint.config(
|
|||
'@vitest/no-standalone-expect': [0],
|
||||
'@vitest/no-test-prefixes': [0],
|
||||
'@vitest/no-test-return-statement': [0],
|
||||
'@vitest/prefer-called-exactly-once-with': [2],
|
||||
'@vitest/prefer-called-with': [0],
|
||||
'@vitest/prefer-comparison-matcher': [0],
|
||||
'@vitest/prefer-each': [0],
|
||||
|
@ -1089,6 +1090,7 @@ export default tseslint.config(
|
|||
'@vitest/prefer-expect-resolves': [0],
|
||||
'@vitest/prefer-hooks-in-order': [0],
|
||||
'@vitest/prefer-hooks-on-top': [2],
|
||||
'@vitest/prefer-import-in-mock': [0],
|
||||
'@vitest/prefer-lowercase-title': [0],
|
||||
'@vitest/prefer-mock-promise-shorthand': [0],
|
||||
'@vitest/prefer-snapshot-hint': [0],
|
||||
|
|
165
go.mod
165
go.mod
|
@ -1,38 +1,42 @@
|
|||
module forgejo.org
|
||||
|
||||
go 1.24
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.24.4
|
||||
toolchain go1.25.3
|
||||
|
||||
require (
|
||||
code.forgejo.org/f3/gof3/v3 v3.11.0
|
||||
code.forgejo.org/f3/gof3/v3 v3.11.1
|
||||
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251
|
||||
code.forgejo.org/forgejo/go-rpmutils v1.0.0
|
||||
code.forgejo.org/forgejo/levelqueue v1.0.0
|
||||
code.forgejo.org/forgejo/reply v1.0.2
|
||||
code.forgejo.org/forgejo/runner/v11 v11.1.2
|
||||
code.forgejo.org/go-chi/binding v1.0.1
|
||||
code.forgejo.org/go-chi/cache v1.0.1
|
||||
code.forgejo.org/go-chi/captcha v1.0.2
|
||||
code.forgejo.org/go-chi/session v1.0.2
|
||||
code.gitea.io/actions-proto-go v0.4.0
|
||||
code.gitea.io/sdk/gitea v0.21.0
|
||||
code.superseriousbusiness.org/exif-terminator v0.11.0
|
||||
code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
|
||||
connectrpc.com/connect v1.18.1
|
||||
connectrpc.com/connect v1.19.1
|
||||
github.com/42wim/httpsig v1.2.3
|
||||
github.com/42wim/sshsig v0.0.0-20250502153856-5100632e8920
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
|
||||
github.com/ProtonMail/go-crypto v1.3.0
|
||||
github.com/PuerkitoBio/goquery v1.10.3
|
||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.2
|
||||
github.com/alecthomas/chroma/v2 v2.18.0
|
||||
github.com/alecthomas/chroma/v2 v2.20.0
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
||||
github.com/blevesearch/bleve/v2 v2.5.2
|
||||
github.com/buildkite/terminal-to-html/v3 v3.16.8
|
||||
github.com/caddyserver/certmagic v0.23.0
|
||||
github.com/caddyserver/certmagic v0.24.0
|
||||
github.com/chi-middleware/proxy v1.1.1
|
||||
github.com/djherbis/buffer v1.2.0
|
||||
github.com/djherbis/nio/v3 v3.0.1
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707
|
||||
github.com/dsoprea/go-exif/v3 v3.0.1
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3
|
||||
github.com/emersion/go-imap v1.2.1
|
||||
|
@ -40,46 +44,44 @@ require (
|
|||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/gliderlabs/ssh v0.3.8
|
||||
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9
|
||||
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
|
||||
github.com/go-chi/chi/v5 v5.2.2
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-ap/jsonld v0.0.0-20250905102310-8480b0fe24d9
|
||||
github.com/go-chi/chi/v5 v5.2.3
|
||||
github.com/go-chi/cors v1.2.2
|
||||
github.com/go-co-op/gocron v1.37.0
|
||||
github.com/go-enry/go-enry/v2 v2.9.2
|
||||
github.com/go-git/go-git/v5 v5.13.2
|
||||
github.com/go-ldap/ldap/v3 v3.4.6
|
||||
github.com/go-openapi/spec v0.21.0
|
||||
github.com/go-openapi/spec v0.22.0
|
||||
github.com/go-sql-driver/mysql v1.9.3
|
||||
github.com/go-webauthn/webauthn v0.13.0
|
||||
github.com/go-webauthn/webauthn v0.14.0
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
|
||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||
github.com/google/go-github/v64 v64.0.0
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e
|
||||
github.com/google/pprof v0.0.0-20250923004556-9e5a51aed1e8
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/feeds v1.2.0
|
||||
github.com/gorilla/sessions v1.4.0
|
||||
github.com/hashicorp/go-version v1.7.0
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||
github.com/huandu/xstrings v1.5.0
|
||||
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056
|
||||
github.com/jhillyerd/enmime/v2 v2.1.0
|
||||
github.com/inbucket/html2text v0.9.0
|
||||
github.com/jhillyerd/enmime/v2 v2.2.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/klauspost/compress v1.18.0
|
||||
github.com/klauspost/cpuid/v2 v2.2.10
|
||||
github.com/klauspost/cpuid/v2 v2.2.11
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/markbates/goth v1.80.0
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/mattn/go-sqlite3 v1.14.28
|
||||
github.com/meilisearch/meilisearch-go v0.31.0
|
||||
github.com/mholt/archiver/v3 v3.5.1
|
||||
github.com/mattn/go-sqlite3 v1.14.32
|
||||
github.com/meilisearch/meilisearch-go v0.34.0
|
||||
github.com/mholt/archives v0.1.5
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
github.com/minio/minio-go/v7 v7.0.94
|
||||
github.com/minio/minio-go/v7 v7.0.95
|
||||
github.com/msteinert/pam/v2 v2.1.0
|
||||
github.com/nektos/act v0.2.52
|
||||
github.com/niklasfasching/go-org v1.8.0
|
||||
github.com/niklasfasching/go-org v1.9.1
|
||||
github.com/olivere/elastic/v7 v7.0.32
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.1.1
|
||||
|
@ -89,27 +91,27 @@ require (
|
|||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
|
||||
github.com/sergi/go-diff v1.4.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/syndtr/goleveldb v1.0.0
|
||||
github.com/ulikunitz/xz v0.5.12
|
||||
github.com/urfave/cli/v3 v3.3.3
|
||||
github.com/ulikunitz/xz v0.5.15
|
||||
github.com/urfave/cli/v3 v3.4.1
|
||||
github.com/valyala/fastjson v1.6.4
|
||||
github.com/yohcop/openid-go v1.0.1
|
||||
github.com/yuin/goldmark v1.7.12
|
||||
github.com/yuin/goldmark v1.7.13
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||
gitlab.com/gitlab-org/api/client-go v0.130.1
|
||||
go.uber.org/mock v0.5.2
|
||||
golang.org/x/crypto v0.39.0
|
||||
golang.org/x/image v0.27.0
|
||||
golang.org/x/net v0.41.0
|
||||
golang.org/x/oauth2 v0.30.0
|
||||
golang.org/x/sync v0.15.0
|
||||
golang.org/x/sys v0.33.0
|
||||
golang.org/x/text v0.26.0
|
||||
google.golang.org/protobuf v1.36.4
|
||||
gitlab.com/gitlab-org/api/client-go v0.143.2
|
||||
go.uber.org/mock v0.6.0
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
golang.org/x/crypto v0.43.0
|
||||
golang.org/x/image v0.32.0
|
||||
golang.org/x/net v0.46.0
|
||||
golang.org/x/oauth2 v0.32.0
|
||||
golang.org/x/sync v0.17.0
|
||||
golang.org/x/sys v0.37.0
|
||||
golang.org/x/text v0.30.0
|
||||
google.golang.org/protobuf v1.36.10
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
mvdan.cc/xurls/v2 v2.5.0
|
||||
xorm.io/builder v0.3.13
|
||||
xorm.io/xorm v1.3.9
|
||||
|
@ -117,12 +119,14 @@ require (
|
|||
|
||||
require (
|
||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
github.com/STARRY-S/zip v0.2.3 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
|
@ -145,35 +149,53 @@ require (
|
|||
github.com/blevesearch/zapx/v14 v14.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.2.4 // indirect
|
||||
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
|
||||
github.com/bodgit/plumbing v1.3.0 // indirect
|
||||
github.com/bodgit/sevenzip v1.6.1 // indirect
|
||||
github.com/bodgit/windows v1.0.1 // indirect
|
||||
github.com/boombuler/barcode v1.0.1 // indirect
|
||||
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
|
||||
github.com/caddyserver/zerossl v0.1.3 // indirect
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.3.6 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb // indirect
|
||||
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
|
||||
github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c // indirect
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||
github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
|
||||
github.com/go-enry/go-oniguruma v1.2.1 // indirect
|
||||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
github.com/go-fed/httpsig v1.1.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
||||
github.com/go-git/go-git/v5 v5.16.2 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/go-webauthn/x v0.1.21 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.22.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.2 // indirect
|
||||
github.com/go-openapi/swag/conv v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/loading v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.25.1 // indirect
|
||||
github.com/go-webauthn/x v0.1.25 // indirect
|
||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
|
@ -184,68 +206,77 @@ require (
|
|||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/libdns/libdns v1.0.0-beta.1 // indirect
|
||||
github.com/libdns/libdns v1.0.0 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/markbates/going v1.0.3 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.17 // indirect
|
||||
github.com/mattn/go-shellwords v1.0.12 // indirect
|
||||
github.com/mholt/acmez/v3 v3.1.2 // indirect
|
||||
github.com/miekg/dns v1.1.63 // indirect
|
||||
github.com/minio/crc64nvme v1.0.1 // indirect
|
||||
github.com/mikelolasagasti/xz v1.0.1 // indirect
|
||||
github.com/minio/crc64nvme v1.0.2 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/minio/minlz v1.0.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
|
||||
github.com/mschoch/smat v0.2.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nwaples/rardecode v1.1.3 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/nwaples/rardecode/v2 v2.2.0 // indirect
|
||||
github.com/olekukonko/errors v1.1.0 // indirect
|
||||
github.com/olekukonko/ll v0.0.9 // indirect
|
||||
github.com/olekukonko/tablewriter v1.0.7 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.62.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/rhysd/actionlint v1.6.27 // indirect
|
||||
github.com/rhysd/actionlint v1.7.8 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/skeema/knownhosts v1.3.0 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/sorairolake/lzip-go v0.3.8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||
github.com/tinylib/msgp v1.3.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/zeebo/assert v1.3.0 // indirect
|
||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||
go.etcd.io/bbolt v1.4.0 // indirect
|
||||
go.etcd.io/bbolt v1.4.3 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
golang.org/x/mod v0.25.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.34.0 // indirect
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||
golang.org/x/mod v0.29.0 // indirect
|
||||
golang.org/x/time v0.13.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
|
||||
|
||||
replace github.com/nektos/act => code.forgejo.org/forgejo/act v1.28.0
|
||||
|
||||
replace github.com/mholt/archiver/v3 => code.forgejo.org/forgejo/archiver/v3 v3.5.1
|
||||
|
||||
replace github.com/gliderlabs/ssh => code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616
|
||||
|
||||
replace git.sr.ht/~mariusor/go-xsd-duration => code.forgejo.org/forgejo/go-xsd-duration v0.0.0-20220703122237-02e73435a078
|
||||
|
||||
replace xorm.io/xorm v1.3.9 => code.forgejo.org/xorm/xorm v1.3.9-forgejo.3
|
||||
|
|
595
go.sum
595
go.sum
|
@ -1,13 +1,25 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
|
||||
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
||||
code.forgejo.org/f3/gof3/v3 v3.11.0 h1:f/xToKwqTgxG6PYxvewywjDQyCcyHEEJ6sZqUitFsAE=
|
||||
code.forgejo.org/f3/gof3/v3 v3.11.0/go.mod h1:4FaRUNSQGBiD1M0DuB0yNv+Z2wMtlOeckgygHSSq4KQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
code.forgejo.org/f3/gof3/v3 v3.11.1 h1:c0vE8XvqpbXuSv8gzttn96k5T2FQi0u9bYnux46qSAs=
|
||||
code.forgejo.org/f3/gof3/v3 v3.11.1/go.mod h1:1p2UKrqZiwxKneQF2DKrMnc403YIgR/lfcfvadZtmDs=
|
||||
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251 h1:HTZl3CBk3ABNYtFI6TPLvJgGKFIhKT5CBk0sbOtkDKU=
|
||||
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:PphB88CPbx601QrWPMZATeorACeVmQlyv3u+uUMbSaM=
|
||||
code.forgejo.org/forgejo/act v1.28.0 h1:96njNC7C1YNyjWq5OWvLZMF/nw0PMthzIA8Nwbnn7jo=
|
||||
code.forgejo.org/forgejo/act v1.28.0/go.mod h1:dFuiwAmD5vyrzecysHB2kL/GM3wRpoVPl+WdbCTC8Bs=
|
||||
code.forgejo.org/forgejo/archiver/v3 v3.5.1 h1:UmmbA7D5550uf71SQjarmrn6yKwOGxtEjb3jaYYtmSE=
|
||||
code.forgejo.org/forgejo/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
||||
code.forgejo.org/forgejo/go-rpmutils v1.0.0 h1:RZGGeKt70p/WaIEL97pyT6uiiEIoN8/aLmS5Z6WmX0M=
|
||||
code.forgejo.org/forgejo/go-rpmutils v1.0.0/go.mod h1:cg+VbgLXfrDPza9T+kBsMb3TVmmzPN4XseT6gDGLSUk=
|
||||
code.forgejo.org/forgejo/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:RArF5AsF9LH4nEoJxqRxcP5r8hhRfWcId84G82YbqzA=
|
||||
|
@ -16,6 +28,8 @@ code.forgejo.org/forgejo/levelqueue v1.0.0 h1:9krYpU6BM+j/1Ntj6m+VCAIu0UNnne1/Uf
|
|||
code.forgejo.org/forgejo/levelqueue v1.0.0/go.mod h1:fmG6zhVuqim2rxSFOoasgXO8V2W/k9U31VVYqLIRLhQ=
|
||||
code.forgejo.org/forgejo/reply v1.0.2 h1:dMhQCHV6/O3L5CLWNTol+dNzDAuyCK88z4J/lCdgFuQ=
|
||||
code.forgejo.org/forgejo/reply v1.0.2/go.mod h1:RyZUfzQLc+fuLIGjTSQWDAJWPiL4WtKXB/FifT5fM7U=
|
||||
code.forgejo.org/forgejo/runner/v11 v11.1.2 h1:jM5YsNmScH11VJEwmvsTUiqGjAqtiUzBhQ65BIo8ZOs=
|
||||
code.forgejo.org/forgejo/runner/v11 v11.1.2/go.mod h1:9f0D2EG7uabL+cv71SWHKrGgo2vmLpvko0QLmtn3RDE=
|
||||
code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616 h1:kEZL84+02jY9RxXM4zHBWZ3Fml0B09cmP1LGkDsCfIA=
|
||||
code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
||||
code.forgejo.org/go-chi/binding v1.0.1 h1:coKNI+X1NzRN7X85LlrpvBRqk0TXpJ+ja28vusQWEuY=
|
||||
|
@ -26,16 +40,25 @@ code.forgejo.org/go-chi/captcha v1.0.2 h1:vyHDPXkpjDv8bLO9NqtWzZayzstD/WpJ5xwEkA
|
|||
code.forgejo.org/go-chi/captcha v1.0.2/go.mod h1:lxiPLcJ76UCZHoH31/Wbum4GUi2NgjfFZLrJkKv1lLE=
|
||||
code.forgejo.org/go-chi/session v1.0.2 h1:pG+AXre9L9VXJmTaADXkmeEPuRalhmBXyv6tG2Rvjcc=
|
||||
code.forgejo.org/go-chi/session v1.0.2/go.mod h1:HnEGyBny7WPzCiVLP2vzL5ssma+3gCSl/vLpuVNYrqc=
|
||||
code.forgejo.org/xorm/xorm v1.3.9-forgejo.3 h1:7KW1+3cr2YIZCS85TM+imdsUe8OdNsfV3F8iP2t36rM=
|
||||
code.forgejo.org/xorm/xorm v1.3.9-forgejo.3/go.mod h1:5ouTxqMcalQUvlBpQynRpzu/44GwaMpkA1nU+encsDE=
|
||||
code.gitea.io/actions-proto-go v0.4.0 h1:OsPBPhodXuQnsspG1sQ4eRE1PeoZyofd7+i73zCwnsU=
|
||||
code.gitea.io/actions-proto-go v0.4.0/go.mod h1:mn7Wkqz6JbnTOHQpot3yDeHx+O5C9EGhMEE+htvHBas=
|
||||
code.gitea.io/sdk/gitea v0.21.0 h1:69n6oz6kEVHRo1+APQQyizkhrZrLsTLXey9142pfkD4=
|
||||
code.gitea.io/sdk/gitea v0.21.0/go.mod h1:tnBjVhuKJCn8ibdyyhvUyxrR1Ca2KHEoTWoukNhXQPA=
|
||||
code.superseriousbusiness.org/exif-terminator v0.11.0 h1:Hof0MCcsa+1fS17gf86fTTZ8AQnMY9h9kzcc+2C6mVg=
|
||||
code.superseriousbusiness.org/exif-terminator v0.11.0/go.mod h1:9sutT1axa/kSdlPLlRFjCNKmyo/KNx8eX3XZvWBlAEY=
|
||||
code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0 h1:r9uq8StaSHYKJ8DklR9Xy+E9c40G1Z8yj5TRGi8L6+4=
|
||||
code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0/go.mod h1:IK1OlR6APjVB3E9tuYGvf0qXMrwP+TrzcHS5rf4wffQ=
|
||||
code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0 h1:I512jiIeXDC4//2BeSPrRM2ZS4wpBKUaPeTPxakMNGA=
|
||||
code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0/go.mod h1:SNHomXNW88o1pFfLHpD4KsCZLfcr4z5dm+xcX5SV10A=
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 h1:TXbikPqa7YRtfU9vS6QJBg77pUvbEb6StRdZO8t1bEY=
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570/go.mod h1:IIAjsijsd8q1isWX8MACefDEgTQslQ4stk2AeeTt3kM=
|
||||
connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw=
|
||||
connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8=
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14=
|
||||
connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w=
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
||||
|
@ -48,6 +71,8 @@ github.com/6543/go-version v1.3.1 h1:HvOp+Telns7HWJ2Xo/05YXQSB2bE0WmVgbHqwMPZT4U
|
|||
github.com/6543/go-version v1.3.1/go.mod h1:oqFAHCwtLVUTLdhQmVZWYvaHXTdsbB4SY85at64SQEo=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
|
@ -57,21 +82,22 @@ github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiU
|
|||
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.4.5 h1:uGrrMreGjvAtTBobc0g5IrW1D5ldxDQYe2JW2gggRdg=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.4.5/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0=
|
||||
github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4=
|
||||
github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk=
|
||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.2 h1:cSXom2MoKJ9KPPw29RoZtHvUETY4F4n/kXl8m9btnQ0=
|
||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.2/go.mod h1:JitQWJ8JuV4Y87l8VsHiiwhb3cgdyn68mX40s7NT6PA=
|
||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
||||
github.com/alecthomas/chroma/v2 v2.18.0 h1:6h53Q4hW83SuF+jcsp7CVhLsMozzvQvO8HBbKQW+gn4=
|
||||
github.com/alecthomas/chroma/v2 v2.18.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
|
||||
github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
|
||||
github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=
|
||||
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=
|
||||
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA=
|
||||
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
|
@ -123,6 +149,14 @@ github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFx
|
|||
github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw=
|
||||
github.com/blevesearch/zapx/v16 v16.2.4 h1:tGgfvleXTAkwsD5mEzgM3zCS/7pgocTCnO1oyAUjlww=
|
||||
github.com/blevesearch/zapx/v16 v16.2.4/go.mod h1:Rti/REtuuMmzwsI8/C/qIzRaEoSK/wiFYw5e5ctUKKs=
|
||||
github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE=
|
||||
github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
|
||||
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
|
||||
github.com/bodgit/sevenzip v1.6.1 h1:kikg2pUMYC9ljU7W9SaqHXhym5HyKm8/M/jd31fYan4=
|
||||
github.com/bodgit/sevenzip v1.6.1/go.mod h1:GVoYQbEVbOGT8n2pfqCIMRUaRjQ8F9oSqoBEqZh5fQ8=
|
||||
github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=
|
||||
github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
|
||||
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
|
@ -134,10 +168,11 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
|||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/buildkite/terminal-to-html/v3 v3.16.8 h1:QN/daUob6cmK8GcdKnwn9+YTlPr1vNj+oeAIiJK6fPc=
|
||||
github.com/buildkite/terminal-to-html/v3 v3.16.8/go.mod h1:+k1KVKROZocrTLsEQ9PEf9A+8+X8uaVV5iO1ZIOwKYM=
|
||||
github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU=
|
||||
github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4=
|
||||
github.com/caddyserver/certmagic v0.24.0 h1:EfXTWpxHAUKgDfOj6MHImJN8Jm4AMFfMT6ITuKhrDF0=
|
||||
github.com/caddyserver/certmagic v0.24.0/go.mod h1:xPT7dC1DuHHnS2yuEQCEyks+b89sUkMENh8dJF+InLE=
|
||||
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
|
||||
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI=
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
|
@ -147,14 +182,18 @@ github.com/chi-middleware/proxy v1.1.1/go.mod h1:jQwMEJct2tz9VmtCELxvnXoMfa+SOdi
|
|||
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||
github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
|
||||
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM=
|
||||
github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
|
@ -173,16 +212,37 @@ github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55k
|
|||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||
github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20200717053412-08f1b6708903/go.mod h1:0nsO1ce0mh5czxGeLo4+OCZ/C6Eo6ZlMWsz7rH/Gxv8=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20210428042052-dca55bf8ca15/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20210625224831-a6301f85c82b/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20221003160559-cf5cd88aa559/go.mod h1:rW6DMEv25U9zCtE5ukC7ttBRllXj7g7TAHl7tQrT5No=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20221003171958-de6cb6e380a8/go.mod h1:akyZEJZ/k5bmbC9gA612ZLQkcED8enS9vuTiuAkENr0=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.1 h1:/IE4iW7gvY7BablV1XY0unqhMv26EYpOquVMwoBo/wc=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.1/go.mod h1:10HkA1Wz3h398cDP66L+Is9kKDmlqlIJGPv8pk4EWvc=
|
||||
github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb h1:gwjJjUr6FY7zAWVEueFPrcRHhd9+IK81TcItbqw2du4=
|
||||
github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb/go.mod h1:kYIdx9N9NaOyD7U6D+YtExN7QhRm+5kq7//yOsRXQtM=
|
||||
github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA=
|
||||
github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8=
|
||||
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd h1:l+vLbuxptsC6VQyQsfD7NnEC8BZuFpz45PgY+pH8YTg=
|
||||
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8=
|
||||
github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c h1:7j5aWACOzROpr+dvMtu8GnI97g9ShLWD72XIELMgn+c=
|
||||
github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c/go.mod h1:pqKB+ijp27cEcrHxhXVgUUMlSDRuGJJp1E+20Lj5H0E=
|
||||
github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8=
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e/go.mod h1:uAzdkPTub5Y9yQwXe8W4m2XuP0tK4a9Q/dantD0+uaU=
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003142440-7a1927d49d9d/go.mod h1:LVjRU0RNUuMDqkPTxcALio0LWPFPXxxFCvVGVAwEpFc=
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003160719-7bc88537c05e/go.mod h1:VZ7cB0pTjm1ADBWhJUOHESu4ZYy9JN+ZPqjfiW09EPU=
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje0z+3UQ5YjYiSRRzVdtamFpvBQXKwMglWqw=
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3 h1:XVUp6qW3BIkmM3/1EkrHpa6bL56APOynfXcZEmIgOhs=
|
||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3/go.mod h1:ThHVc+hqbUsmE1wmK/MASpQEhCleWu1JDJDNhUOMy0c=
|
||||
github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM=
|
||||
github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ=
|
||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||
github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA=
|
||||
github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY=
|
||||
github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
|
||||
|
@ -192,8 +252,10 @@ github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43/go.mod h1:iL2twTe
|
|||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=
|
||||
github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM=
|
||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||
|
@ -202,27 +264,33 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
|||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
|
||||
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9 h1:j2TrkUG/NATGi/EQS+MvEoF79CxiRUmT16ErFroNcKI=
|
||||
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9/go.mod h1:cJ9Ye0ZNSMN7RzZDBRY3E+8M3Bpf/R1JX22Ir9yX6WI=
|
||||
github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 h1:I2nuhyVI/48VXoRCCZR2hYBgnSXa+EuDJf/VyX06TC0=
|
||||
github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7/go.mod h1:5x8a6P/dhmMGFxWLcyYlyOuJ2lRNaHGhRv+yu8BaTSI=
|
||||
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 h1:GMKIYXyXPGIp+hYiWOhfqK4A023HdgisDT4YGgf99mw=
|
||||
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73/go.mod h1:jyveZeGw5LaADntW+UEsMjl3IlIwk+DxlYNsbofQkGA=
|
||||
github.com/go-ap/jsonld v0.0.0-20250905102310-8480b0fe24d9 h1:gaBrU/E+usPHIafDIC2EwvZbehvgAEuu78Jk0zjxw5w=
|
||||
github.com/go-ap/jsonld v0.0.0-20250905102310-8480b0fe24d9/go.mod h1:4h93IBxgfnE/DEleMLgJ/XCeu/RtQ+MUh3ucANseeXA=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
|
||||
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
|
||||
github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY=
|
||||
github.com/go-enry/go-enry/v2 v2.9.2 h1:giOQAtCgBX08kosrX818DCQJTCNtKwoPBGu0qb6nKTY=
|
||||
github.com/go-enry/go-enry/v2 v2.9.2/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8=
|
||||
github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
|
||||
github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||
github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
|
||||
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
|
@ -231,29 +299,47 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN
|
|||
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0=
|
||||
github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A=
|
||||
github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
|
||||
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A=
|
||||
github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc=
|
||||
github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
|
||||
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
|
||||
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
|
||||
github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk=
|
||||
github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM=
|
||||
github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU=
|
||||
github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ=
|
||||
github.com/go-openapi/spec v0.22.0 h1:xT/EsX4frL3U09QviRIZXvkh80yibxQmtoEvyqug0Tw=
|
||||
github.com/go-openapi/spec v0.22.0/go.mod h1:K0FhKxkez8YNS94XzF8YKEMULbFrRw4m15i2YUht4L0=
|
||||
github.com/go-openapi/swag/conv v0.25.1 h1:+9o8YUg6QuqqBM5X6rYL/p1dpWeZRhoIt9x7CCP+he0=
|
||||
github.com/go-openapi/swag/conv v0.25.1/go.mod h1:Z1mFEGPfyIKPu0806khI3zF+/EUXde+fdeksUl2NiDs=
|
||||
github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU=
|
||||
github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo=
|
||||
github.com/go-openapi/swag/jsonutils v0.25.1 h1:AihLHaD0brrkJoMqEZOBNzTLnk81Kg9cWr+SPtxtgl8=
|
||||
github.com/go-openapi/swag/jsonutils v0.25.1/go.mod h1:JpEkAjxQXpiaHmRO04N1zE4qbUEg3b7Udll7AMGTNOo=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1 h1:DSQGcdB6G0N9c/KhtpYc71PzzGEIc/fZ1no35x4/XBY=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1/go.mod h1:kjmweouyPwRUEYMSrbAidoLMGeJ5p6zdHi9BgZiqmsg=
|
||||
github.com/go-openapi/swag/loading v0.25.1 h1:6OruqzjWoJyanZOim58iG2vj934TysYVptyaoXS24kw=
|
||||
github.com/go-openapi/swag/loading v0.25.1/go.mod h1:xoIe2EG32NOYYbqxvXgPzne989bWvSNoWoyQVWEZicc=
|
||||
github.com/go-openapi/swag/stringutils v0.25.1 h1:Xasqgjvk30eUe8VKdmyzKtjkVjeiXx1Iz0zDfMNpPbw=
|
||||
github.com/go-openapi/swag/stringutils v0.25.1/go.mod h1:JLdSAq5169HaiDUbTvArA2yQxmgn4D6h4A+4HqVvAYg=
|
||||
github.com/go-openapi/swag/typeutils v0.25.1 h1:rD/9HsEQieewNt6/k+JBwkxuAHktFtH3I3ysiFZqukA=
|
||||
github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91oSJLDPF1bmGk=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg=
|
||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
||||
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/go-webauthn/webauthn v0.13.0 h1:cJIL1/1l+22UekVhipziAaSgESJxokYkowUqAIsWs0Y=
|
||||
github.com/go-webauthn/webauthn v0.13.0/go.mod h1:Oy9o2o79dbLKRPZWWgRIOdtBGAhKnDIaBp2PFkICRHs=
|
||||
github.com/go-webauthn/x v0.1.21 h1:nFbckQxudvHEJn2uy1VEi713MeSpApoAv9eRqsb9AdQ=
|
||||
github.com/go-webauthn/x v0.1.21/go.mod h1:sEYohtg1zL4An1TXIUIQ5csdmoO+WO0R4R2pGKaHYKA=
|
||||
github.com/go-webauthn/webauthn v0.14.0 h1:ZLNPUgPcDlAeoxe+5umWG/tEeCoQIDr7gE2Zx2QnhL0=
|
||||
github.com/go-webauthn/webauthn v0.14.0/go.mod h1:QZzPFH3LJ48u5uEPAu+8/nWJImoLBWM7iAH/kSVSo6k=
|
||||
github.com/go-webauthn/x v0.1.25 h1:g/0noooIGcz/yCVqebcFgNnGIgBlJIccS+LYAa+0Z88=
|
||||
github.com/go-webauthn/x v0.1.25/go.mod h1:ieblaPY1/BVCV0oQTsA/VAo08/TWayQuJuo5Q+XxmTY=
|
||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4/FHQWkvVRmgijNXRfzkIDHh23ggEo=
|
||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
|
@ -265,16 +351,30 @@ github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7w
|
|||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 h1:UjoPNDAQ5JPCjlxoJd6K8ALZqSDDhk2ymieAZOVaDg0=
|
||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85/go.mod h1:fR6z1Ie6rtF7kl/vBYMfgD5/G5B1blui7z426/sj2DU=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
|
@ -284,11 +384,13 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
|
|||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
|
@ -306,13 +408,20 @@ github.com/google/go-tpm v0.9.5/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u
|
|||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/pprof v0.0.0-20250923004556-9e5a51aed1e8 h1:ZI8gCoCjGzPsum4L21jHdQs8shFBIQih1TM9Rd/c+EQ=
|
||||
github.com/google/pprof v0.0.0-20250923004556-9e5a51aed1e8/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
|
@ -331,8 +440,10 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n
|
|||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
|
@ -340,30 +451,34 @@ github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSo
|
|||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
|
||||
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 h1:iCHtR9CQyktQ5+f3dMVZfwD2KWJUgm7M0gdL9NGr8KA=
|
||||
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
||||
github.com/inbucket/html2text v0.9.0 h1:ULJmVcBEMAcmLE+/rN815KG1Fx6+a4HhbUxiDiN+qks=
|
||||
github.com/inbucket/html2text v0.9.0/go.mod h1:QDaumzl+/OzlSVbNohhmg+yAy5pKjUjzCKW2BMvztKE=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jhillyerd/enmime/v2 v2.1.0 h1:c8Qwi5Xq5EdtMN6byQWoZ/8I2RMTo6OJ7Xay+s1oPO0=
|
||||
github.com/jhillyerd/enmime/v2 v2.1.0/go.mod h1:EJ74dcRbBcqHSP2TBu08XRoy6y3Yx0cevwb1YkGMEmQ=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||
github.com/jhillyerd/enmime/v2 v2.2.0 h1:Pe35MB96eZK5Q0XjlvPftOgWypQpd1gcbfJKAt7rsB8=
|
||||
github.com/jhillyerd/enmime/v2 v2.2.0/go.mod h1:SOBXlCemjhiV2DvHhAKnJiWrtJGS/Ffuw4Iy7NjBTaI=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
|
||||
github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
|
@ -380,8 +495,8 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+
|
|||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/libdns/libdns v1.0.0-beta.1 h1:KIf4wLfsrEpXpZ3vmc/poM8zCATXT2klbdPe6hyOBjQ=
|
||||
github.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/libdns/libdns v1.0.0 h1:IvYaz07JNz6jUQ4h/fv2R4sVnRnm77J/aOuC9B+TQTA=
|
||||
github.com/libdns/libdns v1.0.0/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
|
@ -389,30 +504,36 @@ github.com/markbates/going v1.0.3 h1:mY45T5TvW+Xz5A6jY7lf4+NLg9D8+iuStIHyR7M8qsE
|
|||
github.com/markbates/going v1.0.3/go.mod h1:fQiT6v6yQar9UD6bd/D4Z5Afbk9J6BBVBtLiyY4gp2o=
|
||||
github.com/markbates/goth v1.80.0 h1:NnvatczZDzOs1hn9Ug+dVYf2Viwwkp/ZDX5K+GLjan8=
|
||||
github.com/markbates/goth v1.80.0/go.mod h1:4/GYHo+W6NWisrMPZnq0Yr2Q70UntNLn7KXEFhrIdAY=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
|
||||
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/meilisearch/meilisearch-go v0.31.0 h1:yZRhY1qJqdH8h6GFZALGtkDLyj8f9v5aJpsNMyrUmnY=
|
||||
github.com/meilisearch/meilisearch-go v0.31.0/go.mod h1:aNtyuwurDg/ggxQIcKqWH6G9g2ptc8GyY7PLY4zMn/g=
|
||||
github.com/mattn/go-runewidth v0.0.17 h1:78v8ZlW0bP43XfmAfPsdXcoNCelfMHsDmd/pkENfrjQ=
|
||||
github.com/mattn/go-runewidth v0.0.17/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
||||
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/meilisearch/meilisearch-go v0.34.0 h1:P+Ohdx4/PCxXaoI5wNi0LMwPkuiNrF/kGIzBrKYS4tw=
|
||||
github.com/meilisearch/meilisearch-go v0.34.0/go.mod h1:cUVJZ2zMqTvvwIMEEAdsWH+zrHsrLpAw6gm8Lt1MXK0=
|
||||
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
|
||||
github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ=
|
||||
github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
||||
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
|
||||
github.com/minio/crc64nvme v1.0.1 h1:DHQPrYPdqK7jQG/Ls5CTBZWeex/2FMS3G5XGkycuFrY=
|
||||
github.com/minio/crc64nvme v1.0.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||
github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=
|
||||
github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=
|
||||
github.com/minio/crc64nvme v1.0.2 h1:6uO1UxGAD+kwqWWp7mBFsi5gAse66C4NXO8cmcVculg=
|
||||
github.com/minio/crc64nvme v1.0.2/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.94 h1:1ZoksIKPyaSt64AVOyaQvhDOgVC3MfZsWM6mZXRUGtM=
|
||||
github.com/minio/minio-go/v7 v7.0.94/go.mod h1:71t2CqDt3ThzESgZUlU1rBN54mksGGlkLcFgguDnnAc=
|
||||
github.com/minio/minio-go/v7 v7.0.95 h1:ywOUPg+PebTMTzn9VDsoFJy32ZuARN9zhB+K3IYEvYU=
|
||||
github.com/minio/minio-go/v7 v7.0.95/go.mod h1:wOOX3uxS334vImCNRVyIDdXX9OsXDm89ToynKgqUKlo=
|
||||
github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A=
|
||||
github.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
|
@ -428,16 +549,21 @@ github.com/msteinert/pam/v2 v2.1.0 h1:er5F9TKV5nGFuTt12ubtqPHEUdeBwReP7vd3wovidG
|
|||
github.com/msteinert/pam/v2 v2.1.0/go.mod h1:KT28NNIcDFf3PcBmNI2mIGO4zZJ+9RSs/At2PB3IDVc=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/niklasfasching/go-org v1.8.0 h1:WyGLaajLLp8JbQzkmapZ1y0MOzKuKV47HkZRloi+HGY=
|
||||
github.com/niklasfasching/go-org v1.8.0/go.mod h1:e2A9zJs7cdONrEGs3gvxCcaAEpwwPNPG7csDpXckMNg=
|
||||
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||
github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc=
|
||||
github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/niklasfasching/go-org v1.9.1 h1:/3s4uTPOF06pImGa2Yvlp24yKXZoTYM+nsIlMzfpg/0=
|
||||
github.com/niklasfasching/go-org v1.9.1/go.mod h1:ZAGFFkWvUQcpazmi/8nHqwvARpr1xpb+Es67oUGX/48=
|
||||
github.com/nwaples/rardecode/v2 v2.2.0 h1:4ufPGHiNe1rYJxYfehALLjup4Ls3ck42CWwjKiOqu0A=
|
||||
github.com/nwaples/rardecode/v2 v2.2.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
|
||||
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
|
||||
github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
|
||||
github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
|
||||
github.com/olekukonko/tablewriter v1.0.7 h1:HCC2e3MM+2g72M81ZcJU11uciw6z/p82aEnm4/ySDGw=
|
||||
github.com/olekukonko/tablewriter v1.0.7/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs=
|
||||
github.com/olivere/elastic/v7 v7.0.32 h1:R7CXvbu8Eq+WlsLgxmKVKPox0oOwAE/2T9Si5BnvK6E=
|
||||
github.com/olivere/elastic/v7 v7.0.32/go.mod h1:c7PVmLe3Fxq77PIfY/bZmxY/TAamBhCzZ8xDOE09a9k=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
|
@ -455,11 +581,10 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I
|
|||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
|
||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
|
@ -472,6 +597,7 @@ github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
|
|||
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
|
||||
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
|
||||
|
@ -480,21 +606,23 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
|
|||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=
|
||||
github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rhysd/actionlint v1.6.27 h1:xxwe8YmveBcC8lydW6GoHMGmB6H/MTqUU60F2p10wjw=
|
||||
github.com/rhysd/actionlint v1.6.27/go.mod h1:m2nFUjAnOrxCMXuOMz9evYBRCLUsMnKY2IJl/N5umbk=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rhysd/actionlint v1.7.8 h1:3d+N9ourgAxVYG4z2IFxFIk/YiT6V+VnKASfXGwT60E=
|
||||
github.com/rhysd/actionlint v1.7.8/go.mod h1:3kiS6egcbXG+vQsJIhFxTz+UKaF1JprsE0SKrpCZKvU=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
||||
github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs=
|
||||
|
@ -503,13 +631,19 @@ github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepq
|
|||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
|
||||
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
|
||||
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
|
||||
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
||||
github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
|
||||
github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
|
@ -519,26 +653,23 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||
github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
|
||||
github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
|
||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
|
||||
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/urfave/cli/v3 v3.3.3 h1:byCBaVdIXuLPIDm5CYZRVG6NvT7tv1ECqdU4YzlEa3I=
|
||||
github.com/urfave/cli/v3 v3.3.3/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
|
||||
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/urfave/cli/v3 v3.4.1 h1:1M9UOCy5bLmGnuu1yn3t3CB4rG79Rtoxuv1sPhnm6qM=
|
||||
github.com/urfave/cli/v3 v3.4.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
|
||||
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
||||
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js=
|
||||
|
@ -546,8 +677,8 @@ github.com/yohcop/openid-go v1.0.1/go.mod h1:b/AvD03P0KHj4yuihb+VtLD6bYYgsy0zqBz
|
|||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.7.12 h1:YwGP/rrea2/CnCtUHgjuolG/PnMxdQtPMO5PvaE2/nY=
|
||||
github.com/yuin/goldmark v1.7.12/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
|
||||
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
|
||||
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||
|
@ -556,24 +687,36 @@ github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
|
|||
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
|
||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
gitlab.com/gitlab-org/api/client-go v0.130.1 h1:1xF5C5Zq3sFeNg3PzS2z63oqrxifne3n/OnbI7nptRc=
|
||||
gitlab.com/gitlab-org/api/client-go v0.130.1/go.mod h1:ZhSxLAWadqP6J9lMh40IAZOlOxBLPRh7yFOXR/bMJWM=
|
||||
go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
|
||||
go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
|
||||
gitlab.com/gitlab-org/api/client-go v0.143.2 h1:tfmUW8u+G/DGKOB/FDR0c06f0RVUAEe0ym8WpLoiHXI=
|
||||
gitlab.com/gitlab-org/api/client-go v0.143.2/go.mod h1:gJn5yLx9vYGXr73Yv0ueHWCVl+fL8iUOgJFxC7qV+iM=
|
||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
|
||||
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
|
||||
go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
|
@ -583,28 +726,69 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
|
|||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
|
||||
golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ=
|
||||
golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
|
@ -612,12 +796,21 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
|||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
|
||||
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -625,27 +818,39 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
|||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -653,8 +858,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
|
@ -664,9 +869,12 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
|||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
|
@ -676,31 +884,88 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
|
||||
golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
@ -721,36 +986,32 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN
|
|||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
|
||||
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
||||
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
||||
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
||||
modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0=
|
||||
modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk=
|
||||
modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.20.4 h1:J8+m2trkN+KKoE7jglyHYYYiaq5xmz2HoHJIiBlRzbE=
|
||||
modernc.org/sqlite v1.20.4/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A=
|
||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
|
||||
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A=
|
||||
modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/sqlite v1.39.1 h1:H+/wGFzuSCIEVCvXYVHX5RQglwhMOvtHSv+VtidL2r4=
|
||||
modernc.org/sqlite v1.39.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE=
|
||||
mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8=
|
||||
mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo=
|
||||
xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||
xorm.io/xorm v1.3.9 h1:TUovzS0ko+IQ1XnNLfs5dqK1cJl1H5uHpWbWqAQ04nU=
|
||||
xorm.io/xorm v1.3.9/go.mod h1:LsCCffeeYp63ssk0pKumP6l96WZcHix7ChpurcLNuMw=
|
||||
|
|
|
@ -108,6 +108,7 @@ func UpdateArtifactByID(ctx context.Context, id int64, art *ActionArtifact) erro
|
|||
|
||||
type FindArtifactsOptions struct {
|
||||
db.ListOptions
|
||||
ID int64
|
||||
RepoID int64
|
||||
RunID int64
|
||||
ArtifactName string
|
||||
|
@ -116,6 +117,9 @@ type FindArtifactsOptions struct {
|
|||
|
||||
func (opts FindArtifactsOptions) ToConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.ID > 0 {
|
||||
cond = cond.And(builder.Eq{"id": opts.ID})
|
||||
}
|
||||
if opts.RepoID > 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
}
|
||||
|
@ -132,6 +136,13 @@ func (opts FindArtifactsOptions) ToConds() builder.Cond {
|
|||
return cond
|
||||
}
|
||||
|
||||
var _ db.FindOptionsOrder = FindArtifactsOptions{}
|
||||
|
||||
// ToOrders implements db.FindOptionsOrder, to have a stable order
|
||||
func (opts FindArtifactsOptions) ToOrders() string {
|
||||
return "id"
|
||||
}
|
||||
|
||||
// ActionArtifactMeta is the meta data of an artifact
|
||||
type ActionArtifactMeta struct {
|
||||
ArtifactName string
|
||||
|
|
|
@ -15,6 +15,10 @@ func TestMain(m *testing.M) {
|
|||
"action_runner.yml",
|
||||
"repository.yml",
|
||||
"action_runner_token.yml",
|
||||
"user.yml",
|
||||
"action_run.yml",
|
||||
"action_run_job.yml",
|
||||
"action_task.yml",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -21,15 +21,27 @@ import (
|
|||
"forgejo.org/modules/util"
|
||||
webhook_module "forgejo.org/modules/webhook"
|
||||
|
||||
"github.com/nektos/act/pkg/jobparser"
|
||||
"code.forgejo.org/forgejo/runner/v11/act/jobparser"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
type ConcurrencyMode int
|
||||
|
||||
const (
|
||||
// Don't enforce concurrency control. Note that you won't find `UnlimitedConcurrency` implemented directly in the
|
||||
// code; setting it on an `ActionRun` prevents the other limiting behaviors.
|
||||
UnlimitedConcurrency ConcurrencyMode = iota
|
||||
// Queue behind other jobs with the same concurrency group
|
||||
QueueBehind
|
||||
// Cancel other jobs with the same concurrency group
|
||||
CancelInProgress
|
||||
)
|
||||
|
||||
// ActionRun represents a run of a workflow file
|
||||
type ActionRun struct {
|
||||
ID int64
|
||||
Title string
|
||||
RepoID int64 `xorm:"index unique(repo_index)"`
|
||||
RepoID int64 `xorm:"index unique(repo_index) index(concurrency)"`
|
||||
Repo *repo_model.Repository `xorm:"-"`
|
||||
OwnerID int64 `xorm:"index"`
|
||||
WorkflowID string `xorm:"index"` // the name of workflow file
|
||||
|
@ -56,6 +68,11 @@ type ActionRun struct {
|
|||
Created timeutil.TimeStamp `xorm:"created"`
|
||||
Updated timeutil.TimeStamp `xorm:"updated"`
|
||||
NotifyEmail bool
|
||||
|
||||
ConcurrencyGroup string `xorm:"'concurrency_group' index(concurrency)"`
|
||||
ConcurrencyType ConcurrencyMode
|
||||
|
||||
PreExecutionError string `xorm:"LONGTEXT"` // used to report errors that blocked execution of a workflow
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -163,6 +180,24 @@ func (run *ActionRun) GetPullRequestEventPayload() (*api.PullRequestPayload, err
|
|||
return nil, fmt.Errorf("event %s is not a pull request event", run.Event)
|
||||
}
|
||||
|
||||
func (run *ActionRun) SetConcurrencyGroup(concurrencyGroup string) {
|
||||
// Concurrency groups are case insensitive identifiers, implemented by collapsing case here. Unfortunately the
|
||||
// `ConcurrencyGroup` field can't be made a private field because xorm doesn't map those fields -- using
|
||||
// `SetConcurrencyGroup` is required for consistency but not enforced at compile-time.
|
||||
run.ConcurrencyGroup = strings.ToLower(concurrencyGroup)
|
||||
}
|
||||
|
||||
func (run *ActionRun) SetDefaultConcurrencyGroup() {
|
||||
// Before ConcurrencyGroups were supported, Forgejo would automatically cancel runs with matching git refs, workflow
|
||||
// IDs, and trigger events. For backwards compatibility we emulate that behavior:
|
||||
run.SetConcurrencyGroup(fmt.Sprintf(
|
||||
"%s_%s_%s__auto",
|
||||
run.Ref,
|
||||
run.WorkflowID,
|
||||
run.TriggerEvent,
|
||||
))
|
||||
}
|
||||
|
||||
func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error {
|
||||
_, err := db.GetEngine(ctx).ID(repo.ID).
|
||||
SetExpr("num_action_runs",
|
||||
|
@ -183,6 +218,7 @@ func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) err
|
|||
),
|
||||
),
|
||||
).
|
||||
Cols("num_action_runs", "num_closed_action_runs").
|
||||
Update(repo)
|
||||
return err
|
||||
}
|
||||
|
@ -284,16 +320,10 @@ func GetLatestRun(ctx context.Context, repoID int64) (*ActionRun, error) {
|
|||
return &run, nil
|
||||
}
|
||||
|
||||
// GetRunBefore returns the last run that completed a given timestamp (not inclusive).
|
||||
func GetRunBefore(ctx context.Context, repoID int64, timestamp timeutil.TimeStamp) (*ActionRun, error) {
|
||||
var run ActionRun
|
||||
has, err := db.GetEngine(ctx).Where("repo_id=? AND stopped IS NOT NULL AND stopped<?", repoID, timestamp).OrderBy("stopped DESC").Limit(1).Get(&run)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, fmt.Errorf("run before: %w", util.ErrNotExist)
|
||||
}
|
||||
return &run, nil
|
||||
func GetRunBefore(ctx context.Context, _ *ActionRun) (*ActionRun, error) {
|
||||
// TODO return the most recent run related to the run given in argument
|
||||
// see https://codeberg.org/forgejo/user-research/issues/63 for context
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func GetLatestRunForBranchAndWorkflow(ctx context.Context, repoID int64, branch, workflowFile, event string) (*ActionRun, error) {
|
||||
|
@ -315,15 +345,26 @@ func GetLatestRunForBranchAndWorkflow(ctx context.Context, repoID int64, branch,
|
|||
}
|
||||
|
||||
func GetRunByID(ctx context.Context, id int64) (*ActionRun, error) {
|
||||
var run ActionRun
|
||||
has, err := db.GetEngine(ctx).Where("id=?", id).Get(&run)
|
||||
run, has, err := GetRunByIDWithHas(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, fmt.Errorf("run with id %d: %w", id, util.ErrNotExist)
|
||||
}
|
||||
|
||||
return &run, nil
|
||||
return run, nil
|
||||
}
|
||||
|
||||
func GetRunByIDWithHas(ctx context.Context, id int64) (*ActionRun, bool, error) {
|
||||
var run ActionRun
|
||||
has, err := db.GetEngine(ctx).Where("id=?", id).Get(&run)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
} else if !has {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
return &run, true, nil
|
||||
}
|
||||
|
||||
func GetRunByIndex(ctx context.Context, repoID, index int64) (*ActionRun, error) {
|
||||
|
|
|
@ -44,6 +44,38 @@ func init() {
|
|||
db.RegisterModel(new(ActionRunJob))
|
||||
}
|
||||
|
||||
func (job *ActionRunJob) HTMLURL(ctx context.Context) (string, error) {
|
||||
if job.Run == nil || job.Run.Repo == nil {
|
||||
return "", fmt.Errorf("action_run_job: load run and repo before accessing HTMLURL")
|
||||
}
|
||||
|
||||
// Find the "index" of the currently selected job... kinda ugly that the URL uses the index rather than some other
|
||||
// unique identifier of the job which could actually be stored upon it. But hard to change that now.
|
||||
allJobs, err := GetRunJobsByRunID(ctx, job.RunID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
jobIndex := -1
|
||||
for i, otherJob := range allJobs {
|
||||
if job.ID == otherJob.ID {
|
||||
jobIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if jobIndex == -1 {
|
||||
return "", fmt.Errorf("action_run_job: unable to find job on run: %d", job.ID)
|
||||
}
|
||||
|
||||
attempt := job.Attempt
|
||||
// If a job has never been fetched by a runner yet, it will have attempt 0 -- but this attempt will never have a
|
||||
// valid UI since attempt is incremented to 1 if it is picked up by a runner.
|
||||
if attempt == 0 {
|
||||
attempt = 1
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/actions/runs/%d/jobs/%d/attempt/%d", job.Run.Repo.HTMLURL(), job.Run.Index, jobIndex, attempt), nil
|
||||
}
|
||||
|
||||
func (job *ActionRunJob) Duration() time.Duration {
|
||||
return calculateDuration(job.Started, job.Stopped, job.Status)
|
||||
}
|
||||
|
|
|
@ -3,9 +3,14 @@
|
|||
package actions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/models/unittest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestActionRunJob_ItRunsOn(t *testing.T) {
|
||||
|
@ -27,3 +32,41 @@ func TestActionRunJob_ItRunsOn(t *testing.T) {
|
|||
|
||||
assert.False(t, actionJob.ItRunsOn(agentLabels))
|
||||
}
|
||||
|
||||
func TestActionRunJob_HTMLURL(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
tests := []struct {
|
||||
id int64
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
id: 192,
|
||||
expected: "https://try.gitea.io/user5/repo4/actions/runs/187/jobs/0/attempt/1",
|
||||
},
|
||||
{
|
||||
id: 393,
|
||||
expected: "https://try.gitea.io/user2/repo1/actions/runs/187/jobs/1/attempt/1",
|
||||
},
|
||||
{
|
||||
id: 394,
|
||||
expected: "https://try.gitea.io/user2/repo1/actions/runs/187/jobs/2/attempt/2",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(fmt.Sprintf("id=%d", tt.id), func(t *testing.T) {
|
||||
var job ActionRunJob
|
||||
has, err := db.GetEngine(t.Context()).Where("id=?", tt.id).Get(&job)
|
||||
require.NoError(t, err)
|
||||
require.True(t, has, "load ActionRunJob from fixture")
|
||||
|
||||
err = job.LoadAttributes(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
url, err := job.HTMLURL(t.Context())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.expected, url)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ package actions
|
|||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
repo_model "forgejo.org/models/repo"
|
||||
|
@ -64,14 +65,15 @@ func (runs RunList) LoadRepos(ctx context.Context) error {
|
|||
|
||||
type FindRunOptions struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
OwnerID int64
|
||||
WorkflowID string
|
||||
Ref string // the commit/tag/… that caused this workflow
|
||||
TriggerUserID int64
|
||||
TriggerEvent webhook_module.HookEventType
|
||||
Approved bool // not util.OptionalBool, it works only when it's true
|
||||
Status []Status
|
||||
RepoID int64
|
||||
OwnerID int64
|
||||
WorkflowID string
|
||||
Ref string // the commit/tag/… that caused this workflow
|
||||
TriggerUserID int64
|
||||
TriggerEvent webhook_module.HookEventType
|
||||
Approved bool // not util.OptionalBool, it works only when it's true
|
||||
Status []Status
|
||||
ConcurrencyGroup string
|
||||
}
|
||||
|
||||
func (opts FindRunOptions) ToConds() builder.Cond {
|
||||
|
@ -100,6 +102,9 @@ func (opts FindRunOptions) ToConds() builder.Cond {
|
|||
if opts.TriggerEvent != "" {
|
||||
cond = cond.And(builder.Eq{"trigger_event": opts.TriggerEvent})
|
||||
}
|
||||
if opts.ConcurrencyGroup != "" {
|
||||
cond = cond.And(builder.Eq{"concurrency_group": strings.ToLower(opts.ConcurrencyGroup)})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
|
|
|
@ -5,92 +5,84 @@ package actions
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
repo_model "forgejo.org/models/repo"
|
||||
"forgejo.org/models/unittest"
|
||||
"forgejo.org/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetRunBefore(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSetConcurrencyGroup(t *testing.T) {
|
||||
run := ActionRun{}
|
||||
run.SetConcurrencyGroup("abc123")
|
||||
assert.Equal(t, "abc123", run.ConcurrencyGroup)
|
||||
run.SetConcurrencyGroup("ABC123") // case should collapse in SetConcurrencyGroup
|
||||
assert.Equal(t, "abc123", run.ConcurrencyGroup)
|
||||
}
|
||||
|
||||
func TestSetDefaultConcurrencyGroup(t *testing.T) {
|
||||
run := ActionRun{
|
||||
Ref: "refs/heads/main",
|
||||
WorkflowID: "testing",
|
||||
TriggerEvent: "pull_request",
|
||||
}
|
||||
run.SetDefaultConcurrencyGroup()
|
||||
assert.Equal(t, "refs/heads/main_testing_pull_request__auto", run.ConcurrencyGroup)
|
||||
run = ActionRun{
|
||||
Ref: "refs/heads/main",
|
||||
WorkflowID: "TESTING", // case should collapse in SetDefaultConcurrencyGroup
|
||||
TriggerEvent: "pull_request",
|
||||
}
|
||||
run.SetDefaultConcurrencyGroup()
|
||||
assert.Equal(t, "refs/heads/main_testing_pull_request__auto", run.ConcurrencyGroup)
|
||||
}
|
||||
|
||||
func TestUpdateRepoRunsNumbers(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
// this repo is part of the test database requiring loading "repository.yml" in main_test.go
|
||||
var repoID int64 = 1
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
t.Run("Repo 1", func(t *testing.T) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
|
||||
workflowID := "test_workflow"
|
||||
require.NoError(t, updateRepoRunsNumbers(t.Context(), repo))
|
||||
|
||||
// third completed run
|
||||
time1, err := time.Parse(time.RFC3339, "2024-07-31T15:47:55+08:00")
|
||||
require.NoError(t, err)
|
||||
timeutil.MockSet(time1)
|
||||
run1 := ActionRun{
|
||||
ID: 1,
|
||||
Index: 1,
|
||||
RepoID: repoID,
|
||||
Stopped: timeutil.TimeStampNow(),
|
||||
WorkflowID: workflowID,
|
||||
}
|
||||
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
assert.Equal(t, 1, repo.NumActionRuns)
|
||||
assert.Equal(t, 1, repo.NumClosedActionRuns)
|
||||
})
|
||||
|
||||
// fourth completed run
|
||||
time2, err := time.Parse(time.RFC3339, "2024-08-31T15:47:55+08:00")
|
||||
require.NoError(t, err)
|
||||
timeutil.MockSet(time2)
|
||||
run2 := ActionRun{
|
||||
ID: 2,
|
||||
Index: 2,
|
||||
RepoID: repoID,
|
||||
Stopped: timeutil.TimeStampNow(),
|
||||
WorkflowID: workflowID,
|
||||
}
|
||||
t.Run("Repo 4", func(t *testing.T) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
||||
|
||||
// second completed run
|
||||
time3, err := time.Parse(time.RFC3339, "2024-07-31T15:47:54+08:00")
|
||||
require.NoError(t, err)
|
||||
timeutil.MockSet(time3)
|
||||
run3 := ActionRun{
|
||||
ID: 3,
|
||||
Index: 3,
|
||||
RepoID: repoID,
|
||||
Stopped: timeutil.TimeStampNow(),
|
||||
WorkflowID: workflowID,
|
||||
}
|
||||
require.NoError(t, updateRepoRunsNumbers(t.Context(), repo))
|
||||
|
||||
// first completed run
|
||||
time4, err := time.Parse(time.RFC3339, "2024-06-30T15:47:54+08:00")
|
||||
require.NoError(t, err)
|
||||
timeutil.MockSet(time4)
|
||||
run4 := ActionRun{
|
||||
ID: 4,
|
||||
Index: 4,
|
||||
RepoID: repoID,
|
||||
Stopped: timeutil.TimeStampNow(),
|
||||
WorkflowID: workflowID,
|
||||
}
|
||||
require.NoError(t, db.Insert(db.DefaultContext, &run1))
|
||||
runBefore, err := GetRunBefore(db.DefaultContext, repoID, run1.Stopped)
|
||||
// there is no run before run1
|
||||
require.Error(t, err)
|
||||
require.Nil(t, runBefore)
|
||||
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
||||
assert.Equal(t, 4, repo.NumActionRuns)
|
||||
assert.Equal(t, 4, repo.NumClosedActionRuns)
|
||||
})
|
||||
|
||||
// now there is only run3 before run1
|
||||
require.NoError(t, db.Insert(db.DefaultContext, &run3))
|
||||
runBefore, err = GetRunBefore(db.DefaultContext, repoID, run1.Stopped)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, run3.ID, runBefore.ID)
|
||||
t.Run("Repo 63", func(t *testing.T) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 63})
|
||||
|
||||
// there still is only run3 before run1
|
||||
require.NoError(t, db.Insert(db.DefaultContext, &run2))
|
||||
runBefore, err = GetRunBefore(db.DefaultContext, repoID, run1.Stopped)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, run3.ID, runBefore.ID)
|
||||
require.NoError(t, updateRepoRunsNumbers(t.Context(), repo))
|
||||
|
||||
// run4 is further away from run1
|
||||
require.NoError(t, db.Insert(db.DefaultContext, &run4))
|
||||
runBefore, err = GetRunBefore(db.DefaultContext, repoID, run1.Stopped)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, run3.ID, runBefore.ID)
|
||||
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 63})
|
||||
assert.Equal(t, 3, repo.NumActionRuns)
|
||||
assert.Equal(t, 2, repo.NumClosedActionRuns)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Columns specifc", func(t *testing.T) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
repo.Name = "ishouldnotbeupdated"
|
||||
|
||||
require.NoError(t, updateRepoRunsNumbers(t.Context(), repo))
|
||||
|
||||
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
assert.Equal(t, "repo1", repo.Name)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -17,8 +17,8 @@ import (
|
|||
"forgejo.org/modules/timeutil"
|
||||
"forgejo.org/modules/util"
|
||||
|
||||
"code.forgejo.org/forgejo/runner/v11/act/jobparser"
|
||||
lru "github.com/hashicorp/golang-lru/v2"
|
||||
"github.com/nektos/act/pkg/jobparser"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
|
@ -148,6 +148,21 @@ func (task *ActionTask) GenerateToken() (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
// Retrieve all the attempts from the same job as the target `ActionTask`. Limited fields are queried to avoid loading
|
||||
// the LogIndexes blob when not needed.
|
||||
func (task *ActionTask) GetAllAttempts(ctx context.Context) ([]*ActionTask, error) {
|
||||
var attempts []*ActionTask
|
||||
err := db.GetEngine(ctx).
|
||||
Cols("id", "attempt", "status", "started").
|
||||
Where("job_id=?", task.JobID).
|
||||
Desc("attempt").
|
||||
Find(&attempts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return attempts, nil
|
||||
}
|
||||
|
||||
func GetTaskByID(ctx context.Context, id int64) (*ActionTask, error) {
|
||||
var task ActionTask
|
||||
has, err := db.GetEngine(ctx).Where("id=?", id).Get(&task)
|
||||
|
@ -160,6 +175,18 @@ func GetTaskByID(ctx context.Context, id int64) (*ActionTask, error) {
|
|||
return &task, nil
|
||||
}
|
||||
|
||||
func GetTaskByJobAttempt(ctx context.Context, jobID, attempt int64) (*ActionTask, error) {
|
||||
var task ActionTask
|
||||
has, err := db.GetEngine(ctx).Where("job_id=?", jobID).Where("attempt=?", attempt).Get(&task)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, fmt.Errorf("task with job_id %d and attempt %d: %w", jobID, attempt, util.ErrNotExist)
|
||||
}
|
||||
|
||||
return &task, nil
|
||||
}
|
||||
|
||||
func GetRunningTaskByToken(ctx context.Context, token string) (*ActionTask, error) {
|
||||
errNotExist := fmt.Errorf("task with token %q: %w", token, util.ErrNotExist)
|
||||
if token == "" {
|
||||
|
@ -212,6 +239,88 @@ func GetRunningTaskByToken(ctx context.Context, token string) (*ActionTask, erro
|
|||
return nil, errNotExist
|
||||
}
|
||||
|
||||
func getConcurrencyCondition() builder.Cond {
|
||||
concurrencyCond := builder.NewCond()
|
||||
|
||||
// OK to pick if there's no concurrency_group on the run
|
||||
concurrencyCond = concurrencyCond.Or(builder.Eq{"concurrency_group": ""})
|
||||
concurrencyCond = concurrencyCond.Or(builder.IsNull{"concurrency_group"})
|
||||
|
||||
// OK to pick if it's not a "QueueBehind" concurrency type
|
||||
concurrencyCond = concurrencyCond.Or(builder.Neq{"concurrency_type": QueueBehind})
|
||||
|
||||
// subQuery ends up representing all the runs that would block a run from executing:
|
||||
subQuery := builder.Select("id").From("action_run", "inner_run").
|
||||
// A run can't block itself, so exclude it from this search
|
||||
Where(builder.Neq{"inner_run.id": builder.Expr("outer_run.id")}).
|
||||
// Blocking runs must be from the same repo & concurrency group
|
||||
And(builder.Eq{"inner_run.repo_id": builder.Expr("outer_run.repo_id")}).
|
||||
And(builder.Eq{"inner_run.concurrency_group": builder.Expr("outer_run.concurrency_group")}).
|
||||
And(
|
||||
// Ideally the logic here would be that a blocking run is "not done", and "younger", which allows each run
|
||||
// to be blocked on the previous runs in the concurrency group and therefore execute in order from oldest to
|
||||
// newest.
|
||||
//
|
||||
// But it's possible for runs to be required to run out-of-order -- for example, if a younger run has
|
||||
// already completed but then it is re-run. If we only used "not done" and "younger" as logic, then the
|
||||
// re-run would not be blocked, and therefore would violate the concurrency group's single-run goal.
|
||||
//
|
||||
// So we use two conditions to meet both needs:
|
||||
//
|
||||
// Blocking runs have a running status...
|
||||
builder.Eq{"inner_run.status": StatusRunning}.Or(
|
||||
// Blocking runs don't have a IsDone status & are younger than the outer_run
|
||||
builder.NotIn("inner_run.status", []Status{StatusSuccess, StatusFailure, StatusCancelled, StatusSkipped}).
|
||||
And(builder.Lt{"inner_run.`index`": builder.Expr("outer_run.`index`")})))
|
||||
|
||||
// OK to pick if there are no blocking runs
|
||||
concurrencyCond = concurrencyCond.Or(builder.NotExists(subQuery))
|
||||
|
||||
return concurrencyCond
|
||||
}
|
||||
|
||||
// Returns all the available jobs that could be executed on `runner`, before label filtering is applied. Note that
|
||||
// only a single job can actually be run from this result for any given invocation, as multiple runs (in order) from any
|
||||
// single concurrency group could be returned.
|
||||
func GetAvailableJobsForRunner(e db.Engine, runner *ActionRunner) ([]*ActionRunJob, error) {
|
||||
jobCond := builder.NewCond()
|
||||
if runner.RepoID != 0 {
|
||||
jobCond = builder.Eq{"repo_id": runner.RepoID}
|
||||
} else if runner.OwnerID != 0 {
|
||||
jobCond = builder.In("repo_id", builder.Select("`repository`.id").From("repository").
|
||||
Join("INNER", "repo_unit", "`repository`.id = `repo_unit`.repo_id").
|
||||
Where(builder.Eq{"`repository`.owner_id": runner.OwnerID, "`repo_unit`.type": unit.TypeActions}))
|
||||
}
|
||||
// Concurrency group checks for queuing one run behind the last run in the concurrency group are more
|
||||
// computationally expensive on the database. To manage the risk that this might have on large-scale deployments
|
||||
// When this feature is initially released, it can be disabled in the ini file by setting
|
||||
// `CONCURRENCY_GROUP_QUEUE_ENABLED = false` in the `[actions]` section. If disabled, then actions with a
|
||||
// concurrency group and `cancel-in-progress: false` will run simultaneously rather than being queued.
|
||||
if setting.Actions.ConcurrencyGroupQueueEnabled {
|
||||
jobCond = jobCond.And(getConcurrencyCondition())
|
||||
}
|
||||
if jobCond.IsValid() {
|
||||
// It is *likely* more efficient to use an EXISTS query here rather than an IN clause, as that allows the
|
||||
// database's query optimizer to perform partial computation of the subquery rather than complete computation.
|
||||
// However, database engines can be fickle and difficult to predict. We'll retain the original IN clause
|
||||
// implementation when ConcurrencyGroupQueueEnabled is disabled, which should maintain the same performance
|
||||
// characteristics. When ConcurrencyGroupQueueEnabled is enabled, it will switch to the EXISTS clause.
|
||||
if setting.Actions.ConcurrencyGroupQueueEnabled {
|
||||
jobCond = builder.Exists(builder.Select("id").From("action_run", "outer_run").
|
||||
Where(builder.Eq{"outer_run.id": builder.Expr("action_run_job.run_id")}).
|
||||
And(jobCond))
|
||||
} else {
|
||||
jobCond = builder.In("run_id", builder.Select("id").From("action_run", "outer_run").Where(jobCond))
|
||||
}
|
||||
}
|
||||
|
||||
var jobs []*ActionRunJob
|
||||
if err := e.Where("task_id=? AND status=?", 0, StatusWaiting).And(jobCond).Asc("updated", "id").Find(&jobs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return jobs, nil
|
||||
}
|
||||
|
||||
func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask, bool, error) {
|
||||
ctx, commiter, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
|
@ -221,20 +330,8 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
|
|||
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
jobCond := builder.NewCond()
|
||||
if runner.RepoID != 0 {
|
||||
jobCond = builder.Eq{"repo_id": runner.RepoID}
|
||||
} else if runner.OwnerID != 0 {
|
||||
jobCond = builder.In("repo_id", builder.Select("`repository`.id").From("repository").
|
||||
Join("INNER", "repo_unit", "`repository`.id = `repo_unit`.repo_id").
|
||||
Where(builder.Eq{"`repository`.owner_id": runner.OwnerID, "`repo_unit`.type": unit.TypeActions}))
|
||||
}
|
||||
if jobCond.IsValid() {
|
||||
jobCond = builder.In("run_id", builder.Select("id").From("action_run").Where(jobCond))
|
||||
}
|
||||
|
||||
var jobs []*ActionRunJob
|
||||
if err := e.Where("task_id=? AND status=?", 0, StatusWaiting).And(jobCond).Asc("updated", "id").Find(&jobs); err != nil {
|
||||
jobs, err := GetAvailableJobsForRunner(e, runner)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
|
@ -275,7 +372,7 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
|
|||
}
|
||||
|
||||
var workflowJob *jobparser.Job
|
||||
if gots, err := jobparser.Parse(job.WorkflowPayload); err != nil {
|
||||
if gots, err := jobparser.Parse(job.WorkflowPayload, false); err != nil {
|
||||
return nil, false, fmt.Errorf("parse workflow of job %d: %w", job.ID, err)
|
||||
} else if len(gots) != 1 {
|
||||
return nil, false, fmt.Errorf("workflow of job %d: not single workflow", job.ID)
|
||||
|
|
48
models/actions/task_test.go
Normal file
48
models/actions/task_test.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/models/unittest"
|
||||
"forgejo.org/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestActionTask_GetAllAttempts(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
var task ActionTask
|
||||
has, err := db.GetEngine(t.Context()).Where("id=?", 47).Get(&task)
|
||||
require.NoError(t, err)
|
||||
require.True(t, has, "load ActionTask from fixture")
|
||||
|
||||
allAttempts, err := task.GetAllAttempts(t.Context())
|
||||
require.NoError(t, err)
|
||||
require.Len(t, allAttempts, 3)
|
||||
assert.EqualValues(t, 47, allAttempts[0].ID, "ordered by attempt, 1")
|
||||
assert.EqualValues(t, 53, allAttempts[1].ID, "ordered by attempt, 2")
|
||||
assert.EqualValues(t, 52, allAttempts[2].ID, "ordered by attempt, 3")
|
||||
|
||||
// GetAllAttempts doesn't populate all fields; so check expected fields from one of the records
|
||||
assert.EqualValues(t, 3, allAttempts[0].Attempt, "read Attempt field")
|
||||
assert.Equal(t, StatusRunning, allAttempts[0].Status, "read Status field")
|
||||
assert.Equal(t, timeutil.TimeStamp(1683636528), allAttempts[0].Started, "read Started field")
|
||||
}
|
||||
|
||||
func TestActionTask_GetTaskByJobAttempt(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
task, err := GetTaskByJobAttempt(t.Context(), 192, 2)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, 192, task.JobID)
|
||||
assert.EqualValues(t, 2, task.Attempt)
|
||||
|
||||
_, err = GetTaskByJobAttempt(t.Context(), 192, 100)
|
||||
assert.ErrorContains(t, err, "task with job_id 192 and attempt 100: resource does not exist")
|
||||
}
|
|
@ -26,6 +26,7 @@ import (
|
|||
"forgejo.org/modules/base"
|
||||
"forgejo.org/modules/container"
|
||||
"forgejo.org/modules/git"
|
||||
"forgejo.org/modules/json"
|
||||
"forgejo.org/modules/log"
|
||||
"forgejo.org/modules/setting"
|
||||
"forgejo.org/modules/structs"
|
||||
|
@ -386,8 +387,21 @@ func (a *Action) IsIssueEvent() bool {
|
|||
|
||||
// GetIssueInfos returns a list of associated information with the action.
|
||||
func (a *Action) GetIssueInfos() []string {
|
||||
// Previously multiple pieces of data used to be encoded into a.Content by pipe-separating them, but this doesn't
|
||||
// work well if some of the user-entered pieces of content (issue titles, comments, etc.) contain pipes. The newer
|
||||
// storage format is to json-encode a string array, which we check for and prefer... then fallback to assuming old.
|
||||
var ret []string
|
||||
if strings.HasPrefix(a.Content, "[") && strings.HasSuffix(a.Content, "]") {
|
||||
ret = make([]string, 0, 3)
|
||||
err := json.Unmarshal([]byte(a.Content), &ret)
|
||||
if err != nil {
|
||||
log.Error("GetIssueInfos json decoding error: %v", err)
|
||||
}
|
||||
} else {
|
||||
ret = strings.SplitN(a.Content, "|", 3)
|
||||
}
|
||||
|
||||
// make sure it always returns 3 elements, because there are some access to the a[1] and a[2] without checking the length
|
||||
ret := strings.SplitN(a.Content, "|", 3)
|
||||
for len(ret) < 3 {
|
||||
ret = append(ret, "")
|
||||
}
|
||||
|
@ -473,8 +487,11 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
|
|||
return nil, 0, err
|
||||
}
|
||||
|
||||
sess := db.GetEngine(ctx).Where(cond).
|
||||
Select("`action`.*"). // this line will avoid select other joined table's columns
|
||||
Join("INNER", "repository", "`repository`.id = `action`.repo_id")
|
||||
|
||||
opts.SetDefaultValues()
|
||||
sess := db.GetEngine(ctx).Where(cond)
|
||||
sess = db.SetSessionPagination(sess, &opts)
|
||||
|
||||
actions := make([]*Action, 0, opts.PageSize)
|
||||
|
@ -767,7 +784,9 @@ func DeleteIssueActions(ctx context.Context, repoID, issueID, issueIndex int64)
|
|||
|
||||
_, err := e.Where("repo_id = ?", repoID).
|
||||
In("op_type", ActionCreateIssue, ActionCreatePullRequest).
|
||||
Where("content LIKE ?", strconv.FormatInt(issueIndex, 10)+"|%"). // "IssueIndex|content..."
|
||||
Where(builder.Or(
|
||||
builder.Like{"content", strconv.FormatInt(issueIndex, 10) + "|%"}, // "IssueIndex|content..."
|
||||
builder.Like{"content", "[\"" + strconv.FormatInt(issueIndex, 10) + "\"%"})). // JSON, ["IssueIndex"...
|
||||
Delete(&Action{})
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -227,6 +227,24 @@ func TestNotifyWatchers(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestGetFeedsCorrupted(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
unittest.AssertExistsAndLoadBean(t, &activities_model.Action{
|
||||
ID: 8,
|
||||
RepoID: 1700,
|
||||
})
|
||||
|
||||
actions, count, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
|
||||
RequestedUser: user,
|
||||
Actor: user,
|
||||
IncludePrivate: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, actions)
|
||||
assert.Equal(t, int64(0), count)
|
||||
}
|
||||
|
||||
func TestConsistencyUpdateAction(t *testing.T) {
|
||||
if !setting.Database.Type.IsSQLite3() {
|
||||
t.Skip("Test is only for SQLite database.")
|
||||
|
@ -290,14 +308,69 @@ func TestDeleteIssueActions(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
err = db.Insert(db.DefaultContext, &activities_model.Action{
|
||||
OpType: activities_model.ActionCreateIssue,
|
||||
RepoID: issue.RepoID,
|
||||
OpType: activities_model.ActionCreateIssue,
|
||||
RepoID: issue.RepoID,
|
||||
// Older Content format...
|
||||
Content: fmt.Sprintf("%d|content...", issue.Index),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = db.Insert(db.DefaultContext, &activities_model.Action{
|
||||
OpType: activities_model.ActionCreateIssue,
|
||||
RepoID: issue.RepoID,
|
||||
// JSON-encoded Content format...
|
||||
Content: fmt.Sprintf("[\"%d\",\"content...\"]", issue.Index),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// assert that the actions exist, then delete them
|
||||
unittest.AssertCount(t, &activities_model.Action{}, 2)
|
||||
unittest.AssertCount(t, &activities_model.Action{}, 3)
|
||||
require.NoError(t, activities_model.DeleteIssueActions(db.DefaultContext, issue.RepoID, issue.ID, issue.Index))
|
||||
unittest.AssertCount(t, &activities_model.Action{}, 0)
|
||||
}
|
||||
|
||||
func TestGetIssueInfos(t *testing.T) {
|
||||
tt := []struct {
|
||||
content string
|
||||
field1 string
|
||||
field2 string
|
||||
field3 string
|
||||
}{
|
||||
{
|
||||
content: "4|",
|
||||
field1: "4",
|
||||
},
|
||||
{
|
||||
content: "2|docs: Add README w/ template sections",
|
||||
field1: "2",
|
||||
field2: "docs: Add README w/ template sections",
|
||||
},
|
||||
{
|
||||
content: "2|docs: Add README w/ template sections|Some comment...",
|
||||
field1: "2",
|
||||
field2: "docs: Add README w/ template sections",
|
||||
field3: "Some comment...",
|
||||
},
|
||||
{
|
||||
content: "[\"4\"]",
|
||||
field1: "4",
|
||||
},
|
||||
{
|
||||
content: "[\"2\", \"docs: Add README w/ | template sections\"]",
|
||||
field1: "2",
|
||||
field2: "docs: Add README w/ | template sections",
|
||||
},
|
||||
{
|
||||
content: "[\"2\", \"docs: Add README w/ | template sections\", \"Some | comment...\"]",
|
||||
field1: "2",
|
||||
field2: "docs: Add README w/ | template sections",
|
||||
field3: "Some | comment...",
|
||||
},
|
||||
}
|
||||
for _, test := range tt {
|
||||
action := &activities_model.Action{Content: test.content}
|
||||
issueInfos := action.GetIssueInfos()
|
||||
assert.Equal(t, test.field1, issueInfos[0])
|
||||
assert.Equal(t, test.field2, issueInfos[1])
|
||||
assert.Equal(t, test.field3, issueInfos[2])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ type ObjectVerification struct {
|
|||
TrustStatus string
|
||||
}
|
||||
|
||||
// llu:TrKeys
|
||||
const (
|
||||
// BadSignature is used as the reason when the signature has a KeyID that is in the db
|
||||
// but no key that has that ID verifies the signature. This is a suspicious failure.
|
||||
|
|
|
@ -15,8 +15,6 @@ func TestMain(m *testing.M) {
|
|||
"gpg_key.yml",
|
||||
"public_key.yml",
|
||||
"TestParseCommitWithSSHSignature/public_key.yml",
|
||||
"deploy_key.yml",
|
||||
"gpg_key_import.yml",
|
||||
"user.yml",
|
||||
"email_address.yml",
|
||||
},
|
||||
|
|
|
@ -127,6 +127,13 @@ func (t *AccessToken) DisplayPublicOnly() bool {
|
|||
return publicOnly
|
||||
}
|
||||
|
||||
// UpdateLastUsed updates the time this token was last used to now.
|
||||
func (t *AccessToken) UpdateLastUsed(ctx context.Context) error {
|
||||
t.UpdatedUnix = timeutil.TimeStampNow()
|
||||
_, err := db.GetEngine(ctx).ID(t.ID).Cols("updated_unix").NoAutoTime().Update(t)
|
||||
return err
|
||||
}
|
||||
|
||||
func getAccessTokenIDFromCache(token string) int64 {
|
||||
if successfulAccessTokenCache == nil {
|
||||
return 0
|
||||
|
@ -220,12 +227,6 @@ func (opts ListAccessTokensOptions) ToOrders() string {
|
|||
return "created_unix DESC"
|
||||
}
|
||||
|
||||
// UpdateAccessToken updates information of access token.
|
||||
func UpdateAccessToken(ctx context.Context, t *AccessToken) error {
|
||||
_, err := db.GetEngine(ctx).ID(t.ID).AllCols().Update(t)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteAccessTokenByID deletes access token by given ID.
|
||||
func DeleteAccessTokenByID(ctx context.Context, id, userID int64) error {
|
||||
cnt, err := db.GetEngine(ctx).ID(id).Delete(&AccessToken{
|
||||
|
@ -258,5 +259,6 @@ func RegenerateAccessTokenByID(ctx context.Context, id, userID int64) (*AccessTo
|
|||
// Reset the creation time, token is unused
|
||||
t.UpdatedUnix = timeutil.TimeStampNow()
|
||||
|
||||
return t, UpdateAccessToken(ctx, t)
|
||||
_, err = db.GetEngine(ctx).ID(t.ID).Cols("token_salt", "token", "token_hash", "token_last_eight", "updated_unix").NoAutoTime().Update(t)
|
||||
return t, err
|
||||
}
|
||||
|
|
|
@ -5,10 +5,12 @@ package auth_test
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
auth_model "forgejo.org/models/auth"
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/models/unittest"
|
||||
"forgejo.org/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -107,14 +109,17 @@ func TestListAccessTokens(t *testing.T) {
|
|||
assert.Empty(t, tokens)
|
||||
}
|
||||
|
||||
func TestUpdateAccessToken(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
token, err := auth_model.GetAccessTokenBySHA(db.DefaultContext, "4c6f36e6cf498e2a448662f915d932c09c5a146c")
|
||||
require.NoError(t, err)
|
||||
token.Name = "Token Z"
|
||||
func TestUpdateLastUsed(t *testing.T) {
|
||||
timeutil.MockSet(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
defer timeutil.MockUnset()
|
||||
|
||||
require.NoError(t, auth_model.UpdateAccessToken(db.DefaultContext, token))
|
||||
unittest.AssertExistsAndLoadBean(t, token)
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
token := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{ID: 2})
|
||||
|
||||
require.NoError(t, token.UpdateLastUsed(t.Context()))
|
||||
|
||||
token = unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{ID: 2})
|
||||
assert.Equal(t, timeutil.TimeStampNow(), token.UpdatedUnix)
|
||||
}
|
||||
|
||||
func TestDeleteAccessTokenByID(t *testing.T) {
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"forgejo.org/models/unittest"
|
||||
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -32,3 +33,30 @@ func TestHasTwoFactorByUID(t *testing.T) {
|
|||
assert.True(t, ok)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewTwoFactor(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
otpKey, err := totp.Generate(totp.GenerateOpts{
|
||||
SecretSize: 40,
|
||||
Issuer: "forgejo-test",
|
||||
AccountName: "user2",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("Transaction failed", func(t *testing.T) {
|
||||
reset := unittest.SetFaultInjector(2)
|
||||
require.ErrorIs(t, NewTwoFactor(t.Context(), &TwoFactor{UID: 44}, otpKey.Secret()), unittest.ErrFaultInjected)
|
||||
reset()
|
||||
|
||||
unittest.AssertExistsIf(t, false, &TwoFactor{UID: 44})
|
||||
})
|
||||
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
reset := unittest.SetFaultInjector(4)
|
||||
require.NoError(t, NewTwoFactor(t.Context(), &TwoFactor{UID: 44}, otpKey.Secret()))
|
||||
reset()
|
||||
|
||||
unittest.AssertExistsIf(t, true, &TwoFactor{UID: 44})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -141,7 +141,7 @@ func TxContext(parentCtx context.Context) (*Context, Committer, error) {
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
return newContext(DefaultContext, sess, true), sess, nil
|
||||
return newContext(parentCtx, sess, true), sess, nil
|
||||
}
|
||||
|
||||
// WithTx represents executing database operations on a transaction, if the transaction exist,
|
||||
|
@ -298,6 +298,31 @@ func TruncateBeans(ctx context.Context, beans ...any) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
// TruncateBeansCascade deletes all given beans. Beans MUST NOT contain delete conditions, as tables related by foreign
|
||||
// keys will also be truncated.
|
||||
func TruncateBeansCascade(ctx context.Context, beans ...any) (err error) {
|
||||
// Expand the list of beans to any other table with a foreign key reference to the beans
|
||||
cascadeTables, err := extendBeansForCascade(beans)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Sort the beans in inverse foreign key delete order
|
||||
cascadeSorted, err := sortBeans(cascadeTables, foreignKeySortDelete)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Execute the truncate
|
||||
e := GetEngine(ctx)
|
||||
for i := range cascadeSorted {
|
||||
if _, err = e.Truncate(cascadeSorted[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CountByBean counts the number of database records according non-empty fields of the bean as conditions.
|
||||
func CountByBean(ctx context.Context, bean any) (int64, error) {
|
||||
return GetEngine(ctx).Count(bean)
|
||||
|
|
|
@ -84,4 +84,16 @@ func TestTxContext(t *testing.T) {
|
|||
return nil
|
||||
}))
|
||||
}
|
||||
|
||||
t.Run("Reuses parent context", func(t *testing.T) {
|
||||
type unique struct{}
|
||||
|
||||
ctx := context.WithValue(db.DefaultContext, unique{}, "yes!")
|
||||
assert.False(t, db.InTransaction(ctx))
|
||||
|
||||
require.NoError(t, db.WithTx(ctx, func(ctx context.Context) error {
|
||||
assert.Equal(t, "yes!", ctx.Value(unique{}))
|
||||
return nil
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"forgejo.org/modules/container"
|
||||
"forgejo.org/modules/log"
|
||||
"forgejo.org/modules/setting"
|
||||
|
||||
|
@ -160,9 +161,13 @@ func (w engineGroupWrapper) AddHook(hook contexts.Hook) bool {
|
|||
|
||||
// SyncAllTables sync the schemas of all tables
|
||||
func SyncAllTables() error {
|
||||
_, err := x.StoreEngine("InnoDB").SyncWithOptions(xorm.SyncOptions{
|
||||
sortedTables, err := sortBeans(tables, foreignKeySortInsert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = x.StoreEngine("InnoDB").SyncWithOptions(xorm.SyncOptions{
|
||||
WarnIfDatabaseColumnMissed: true,
|
||||
}, tables...)
|
||||
}, sortedTables...)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -385,6 +390,15 @@ func (TracingHook) BeforeProcess(c *contexts.ContextHook) (context.Context, erro
|
|||
}
|
||||
|
||||
func (TracingHook) AfterProcess(c *contexts.ContextHook) error {
|
||||
if c.Result != nil {
|
||||
if rowsAffected, err := c.Result.RowsAffected(); err == nil {
|
||||
trace.Logf(c.Ctx, "rows affected", "%d", rowsAffected)
|
||||
}
|
||||
if lastID, err := c.Result.LastInsertId(); err == nil {
|
||||
trace.Logf(c.Ctx, "last insert id", "%d", lastID)
|
||||
}
|
||||
}
|
||||
|
||||
c.Ctx.Value(sqlTask{}).(*trace.Task).End()
|
||||
return nil
|
||||
}
|
||||
|
@ -438,3 +452,12 @@ func GetMasterEngine(x Engine) (*xorm.Engine, error) {
|
|||
|
||||
return engine, nil
|
||||
}
|
||||
|
||||
// GetTableNames returns the table name of all registered models.
|
||||
func GetTableNames() container.Set[string] {
|
||||
names := make(container.Set[string])
|
||||
for _, table := range tables {
|
||||
names.Add(x.TableName(table))
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
|
275
models/db/foreign_keys.go
Normal file
275
models/db/foreign_keys.go
Normal file
|
@ -0,0 +1,275 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"forgejo.org/modules/container"
|
||||
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
type schemaWithDefaultBean struct {
|
||||
schema *schemas.Table
|
||||
bean any
|
||||
}
|
||||
|
||||
var (
|
||||
cachedForeignKeyOrderedTables = sync.OnceValues(foreignKeyOrderedTables)
|
||||
cachedTableNameLookupOrder = sync.OnceValues(tableNameLookupOrder)
|
||||
// Slice of all registered tables, including their bean from `RegisterModel()` and their schemas.Table reference.
|
||||
cachedSchemaTables = sync.OnceValues(func() ([]schemaWithDefaultBean, error) {
|
||||
schemaTables := make([]schemaWithDefaultBean, 0, len(tables))
|
||||
for _, bean := range tables {
|
||||
table, err := TableInfo(bean)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cachedSchemaTables: failure to fetch schema table for bean %#v: %w", bean, err)
|
||||
}
|
||||
schemaTables = append(schemaTables, schemaWithDefaultBean{
|
||||
schema: table,
|
||||
bean: bean,
|
||||
})
|
||||
}
|
||||
return schemaTables, nil
|
||||
})
|
||||
// Lookup map from table name -> {schemas.Table, bean}. The bean is the empty bean from `RegisterModel()`.
|
||||
cachedTableMap = sync.OnceValues(func() (map[string]schemaWithDefaultBean, error) {
|
||||
schemaTables, err := cachedSchemaTables()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
retval := make(map[string]schemaWithDefaultBean, len(schemaTables))
|
||||
for _, table := range schemaTables {
|
||||
retval[table.schema.Name] = table
|
||||
}
|
||||
return retval, nil
|
||||
})
|
||||
// Table A has foreign keys to [B, C], this is a map of A -> {B, C}.
|
||||
cachedReferencingTables = sync.OnceValues(func() (map[string][]string, error) {
|
||||
schemaTables, err := cachedSchemaTables()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return calculateReferencingTables(schemaTables), nil
|
||||
})
|
||||
// Table A has foreign keys to [B, C], this is a map of B -> {A}, C -> {A}.
|
||||
cachedReferencedTables = sync.OnceValues(func() (map[string][]string, error) {
|
||||
referencingTables, err := cachedReferencingTables()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
referencedTables := make(map[string][]string)
|
||||
for referencingTable, targetTables := range referencingTables {
|
||||
for _, targetTable := range targetTables {
|
||||
referencedTables[targetTable] = append(referencedTables[targetTable], referencingTable)
|
||||
}
|
||||
}
|
||||
return referencedTables, nil
|
||||
})
|
||||
)
|
||||
|
||||
// Create a map for each schema table which contains a slice of all the tables that reference it (with a foreign key).
|
||||
func calculateReferencingTables(tables []schemaWithDefaultBean) map[string][]string {
|
||||
referencingTables := make(map[string][]string, len(tables))
|
||||
for _, table := range tables {
|
||||
tableName := table.schema.Name
|
||||
for _, fk := range table.schema.ForeignKeys {
|
||||
referencingTables[tableName] = append(referencingTables[tableName], fk.TargetTableName)
|
||||
}
|
||||
}
|
||||
return referencingTables
|
||||
}
|
||||
|
||||
// Create a list of database tables in their "foreign key order". This order specifies the safe insertion order for
|
||||
// records into tables, where earlier tables in the list are referenced by foreign keys that exist in tables later in
|
||||
// the list. This order can be used in reverse as a safe deletion order as well.
|
||||
//
|
||||
// An ordered list of tables is incompatible with tables that have self-referencing foreign keys and circular referenced
|
||||
// foreign keys; however neither of those cases are in-use in Forgejo.
|
||||
func calculateTableForeignKeyOrder(tables []schemaWithDefaultBean) ([]schemaWithDefaultBean, error) {
|
||||
remainingTables := slices.Clone(tables)
|
||||
|
||||
referencingTables := calculateReferencingTables(remainingTables)
|
||||
orderedTables := make([]schemaWithDefaultBean, 0, len(remainingTables))
|
||||
|
||||
for len(remainingTables) > 0 {
|
||||
nextGroup := make([]schemaWithDefaultBean, 0, len(remainingTables))
|
||||
|
||||
for _, targetTable := range remainingTables {
|
||||
// Skip if this targetTable has foreign keys and the target table hasn't been created.
|
||||
slice, ok := referencingTables[targetTable.schema.Name]
|
||||
if ok && len(slice) > 0 { // This table is still referencing an uncreated table
|
||||
continue
|
||||
}
|
||||
// This table's references are satisfied or it had none
|
||||
nextGroup = append(nextGroup, targetTable)
|
||||
}
|
||||
|
||||
if len(nextGroup) == 0 {
|
||||
return nil, fmt.Errorf("calculateTableForeignKeyOrder: unable to figure out next table from remainingTables = %#v", remainingTables)
|
||||
}
|
||||
|
||||
orderedTables = append(orderedTables, nextGroup...)
|
||||
|
||||
// Cleanup between loops: remove each table in nextGroup from remainingTables, and remove their table names from
|
||||
// referencingTables as well.
|
||||
for _, doneTable := range nextGroup {
|
||||
remainingTables = slices.DeleteFunc(remainingTables, func(remainingTable schemaWithDefaultBean) bool {
|
||||
return remainingTable.schema.Name == doneTable.schema.Name
|
||||
})
|
||||
for referencingTable, referencedTables := range referencingTables {
|
||||
referencingTables[referencingTable] = slices.DeleteFunc(referencedTables, func(tableName string) bool {
|
||||
return tableName == doneTable.schema.Name
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return orderedTables, nil
|
||||
}
|
||||
|
||||
// Create a list of registered database tables in their "foreign key order", per calculateTableForeignKeyOrder.
|
||||
func foreignKeyOrderedTables() ([]schemaWithDefaultBean, error) {
|
||||
schemaTables, err := cachedSchemaTables()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderedTables, err := calculateTableForeignKeyOrder(schemaTables)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return orderedTables, nil
|
||||
}
|
||||
|
||||
// Create a map from each registered database table's name to its order in "foreign key order", per
|
||||
// calculateTableForeignKeyOrder.
|
||||
func tableNameLookupOrder() (map[string]int, error) {
|
||||
tables, err := cachedForeignKeyOrderedTables()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lookupMap := make(map[string]int, len(tables))
|
||||
for i, table := range tables {
|
||||
lookupMap[table.schema.Name] = i
|
||||
}
|
||||
|
||||
return lookupMap, nil
|
||||
}
|
||||
|
||||
// When used as a comparator function in `slices.SortFunc`, can sort a slice into the safe insertion order for records
|
||||
// in tables, where earlier tables in the list are referenced by foreign keys that exist in tables later in the list.
|
||||
func TableNameInsertionOrderSortFunc(table1, table2 string) int {
|
||||
lookupMap, err := cachedTableNameLookupOrder()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("cachedTableNameLookupOrder failed: %#v", err))
|
||||
}
|
||||
|
||||
// Since this is typically used by `slices.SortFunc` it can't return an error. If a table is referenced that isn't
|
||||
// a registered model then it will be sorted at the beginning -- this case is used in models/gitea_migrations/test.
|
||||
val1, ok := lookupMap[table1]
|
||||
if !ok {
|
||||
val1 = -1
|
||||
}
|
||||
val2, ok := lookupMap[table2]
|
||||
if !ok {
|
||||
val2 = -1
|
||||
}
|
||||
|
||||
return cmp.Compare(val1, val2)
|
||||
}
|
||||
|
||||
// In "Insert" order, tables that have a foreign key will be sorted after the tables that the foreign key points to, so
|
||||
// that records can be safely inserted in this order. "Delete" order is the opposite, and allows records to be safely
|
||||
// deleted in this order.
|
||||
type foreignKeySortOrder int8
|
||||
|
||||
const (
|
||||
foreignKeySortInsert foreignKeySortOrder = iota
|
||||
foreignKeySortDelete
|
||||
)
|
||||
|
||||
// Sort the provided beans in the provided foreign-key sort order.
|
||||
func sortBeans(beans []any, sortOrder foreignKeySortOrder) ([]any, error) {
|
||||
type beanWithTableName struct {
|
||||
bean any
|
||||
tableName string
|
||||
}
|
||||
|
||||
beansWithTableNames := make([]beanWithTableName, 0, len(beans))
|
||||
for _, bean := range beans {
|
||||
table, err := TableInfo(bean)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sortBeans: failure to fetch schema table for bean %#v: %w", bean, err)
|
||||
}
|
||||
beansWithTableNames = append(beansWithTableNames, beanWithTableName{bean: bean, tableName: table.Name})
|
||||
}
|
||||
|
||||
slices.SortFunc(beansWithTableNames, func(a, b beanWithTableName) int {
|
||||
if sortOrder == foreignKeySortInsert {
|
||||
return TableNameInsertionOrderSortFunc(a.tableName, b.tableName)
|
||||
}
|
||||
return TableNameInsertionOrderSortFunc(b.tableName, a.tableName)
|
||||
})
|
||||
|
||||
beanRetval := make([]any, len(beans))
|
||||
for i, beanWithTableName := range beansWithTableNames {
|
||||
beanRetval[i] = beanWithTableName.bean
|
||||
}
|
||||
return beanRetval, nil
|
||||
}
|
||||
|
||||
// A database operation on `beans` may need to affect additional tables based upon foreign keys to those beans.
|
||||
// extendBeansForCascade returns a new list of beans which includes all the referencing tables that link to `beans`'
|
||||
// tables. For example, provided a `&User{}`, it will return `[&User{}, &Stopwatch{}, ...]` where `Stopwatch` is a table
|
||||
// that references `User`. The additional beans returned will be default structs that were provided to
|
||||
// `db.RegisterModel`.
|
||||
func extendBeansForCascade(beans []any) ([]any, error) {
|
||||
referencedTables, err := cachedReferencedTables()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tableMap, err := cachedTableMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deduplicateTables := make(container.Set[string], len(beans))
|
||||
|
||||
finalBeans := slices.Clone(beans)
|
||||
newBeans := beans
|
||||
nextBeanSet := make([]any, 0)
|
||||
|
||||
for {
|
||||
for _, bean := range newBeans {
|
||||
schema, err := TableInfo(bean)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cascadeDeleteTables: failure to fetch schema table for bean %#v: %w", bean, err)
|
||||
}
|
||||
if deduplicateTables.Contains(schema.Name) {
|
||||
continue
|
||||
}
|
||||
deduplicateTables.Add(schema.Name)
|
||||
for _, referencingTable := range referencedTables[schema.Name] {
|
||||
table := tableMap[referencingTable]
|
||||
finalBeans = append(finalBeans, table.bean)
|
||||
nextBeanSet = append(nextBeanSet, table.bean)
|
||||
}
|
||||
}
|
||||
|
||||
if len(nextBeanSet) == 0 {
|
||||
break
|
||||
}
|
||||
newBeans = nextBeanSet
|
||||
nextBeanSet = nextBeanSet[:0] // set len 0, keep allocation for reuse
|
||||
}
|
||||
|
||||
return finalBeans, nil
|
||||
}
|
|
@ -72,14 +72,19 @@ func postgresGetNextResourceIndex(ctx context.Context, tableName string, groupID
|
|||
}
|
||||
|
||||
func mysqlGetNextResourceIndex(ctx context.Context, tableName string, groupID int64) (int64, error) {
|
||||
if _, err := GetEngine(ctx).Exec(fmt.Sprintf("INSERT INTO %s (group_id, max_index) "+
|
||||
"VALUES (?,1) ON DUPLICATE KEY UPDATE max_index = max_index+1",
|
||||
tableName), groupID); err != nil {
|
||||
res, err := GetEngine(ctx).Query(fmt.Sprintf("INSERT INTO %s (group_id, max_index) "+
|
||||
"VALUES (?,1) ON DUPLICATE KEY UPDATE max_index = max_index+1 /*M!100500 RETURNING max_index */",
|
||||
tableName), groupID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return strconv.ParseInt(string(res[0]["max_index"]), 10, 64)
|
||||
}
|
||||
|
||||
var idx int64
|
||||
_, err := GetEngine(ctx).SQL(fmt.Sprintf("SELECT max_index FROM %s WHERE group_id = ?", tableName), groupID).Get(&idx)
|
||||
_, err = GetEngine(ctx).SQL(fmt.Sprintf("SELECT max_index FROM %s WHERE group_id = ?", tableName), groupID).Get(&idx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
|
|
@ -5,39 +5,82 @@ package db
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"forgejo.org/modules/setting"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// Iterate iterate all the Bean object
|
||||
// Iterate iterate all the Bean object. The table being iterated must have a single-column primary key.
|
||||
func Iterate[Bean any](ctx context.Context, cond builder.Cond, f func(ctx context.Context, bean *Bean) error) error {
|
||||
var start int
|
||||
var dummy Bean
|
||||
batchSize := setting.Database.IterateBufferSize
|
||||
sess := GetEngine(ctx)
|
||||
|
||||
table, err := TableInfo(&dummy)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to fetch table info for bean %v: %w", dummy, err)
|
||||
}
|
||||
if len(table.PrimaryKeys) != 1 {
|
||||
return fmt.Errorf("iterate only supported on a table with 1 primary key field, but table %s had %d", table.Name, len(table.PrimaryKeys))
|
||||
}
|
||||
|
||||
pkDbName := table.PrimaryKeys[0]
|
||||
var pkStructFieldName string
|
||||
|
||||
for _, c := range table.Columns() {
|
||||
if c.Name == pkDbName {
|
||||
pkStructFieldName = c.FieldName
|
||||
break
|
||||
}
|
||||
}
|
||||
if pkStructFieldName == "" {
|
||||
return fmt.Errorf("iterate unable to identify struct field for primary key %s", pkDbName)
|
||||
}
|
||||
|
||||
var lastPK any
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
beans := make([]*Bean, 0, batchSize)
|
||||
|
||||
sess := GetEngine(ctx)
|
||||
sess = sess.OrderBy(pkDbName)
|
||||
if cond != nil {
|
||||
sess = sess.Where(cond)
|
||||
}
|
||||
if err := sess.Limit(batchSize, start).Find(&beans); err != nil {
|
||||
if lastPK != nil {
|
||||
sess = sess.Where(builder.Gt{pkDbName: lastPK})
|
||||
}
|
||||
|
||||
if err := sess.Limit(batchSize).Find(&beans); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(beans) == 0 {
|
||||
return nil
|
||||
}
|
||||
start += len(beans)
|
||||
|
||||
for _, bean := range beans {
|
||||
if err := f(ctx, bean); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
lastBean := beans[len(beans)-1]
|
||||
lastPK = extractFieldValue(lastBean, pkStructFieldName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func extractFieldValue(bean any, fieldName string) any {
|
||||
v := reflect.ValueOf(bean)
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
field := v.FieldByName(fieldName)
|
||||
return field.Interface()
|
||||
}
|
||||
|
|
|
@ -5,42 +5,113 @@ package db_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
repo_model "forgejo.org/models/repo"
|
||||
"forgejo.org/models/unittest"
|
||||
"forgejo.org/modules/setting"
|
||||
"forgejo.org/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func TestIterate(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
xe, err := unittest.GetXORMEngine()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, xe.Sync(&repo_model.RepoUnit{}))
|
||||
db.SetLogSQL(t.Context(), true)
|
||||
defer test.MockVariableValue(&setting.Database.IterateBufferSize, 50)()
|
||||
|
||||
cnt, err := db.GetEngine(db.DefaultContext).Count(&repo_model.RepoUnit{})
|
||||
require.NoError(t, err)
|
||||
t.Run("No Modifications", func(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
xe, err := unittest.GetXORMEngine()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, xe.Sync(&repo_model.RepoUnit{}))
|
||||
|
||||
var repoUnitCnt int
|
||||
err = db.Iterate(db.DefaultContext, nil, func(ctx context.Context, repo *repo_model.RepoUnit) error {
|
||||
repoUnitCnt++
|
||||
return nil
|
||||
// Fetch all the repo unit IDs...
|
||||
var remainingRepoIDs []int64
|
||||
db.GetEngine(t.Context()).Table(&repo_model.RepoUnit{}).Cols("id").Find(&remainingRepoIDs)
|
||||
|
||||
// Ensure that every repo unit ID is found when doing iterate:
|
||||
err = db.Iterate(t.Context(), nil, func(ctx context.Context, repo *repo_model.RepoUnit) error {
|
||||
remainingRepoIDs = slices.DeleteFunc(remainingRepoIDs, func(n int64) bool {
|
||||
return repo.ID == n
|
||||
})
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, remainingRepoIDs)
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, cnt, repoUnitCnt)
|
||||
|
||||
err = db.Iterate(db.DefaultContext, nil, func(ctx context.Context, repoUnit *repo_model.RepoUnit) error {
|
||||
has, err := db.ExistByID[repo_model.RepoUnit](ctx, repoUnit.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !has {
|
||||
return db.ErrNotExist{Resource: "repo_unit", ID: repoUnit.ID}
|
||||
}
|
||||
return nil
|
||||
t.Run("Concurrent Delete", func(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
xe, err := unittest.GetXORMEngine()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, xe.Sync(&repo_model.RepoUnit{}))
|
||||
|
||||
// Fetch all the repo unit IDs...
|
||||
var remainingRepoIDs []int64
|
||||
db.GetEngine(t.Context()).Table(&repo_model.RepoUnit{}).Cols("id").Find(&remainingRepoIDs)
|
||||
|
||||
// Ensure that every repo unit ID is found, even if someone else performs a DELETE on the table while we're
|
||||
// iterating. In real-world usage the deleted record may or may not be returned, but the important
|
||||
// subject-under-test is that no *other* record is skipped.
|
||||
didDelete := false
|
||||
err = db.Iterate(t.Context(), nil, func(ctx context.Context, repo *repo_model.RepoUnit) error {
|
||||
// While on page 2 (assuming ID ordering, 50 record buffer size)...
|
||||
if repo.ID == 51 {
|
||||
// Delete a record that would have been on page 1.
|
||||
affected, err := db.GetEngine(t.Context()).ID(25).Delete(&repo_model.RepoUnit{})
|
||||
if err != nil {
|
||||
return err
|
||||
} else if affected != 1 {
|
||||
return fmt.Errorf("expected to delete 1 record, but affected %d records", affected)
|
||||
}
|
||||
didDelete = true
|
||||
}
|
||||
remainingRepoIDs = slices.DeleteFunc(remainingRepoIDs, func(n int64) bool {
|
||||
return repo.ID == n
|
||||
})
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, didDelete, "didDelete")
|
||||
assert.Empty(t, remainingRepoIDs)
|
||||
})
|
||||
|
||||
t.Run("Verify cond applied", func(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
xe, err := unittest.GetXORMEngine()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, xe.Sync(&repo_model.RepoUnit{}))
|
||||
|
||||
// Fetch all the repo unit IDs...
|
||||
var remainingRepoIDs []int64
|
||||
db.GetEngine(t.Context()).Table(&repo_model.RepoUnit{}).Cols("id").Find(&remainingRepoIDs)
|
||||
|
||||
// Remove those that we're not expecting to find based upon `Iterate`'s condition. We'll trim the front few
|
||||
// records and last few records, which will confirm that cond is applied on all pages.
|
||||
remainingRepoIDs = slices.DeleteFunc(remainingRepoIDs, func(n int64) bool {
|
||||
return n <= 15 || n > 1000
|
||||
})
|
||||
err = db.Iterate(t.Context(), builder.Gt{"id": 15}.And(builder.Lt{"id": 1000}), func(ctx context.Context, repo *repo_model.RepoUnit) error {
|
||||
removedRecord := false
|
||||
// Remove the record from remainingRepoIDs, but track to make sure we did actually remove a record
|
||||
remainingRepoIDs = slices.DeleteFunc(remainingRepoIDs, func(n int64) bool {
|
||||
if repo.ID == n {
|
||||
removedRecord = true
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
if !removedRecord {
|
||||
return fmt.Errorf("unable to find record in remainingRepoIDs for repo %d, indicating a cond application failure", repo.ID)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, remainingRepoIDs)
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
|
40
models/db/table_names_test.go
Normal file
40
models/db/table_names_test.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"forgejo.org/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetTableNames(t *testing.T) {
|
||||
t.Run("Simple", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&tables, []any{new(GPGKey)})()
|
||||
|
||||
assert.Equal(t, []string{"gpg_key"}, GetTableNames().Values())
|
||||
})
|
||||
|
||||
t.Run("Multiple tables", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&tables, []any{new(GPGKey), new(User), new(BlockedUser)})()
|
||||
|
||||
tableNames := GetTableNames().Values()
|
||||
slices.Sort(tableNames)
|
||||
|
||||
assert.Equal(t, []string{"forgejo_blocked_user", "gpg_key", "user"}, tableNames)
|
||||
})
|
||||
}
|
||||
|
||||
type GPGKey struct{}
|
||||
|
||||
type User struct{}
|
||||
|
||||
type BlockedUser struct{}
|
||||
|
||||
func (*BlockedUser) TableName() string {
|
||||
return "forgejo_blocked_user"
|
||||
}
|
|
@ -121,6 +121,7 @@ type ErrInvalidCloneAddr struct {
|
|||
IsInvalidPath bool
|
||||
IsProtocolInvalid bool
|
||||
IsPermissionDenied bool
|
||||
HasCredentials bool
|
||||
LocalPath bool
|
||||
}
|
||||
|
||||
|
@ -143,6 +144,9 @@ func (err *ErrInvalidCloneAddr) Error() string {
|
|||
if err.IsURLError {
|
||||
return fmt.Sprintf("migration/cloning from '%s' is not allowed: the provided url is invalid", err.Host)
|
||||
}
|
||||
if err.HasCredentials {
|
||||
return fmt.Sprintf("migration/cloning from '%s' is not allowed: the provided url contains credentials", err.Host)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("migration/cloning from '%s' is not allowed", err.Host)
|
||||
}
|
||||
|
|
21
models/fixtures/ModerationFeatures/abuse_report.yml
Normal file
21
models/fixtures/ModerationFeatures/abuse_report.yml
Normal file
|
@ -0,0 +1,21 @@
|
|||
-
|
||||
id: 1
|
||||
status: 1
|
||||
reporter_id: 2 # @user2
|
||||
content_type: 4 # Comment
|
||||
content_id: 18 # user2/repo2/issues/2#issuecomment-18
|
||||
category: 2 # Spam
|
||||
remarks: The comment I'm reporting is pure SPAM.
|
||||
shadow_copy_id: null
|
||||
created_unix: 1752697980 # 2025-07-16 20:33:00
|
||||
|
||||
-
|
||||
id: 2
|
||||
status: 1 # Open
|
||||
reporter_id: 2 # @user2
|
||||
content_type: 1 # User (users or organizations)
|
||||
content_id: 1002 # @alexsmith
|
||||
category: 2 # Spam
|
||||
remarks: This user just posted a spammy comment on my issue.
|
||||
shadow_copy_id: null
|
||||
created_unix: 1752698010 # 2025-07-16 20:33:30
|
7
models/fixtures/ModerationFeatures/comment.yml
Normal file
7
models/fixtures/ModerationFeatures/comment.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
- # This is a spam comment (abusive content), created for testing moderation functionalities.
|
||||
id: 18
|
||||
type: 0 # Standard comment
|
||||
poster_id: 1002 # @alexsmith
|
||||
issue_id: 7 # user2/repo2#2
|
||||
content: If anyone needs help for promoting their business online using SEO, just contact me (check my profile page).
|
||||
created_unix: 1752697860 # 2025-07-16 20:31:00
|
22
models/fixtures/ModerationFeatures/user.yml
Normal file
22
models/fixtures/ModerationFeatures/user.yml
Normal file
|
@ -0,0 +1,22 @@
|
|||
- # This user is a spammer and will create abusive content (for testing moderation functionalities).
|
||||
id: 1002
|
||||
lower_name: alexsmith
|
||||
name: alexsmith
|
||||
full_name: Alex Smith
|
||||
email: alexsmith@example.org
|
||||
keep_email_private: false
|
||||
passwd: passwdSalt:password
|
||||
passwd_hash_algo: dummy
|
||||
type: 0
|
||||
location: '@master@seo.net'
|
||||
website: http://promote-your-business.biz
|
||||
pronouns: SEO
|
||||
salt: passwdSalt
|
||||
description: I can help you promote your business online using SEO.
|
||||
created_unix: 1752697800 # 2025-07-16 20:30:00
|
||||
is_active: true
|
||||
is_admin: false
|
||||
is_restricted: false
|
||||
avatar: avatar-hash-1002
|
||||
avatar_email: alexsmith@example.org
|
||||
use_custom_avatar: false
|
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