Compare commits
535 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0eb81f9c1a | |||
| 6498459e49 | |||
| 2b82312b4e | |||
| 8808dfc3ce | |||
| 470364e50c | |||
| 69ad436ebb | |||
| 65c5e3d213 | |||
| d40c8e1b85 | |||
| 39c373975e | |||
| b14fcb11f9 | |||
| 60a686ce63 | |||
| 17ac356100 | |||
| 68845abd59 | |||
| ba0fdf3df1 | |||
| ba0b17d9b5 | |||
| 29341b2b9b | |||
| eef72a5b00 | |||
| 530cfc91b1 | |||
| 39249dd0e7 | |||
| aee484780b | |||
| e9ab1fd01d | |||
| 6e78d49e5a | |||
| 627a22ee53 | |||
| 3c5fc21976 | |||
| 23d453a786 | |||
| ef99c7461d | |||
| 658aa524c6 | |||
| 44f6823292 | |||
| 6c06384966 | |||
| d4f2dc33b9 | |||
| 3807dbbb2e | |||
| fd481329a5 | |||
| 04ed2c7ed5 | |||
| c322bfae23 | |||
| 7884e2f141 | |||
| b0acd521e5 | |||
| 9c0e2b48cf | |||
| 1bff46b220 | |||
| 44fd865ee6 | |||
| 4b6df79ae0 | |||
| 1d1482a3dc | |||
| fba9c7eed9 | |||
| 0438ed1b73 | |||
| 6045bf87d9 | |||
| 540e3e129a | |||
| 086c506a10 | |||
| 5bf2f57f53 | |||
| d3bca854e5 | |||
| 203d090123 | |||
| ce344910b2 | |||
| 2fe549482d | |||
| 7aa930227e | |||
| 524b38f494 | |||
| d4824dc05b | |||
| 33b2ae2d4d | |||
| 69f1a77459 | |||
| 0cb4ece382 | |||
| 8af880073f | |||
| 8ee7e9fcde | |||
| 7bd66ae74c | |||
| d10c6ece9b | |||
| aeda83c664 | |||
| 0915b05b0f | |||
| e0698e73bc | |||
| 6f38f89765 | |||
| 25257b9e31 | |||
| a5bdc89faa | |||
| 0ecba968a0 | |||
| bed7adcf1c | |||
| df59b5f6d5 | |||
| 5786f0dfc4 | |||
| 1b0c86847d | |||
| 2de87d8ff4 | |||
| 789d3c9aa8 | |||
| b241acf650 | |||
| 173dfd0f26 | |||
| 700cc77d0b | |||
| c268970505 | |||
| 1f09979c19 | |||
| 7786dee9d4 | |||
| 7959864835 | |||
| 20fef17dac | |||
| 4630327b02 | |||
| 1ad277cd73 | |||
| 4624385501 | |||
| e084c7f4b4 | |||
| 9721728b45 | |||
| 38ed8eaeea | |||
| 1608b5c4b9 | |||
| eb15990510 | |||
| 6654d7605d | |||
| 411ba858f5 | |||
| a7e39fa992 | |||
| bd899bcbb1 | |||
| 7c014dc4da | |||
| d0ca5eff28 | |||
| 4c4d2ac956 | |||
| 47ddd6a277 | |||
| e946d49bf3 | |||
| c3eb57f124 | |||
| c0662ab118 | |||
| fce8389296 | |||
| eeaf9f036b | |||
| 9957fe56ca | |||
| dcc4a49cdb | |||
| f15a0ed7e4 | |||
| 0e0ce94353 | |||
| 455f12e21b | |||
| 360d0b1b1f | |||
| e8ce4ae60b | |||
| 775766bc64 | |||
| cc61032697 | |||
| 6d9f0d9727 | |||
| 00d862f737 | |||
| 233144e33e | |||
| 38beba655b | |||
| dd6fc4b69c | |||
| 3b79d57d45 | |||
| 871cb495b1 | |||
| d66ca8db83 | |||
| 01f6722ccc | |||
| 7019f08f74 | |||
| aa6c3fc4ed | |||
| b577f8f4f4 | |||
| 5ffe68499d | |||
| 65456962b4 | |||
| 020b160403 | |||
| c633024a9c | |||
| 8ffdbff72a | |||
| d609b8db8c | |||
| bf35e5510d | |||
| 0cc7297f23 | |||
| 9dc85cfc2d | |||
| 6bc0cb5bc8 | |||
| 1fb97eeeeb | |||
| 1032ae4268 | |||
| af1c6178ef | |||
| 0f23219ee4 | |||
| 1e1441f8bd | |||
| bc475c91f6 | |||
| 25268d7dd7 | |||
| 3c55a3baca | |||
| c7193abc0c | |||
| e6a4dfccf0 | |||
| a5c805b0f6 | |||
| 46ce0a7e32 | |||
| b7f9743ade | |||
| c236c4e018 | |||
| b79b48b760 | |||
| 22a529dfd8 | |||
| 09dc64eef0 | |||
| d541a07263 | |||
| 2eee4bbf1c | |||
| 2dea95a431 | |||
| f69212859a | |||
| 3828411311 | |||
| 447a45ec15 | |||
| f19fd9683f | |||
| 039ab896cc | |||
| 65660863d6 | |||
| 198ae92579 | |||
| bc4eae2e0f | |||
| 9f549fb801 | |||
| a97755417a | |||
| 26fde4a50e | |||
| 79377399dd | |||
| b6c8b9144e | |||
| 09bf669937 | |||
| 5996661b6f | |||
| 284a1a39c3 | |||
| b64ed1972a | |||
| 71e0af4196 | |||
| 435766841f | |||
| 7d475d6af2 | |||
| 93dc58b106 | |||
| dcef668e9d | |||
| 3842bee2bc | |||
| 1726a2f1fb | |||
| d445496691 | |||
| 4ef4aeb04a | |||
| d1b964235a | |||
| 4ec61ec260 | |||
| b5defc2a4a | |||
| d77713dd77 | |||
| 299ec57b52 | |||
| 34e2a460f9 | |||
| 7921e007f1 | |||
| c21df45434 | |||
| a45be34139 | |||
| d97955394f | |||
| e279f8dbe8 | |||
| 592a71968f | |||
| d55b79a9ff | |||
| 3b57aaff10 | |||
| 64e1e37e20 | |||
| a847129f9c | |||
| 30e16cccc1 | |||
| b74cf800ef | |||
| 90f612f211 | |||
| 49fe3cf6eb | |||
| 13352e7213 | |||
| 07827bcc2e | |||
| 8509932b41 | |||
| 8ad1b8a110 | |||
| be5c2d35a5 | |||
| 83244e8361 | |||
| c572fcfe04 | |||
| c233878484 | |||
| 39150c6968 | |||
| 417bda1735 | |||
| f09aadf60c | |||
| fa54fe1ffc | |||
| 49fb7bb9a4 | |||
| 5dd98c04d8 | |||
| ea3957597c | |||
| 45e08616ac | |||
| 4dafcc5429 | |||
| 3159d53322 | |||
| fff64e6e7c | |||
| 1d4340a142 | |||
| 60670d066b | |||
| e8b2a485fc | |||
| cd496f159d | |||
| afbff02d81 | |||
| 8355b39ad4 | |||
| c2c9e053ff | |||
| 451b3022bd | |||
| 2153d7c916 | |||
| 63c801d595 | |||
| 0270be743f | |||
| dbf70a7def | |||
| 43b5a54ffa | |||
| b182855fc5 | |||
| ca6c8c958c | |||
| e9efbbc93b | |||
| 2482a3726e | |||
| 2f381dc16c | |||
| d2cdd9b1d6 | |||
| 6e0236d433 | |||
| ce83900967 | |||
| ee6405f4fd | |||
| 738117248d | |||
| d2d652e5b7 | |||
| 0166a6d02a | |||
| 0f543903fb | |||
| ec02fb9cf8 | |||
| f639940608 | |||
| 482ad13ff1 | |||
| 8b5abc6b1e | |||
| 7a06e44e24 | |||
| a804ebcf09 | |||
| 3ec28c7f6a | |||
| 6f49a9efbe | |||
| a2b951ea43 | |||
| 8b83c415cf | |||
| f36125926a | |||
| 0902e726ed | |||
| b4907ac75c | |||
| db4ab6c82e | |||
| 7b1f539f05 | |||
| 6c368f81a7 | |||
| 1ceecaa3de | |||
| 9b5ac9246b | |||
| 73f301102a | |||
| ee57494073 | |||
| 5fcdc7fff0 | |||
| b2d6ce9c34 | |||
| 9e78f1c367 | |||
| 0d702937fb | |||
| d891985aac | |||
| f372bcb998 | |||
| 8d942b2ebf | |||
| 01bb4a34ca | |||
| 3f97efb934 | |||
| a60f1b7fc8 | |||
| 026bbce088 | |||
| b6bfe109d9 | |||
| 69fe39374d | |||
| 6e3f9e2cdf | |||
| 22fa3d16bf | |||
| 6d2ccb76eb | |||
| a66f88e0bf | |||
| 58782a3920 | |||
| 53b7e378d1 | |||
| ad78bb7c27 | |||
| ff016ed888 | |||
| 33fd9c5620 | |||
| 6f1b83eb74 | |||
| d300cde639 | |||
| 05f1ac1a12 | |||
| 5d84da9ae8 | |||
| a8a01ed978 | |||
| 871883ef11 | |||
| b441b0a350 | |||
| fcbc28735e | |||
| 5c3a36a225 | |||
| 8936883a40 | |||
| c3af273401 | |||
| 77f3a522eb | |||
| de2a2c9013 | |||
| a78e610040 | |||
| 707eec0098 | |||
| 8c89a33ecf | |||
| 6f82c6af7f | |||
| c74a0d27e4 | |||
| 906682aa14 | |||
| 54fe7b82c9 | |||
| 3d0e2971cb | |||
| eaa9a07085 | |||
| 3566874dae | |||
| a0949e03c1 | |||
| 615867b796 | |||
| be58ffc631 | |||
| d8b702169b | |||
| 5a692ad9cb | |||
| d68ae34834 | |||
| 2b9a9b118d | |||
| 27950bf5a7 | |||
| 8e2e65f74a | |||
| f707415bfa | |||
| 54b28dbde7 | |||
| eb55ffc0ed | |||
| 70ee382ef0 | |||
| 6b31e9b619 | |||
| fd82cee452 | |||
| de8094d77f | |||
| 96eb394a17 | |||
| ddbd592c2f | |||
| 869586c582 | |||
| 02ac392fba | |||
| a26d0bff90 | |||
| c0fccaa759 | |||
| fabcc7ca03 | |||
| 31945210a0 | |||
| d2958254a0 | |||
| ef6a7dcfcf | |||
| c5eb8df8a2 | |||
| 32955397f0 | |||
| 71105345ff | |||
| e50e8ed851 | |||
| 3396440926 | |||
| e2e1073f10 | |||
| c2eb430d3a | |||
| 809e9d2bf3 | |||
| 6a509da96e | |||
| 601c6eb1a0 | |||
| e9f82b0ee3 | |||
| 3e69d6adbe | |||
| c9b9e376fb | |||
| 2200ed7499 | |||
| 19f01691d5 | |||
| 630258410d | |||
| cf48aa0188 | |||
| a39af1a829 | |||
| b093c2c43c | |||
| ebc058f682 | |||
| 35dfc6b9e1 | |||
| 97211bf0c5 | |||
| 45ffe5aa6a | |||
| a5fd8e7e86 | |||
| ee803ad05d | |||
| 6ba907d89c | |||
| 5e8004a515 | |||
| c471af4266 | |||
| 62300eab3b | |||
| 762154cbd7 | |||
| a90d5dd131 | |||
| 89a49de0fd | |||
| 198ef500d2 | |||
| dd17521808 | |||
| f26f71f1b2 | |||
| c4c50bee7f | |||
| 7016f7b37f | |||
| 0385e4783e | |||
| a2a5ef8d0e | |||
| 134e86c78c | |||
| f049668c21 | |||
| 3d838ef96a | |||
| 6b8dd90dc7 | |||
| abcfa53040 | |||
| 31cee60cc7 | |||
| deb31d3f30 | |||
| 02b1b8a549 | |||
| 48cea1fb79 | |||
| 1721c235a7 | |||
| 81692ceafa | |||
| 52d6baf5a8 | |||
| 2b2ec6af85 | |||
| 184ce17167 | |||
| d57d06335d | |||
| 9262803621 | |||
| 9e031eb3df | |||
| 18762c7748 | |||
| 0ba862cb97 | |||
| deec2b0929 | |||
| fedc9dc993 | |||
| 8bf51da65f | |||
| c8e67799b2 | |||
| 15b23f037d | |||
| 596a8868d7 | |||
| c6ffbfe0d2 | |||
| 4952a48b4e | |||
| 89d358d8a7 | |||
| 6da8027446 | |||
| 331450b17a | |||
| b45be5b20d | |||
| 45b4fffae4 | |||
| 55c9b936cb | |||
| 29c510ef94 | |||
| 068b59aa97 | |||
| 2f42c8cf72 | |||
| 2671b997f2 | |||
| 99cd4f6b22 | |||
| b3ed4cde9a | |||
| 712b3a54b5 | |||
| ebf30ac4db | |||
| f2a7f6c999 | |||
| 9e0f692f28 | |||
| d19f653479 | |||
| 3e0b99da3b | |||
| 9b9fb95559 | |||
| 24b60f8ff9 | |||
| 683f8c0014 | |||
| 0277e3ebaa | |||
| c5c9713ed4 | |||
| 6826321570 | |||
| 1483291a87 | |||
| de99b1fbbf | |||
| 58a1330078 | |||
| aa0707c679 | |||
| 3816210c05 | |||
| 0817a405af | |||
| 3b2fd9791c | |||
| 899ede1d55 | |||
| aedf4e84f5 | |||
| 1af16679c3 | |||
| 85192c2e9f | |||
| 7947851e57 | |||
| 12d83cbfa3 | |||
| 8e85454a50 | |||
| 83bdfc2a57 | |||
| 8cfcef32c6 | |||
| 9894ebb79c | |||
| 32fdfb0bd6 | |||
| 4695110d5f | |||
| df8aa2f804 | |||
| f52b6f3315 | |||
| c69cbb75bf | |||
| f1644fc5e2 | |||
| 732e23258e | |||
| b4f48a64fc | |||
| c489db447d | |||
| 38d337c94a | |||
| aee6628bf5 | |||
| 5495b5d126 | |||
| caff989f34 | |||
| 3db3127655 | |||
| 63db5972a1 | |||
| f94b476c45 | |||
| 12733d3624 | |||
| 85c09b8f45 | |||
| 3f3bebda0d | |||
| 8068d608d1 | |||
| b6ea666fd4 | |||
| ca44b5fca8 | |||
| 1d25bb22f4 | |||
| 019d85039c | |||
| aba87285f0 | |||
| f6960096f3 | |||
| 5a3d8d3224 | |||
| ee253afb86 | |||
| 6f761219b2 | |||
| a17d5ebe16 | |||
| 99cd709bd6 | |||
| b31eef2828 | |||
| 284298f2a9 | |||
| 2f5b5a9e9c | |||
| 6ed861589a | |||
| 30be22f30f | |||
| c98134033a | |||
| 16bdae53c8 | |||
| 0bc2a2836f | |||
| ea6280da75 | |||
| f247d7d4e5 | |||
| af31b9d433 | |||
| 0824610e39 | |||
| 98202110be | |||
| cf3f8e807a | |||
| d5831b9385 | |||
| 0161f3019b | |||
| e43422b042 | |||
| a9108ab6aa | |||
| 279bf84066 | |||
| 18064f772d | |||
| eb334e3738 | |||
| dc974715e9 | |||
| 2bfaa33347 | |||
| b1bfca39f1 | |||
| 4a2bba9aed | |||
| 82bfde2a37 | |||
| 2644bb8490 | |||
| 17f62bfec5 | |||
| 893df6b265 | |||
| b9961e193d | |||
| 699eb41e7c | |||
| 9327b1808e | |||
| 84d5c99e64 | |||
| b55528b1a2 | |||
| 0593b58ff7 | |||
| 37396fff13 | |||
| 6bcb666a9d | |||
| 6eae04241d | |||
| fa8f7f15ef | |||
| 80585adab4 | |||
| 47fdf3e284 | |||
| 355aafd1f9 | |||
| c2fa157731 | |||
| 8fcbdf05b0 | |||
| ba9258c478 | |||
| d913fae237 | |||
| 09c2677b21 | |||
| 16d7817338 | |||
| 681c4074e5 | |||
| 45c80bfec1 | |||
| c10a5b908a | |||
| 980a8995bc | |||
| 04fb6f1c0b | |||
| 0914a44a9b | |||
| 6f9fa55785 | |||
| 8bf3c8b79d | |||
| dfd495f823 | |||
| 714f4207d9 | |||
| d600968aaf | |||
| fc178e3203 | |||
| 03205d94da |
+1
-4
@@ -37,10 +37,7 @@ groups:
|
|||||||
name: BUGFIXES
|
name: BUGFIXES
|
||||||
labels:
|
labels:
|
||||||
- type/bug
|
- type/bug
|
||||||
-
|
|
||||||
name: API
|
|
||||||
labels:
|
|
||||||
- modifies/api
|
|
||||||
-
|
-
|
||||||
name: TESTING
|
name: TESTING
|
||||||
labels:
|
labels:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Gitea DevContainer",
|
"name": "Gitea DevContainer",
|
||||||
"image": "mcr.microsoft.com/devcontainers/go:1.25-trixie",
|
"image": "mcr.microsoft.com/devcontainers/go:1.26-trixie",
|
||||||
"containerEnv": {
|
"containerEnv": {
|
||||||
// override "local" from packaged version
|
// override "local" from packaged version
|
||||||
"GOTOOLCHAIN": "auto"
|
"GOTOOLCHAIN": "auto"
|
||||||
|
|||||||
@@ -40,9 +40,7 @@ cpu.out
|
|||||||
*.log
|
*.log
|
||||||
|
|
||||||
/gitea
|
/gitea
|
||||||
/gitea-vet
|
|
||||||
/debug
|
/debug
|
||||||
/integrations.test
|
|
||||||
|
|
||||||
/bin
|
/bin
|
||||||
/dist
|
/dist
|
||||||
@@ -54,12 +52,6 @@ cpu.out
|
|||||||
/indexers
|
/indexers
|
||||||
/log
|
/log
|
||||||
/tests/integration/gitea-integration-*
|
/tests/integration/gitea-integration-*
|
||||||
/tests/integration/indexers-*
|
|
||||||
/tests/e2e/gitea-e2e-*
|
|
||||||
/tests/e2e/indexers-*
|
|
||||||
/tests/e2e/reports
|
|
||||||
/tests/e2e/test-artifacts
|
|
||||||
/tests/e2e/test-snapshots
|
|
||||||
/tests/*.ini
|
/tests/*.ini
|
||||||
/node_modules
|
/node_modules
|
||||||
/yarn.lock
|
/yarn.lock
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ indent_style = tab
|
|||||||
[templates/custom/*.tmpl]
|
[templates/custom/*.tmpl]
|
||||||
insert_final_newline = false
|
insert_final_newline = false
|
||||||
|
|
||||||
[templates/swagger/v1_json.tmpl]
|
[templates/swagger/*_json.tmpl]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
insert_final_newline = false
|
insert_final_newline = false
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
name: docker-dryrun
|
||||||
|
description: Composite action that performs the container build steps for a single platform.
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
platform:
|
||||||
|
description: "The target platform: linux/amd64, linux/arm64, linux/riscv64."
|
||||||
|
required: true
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
||||||
|
- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||||
|
- name: Build regular image
|
||||||
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: ${{ inputs.platform }}
|
||||||
|
push: false
|
||||||
|
file: Dockerfile
|
||||||
|
cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful
|
||||||
|
- name: Build rootless image
|
||||||
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: ${{ inputs.platform }}
|
||||||
|
push: false
|
||||||
|
file: Dockerfile.rootless
|
||||||
|
cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootless
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
name: go-caches
|
||||||
|
description: Restore and save go module, build, and golangci-lint caches
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
cache-name:
|
||||||
|
description: Short identifier used in the per-caller build cache key
|
||||||
|
required: true
|
||||||
|
build-cache:
|
||||||
|
description: Whether to include ~/.cache/go-build
|
||||||
|
default: "true"
|
||||||
|
build-cache-rotate:
|
||||||
|
description: Whether to rotate the build cache key per run so Go's test result cache can accumulate across runs
|
||||||
|
default: "false"
|
||||||
|
lint-cache:
|
||||||
|
description: Whether to include ~/.cache/golangci-lint
|
||||||
|
default: "false"
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
|
with:
|
||||||
|
path: ~/go/pkg/mod
|
||||||
|
key: gomod-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('go.sum') }}
|
||||||
|
restore-keys: gomod-${{ runner.os }}-${{ runner.arch }}
|
||||||
|
- if: ${{ inputs.build-cache == 'true' && inputs.build-cache-rotate == 'true' }}
|
||||||
|
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
|
with:
|
||||||
|
path: ~/.cache/go-build
|
||||||
|
key: gobuild-${{ runner.os }}-${{ runner.arch }}-${{ inputs.cache-name }}-${{ hashFiles('go.sum') }}-${{ github.run_id }}
|
||||||
|
restore-keys: |
|
||||||
|
gobuild-${{ runner.os }}-${{ runner.arch }}-${{ inputs.cache-name }}-${{ hashFiles('go.sum') }}
|
||||||
|
gobuild-${{ runner.os }}-${{ runner.arch }}-${{ inputs.cache-name }}
|
||||||
|
gobuild-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('go.sum') }}
|
||||||
|
gobuild-${{ runner.os }}-${{ runner.arch }}
|
||||||
|
- if: ${{ inputs.build-cache == 'true' && inputs.build-cache-rotate != 'true' }}
|
||||||
|
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
|
with:
|
||||||
|
path: ~/.cache/go-build
|
||||||
|
key: gobuild-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('go.sum') }}
|
||||||
|
restore-keys: gobuild-${{ runner.os }}-${{ runner.arch }}
|
||||||
|
- if: ${{ inputs.lint-cache == 'true' }}
|
||||||
|
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
|
with:
|
||||||
|
path: ~/.cache/golangci-lint
|
||||||
|
key: golangci-${{ runner.os }}-${{ runner.arch }}-${{ inputs.cache-name }}-${{ hashFiles('go.sum', '.golangci.yml') }}
|
||||||
|
restore-keys: golangci-${{ runner.os }}-${{ runner.arch }}-${{ inputs.cache-name }}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
version: 2
|
|
||||||
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: github-actions
|
|
||||||
labels: [modifies/dependencies]
|
|
||||||
directory: /
|
|
||||||
schedule:
|
|
||||||
interval: daily
|
|
||||||
cooldown:
|
|
||||||
default-days: 5
|
|
||||||
@@ -1,80 +1,3 @@
|
|||||||
modifies/docs:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- "**/*.md"
|
|
||||||
- "docs/**"
|
|
||||||
|
|
||||||
modifies/templates:
|
|
||||||
- changed-files:
|
|
||||||
- all-globs-to-any-file:
|
|
||||||
- "templates/**"
|
|
||||||
- "!templates/swagger/v1_json.tmpl"
|
|
||||||
|
|
||||||
modifies/api:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- "routers/api/**"
|
|
||||||
- "templates/swagger/v1_json.tmpl"
|
|
||||||
|
|
||||||
modifies/cli:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- "cmd/**"
|
|
||||||
|
|
||||||
modifies/translation:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- "options/locale/*.ini"
|
|
||||||
|
|
||||||
modifies/migrations:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- "models/migrations/**"
|
|
||||||
|
|
||||||
modifies/internal:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- ".air.toml"
|
|
||||||
- "Makefile"
|
|
||||||
- "Dockerfile"
|
|
||||||
- "Dockerfile.rootless"
|
|
||||||
- ".dockerignore"
|
|
||||||
- "docker/**"
|
|
||||||
- ".editorconfig"
|
|
||||||
- ".eslintrc.cjs"
|
|
||||||
- ".golangci.yml"
|
|
||||||
- ".markdownlint.yaml"
|
|
||||||
- ".spectral.yaml"
|
|
||||||
- "stylelint.config.*"
|
|
||||||
- ".yamllint.yaml"
|
|
||||||
- ".github/**"
|
|
||||||
- ".gitea/**"
|
|
||||||
- ".devcontainer/**"
|
|
||||||
- "build/**"
|
|
||||||
- "contrib/**"
|
|
||||||
|
|
||||||
modifies/dependencies:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- "package.json"
|
|
||||||
- "pnpm-lock.yaml"
|
|
||||||
- "pyproject.toml"
|
|
||||||
- "uv.lock"
|
|
||||||
- "go.mod"
|
|
||||||
- "go.sum"
|
|
||||||
|
|
||||||
modifies/go:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- "**/*.go"
|
|
||||||
|
|
||||||
modifies/frontend:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- "*.js"
|
|
||||||
- "*.ts"
|
|
||||||
- "web_src/**"
|
|
||||||
|
|
||||||
docs-update-needed:
|
docs-update-needed:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
<!-- start tips -->
|
<!-- start tips -->
|
||||||
Please check the following:
|
Please check the following:
|
||||||
1. Make sure you are targeting the `main` branch, pull requests on release branches are only allowed for backports.
|
1. Make sure you are targeting the `main` branch, pull requests on release branches are only allowed for backports.
|
||||||
2. Make sure you have read contributing guidelines: https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md .
|
2. Use a Conventional Commits PR title, for example `fix(repo): handle empty branch names`.
|
||||||
3. For documentations contribution, please go to https://gitea.com/gitea/docs
|
3. Make sure you have read contributing guidelines: https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md .
|
||||||
4. Describe what your pull request does and which issue you're targeting (if any).
|
4. For documentations contribution, please go to https://gitea.com/gitea/docs
|
||||||
5. It is recommended to enable "Allow edits by maintainers", so maintainers can help more easily.
|
5. Describe what your pull request does and which issue you're targeting (if any).
|
||||||
6. Your input here will be included in the commit message when this PR has been merged. If you don't want some content to be included, please separate them with a line like `---`.
|
6. It is recommended to enable "Allow edits by maintainers", so maintainers can help more easily.
|
||||||
7. Delete all these tips before posting.
|
7. Your input here will be included in the commit message when this PR has been merged. If you don't want some content to be included, please separate them with a line like `---`.
|
||||||
|
8. Delete all these tips before posting.
|
||||||
<!-- end tips -->
|
<!-- end tips -->
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# Populates the go module, build, and golangci-lint caches under the default
|
||||||
|
# branch's cache scope so that PR runs have a warm fallback to restore from.
|
||||||
|
#
|
||||||
|
# GitHub Actions caches are scoped per ref: a PR run can only write to its own
|
||||||
|
# branch's scope, but can read from the base branch's scope as a fallback.
|
||||||
|
# PRs therefore cannot seed main's scope themselves. Running the same cache
|
||||||
|
# steps on push-to-main is the only opportunity to populate that fallback
|
||||||
|
# scope so fresh PR branches start with a useful cache on first run.
|
||||||
|
|
||||||
|
# A PR job's exact key lives in its own PR-scope (empty on first run, filled
|
||||||
|
# by later runs of the same PR); on miss, actions/cache's restore-keys fall
|
||||||
|
# back to prefix matches against entries this seeder saves in main's scope.
|
||||||
|
|
||||||
|
name: cache-seeder
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- "go.sum"
|
||||||
|
- ".golangci.yml"
|
||||||
|
- ".github/actions/go-cache/action.yml"
|
||||||
|
- ".github/workflows/cache-seeder.yml"
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: cache-seeder
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
gobuild:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
check-latest: true
|
||||||
|
cache: false
|
||||||
|
- uses: ./.github/actions/go-cache
|
||||||
|
with:
|
||||||
|
cache-name: seed
|
||||||
|
- run: make deps-backend
|
||||||
|
- run: TAGS="bindata" make backend
|
||||||
|
- run: TAGS="bindata gogit" GOEXPERIMENT="" make backend
|
||||||
|
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- { job: lint-backend, tags: "bindata", target: "lint-backend" }
|
||||||
|
- { job: lint-go-windows, tags: "bindata", target: "lint-go-windows" }
|
||||||
|
- { job: lint-go-gogit, tags: "bindata gogit", target: "lint-go" }
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
check-latest: true
|
||||||
|
cache: false
|
||||||
|
- uses: ./.github/actions/go-cache
|
||||||
|
with:
|
||||||
|
cache-name: ${{ matrix.job }}
|
||||||
|
lint-cache: "true"
|
||||||
|
- run: make deps-backend deps-tools
|
||||||
|
- run: make ${{ matrix.target }}
|
||||||
|
env:
|
||||||
|
TAGS: ${{ matrix.tags }}
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
name: cron-flake-updater
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * 0' # runs weekly on Sunday at 00:00
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
nix-flake-update:
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
issues: write
|
|
||||||
pull-requests: write
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
- uses: DeterminateSystems/determinate-nix-action@v3
|
|
||||||
- uses: DeterminateSystems/update-flake-lock@main
|
|
||||||
with:
|
|
||||||
pr-title: "Update Nix flake"
|
|
||||||
pr-labels: |
|
|
||||||
dependencies
|
|
||||||
@@ -12,15 +12,15 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- run: make generate-gitignore
|
- run: make generate-gitignore
|
||||||
timeout-minutes: 40
|
timeout-minutes: 40
|
||||||
- name: push translations to repo
|
- name: push translations to repo
|
||||||
uses: appleboy/git-push-action@v1.2.0
|
uses: appleboy/git-push-action@3b2c8661652360dbf1afe1b319a49dbb739c39f1 # v1.2.0
|
||||||
with:
|
with:
|
||||||
author_email: "teabot@gitea.io"
|
author_email: "teabot@gitea.io"
|
||||||
author_name: GiteaBot
|
author_name: GiteaBot
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
name: cron-renovate
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "23 * * * *" # hourly at :23
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: cron-renovate
|
||||||
|
|
||||||
|
env:
|
||||||
|
RENOVATE_VERSION: 43.141.5 # renovate: datasource=docker depName=ghcr.io/renovatebot/renovate
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cron-renovate:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository == 'go-gitea/gitea' # prevent running on forks
|
||||||
|
timeout-minutes: 30
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
- uses: renovatebot/github-action@f66d8679fcfcfa051abde6e7a623007173bf5164 # v46.1.12
|
||||||
|
with:
|
||||||
|
renovate-version: ${{ env.RENOVATE_VERSION }}
|
||||||
|
configurationFile: renovate.json5
|
||||||
|
token: ${{ secrets.RENOVATE_TOKEN }}
|
||||||
|
env:
|
||||||
|
RENOVATE_BINARY_SOURCE: install # auto-install go/node toolchains needed by post-upgrade tasks.
|
||||||
|
RENOVATE_ALLOWED_POST_UPGRADE_COMMANDS: '["^make (tidy|svg nolyfill)$"]'
|
||||||
|
RENOVATE_REPOSITORIES: '["go-gitea/gitea"]'
|
||||||
@@ -12,8 +12,8 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: crowdin/github-action@v2
|
- uses: crowdin/github-action@8868a33591d21088edfc398968173a3b98d51706 # v2.16.2
|
||||||
with:
|
with:
|
||||||
upload_sources: true
|
upload_sources: true
|
||||||
upload_translations: false
|
upload_translations: false
|
||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
- name: update locales
|
- name: update locales
|
||||||
run: ./build/update-locales.sh
|
run: ./build/update-locales.sh
|
||||||
- name: push translations to repo
|
- name: push translations to repo
|
||||||
uses: appleboy/git-push-action@v1.2.0
|
uses: appleboy/git-push-action@3b2c8661652360dbf1afe1b319a49dbb739c39f1 # v1.2.0
|
||||||
with:
|
with:
|
||||||
author_email: "teabot@gitea.io"
|
author_email: "teabot@gitea.io"
|
||||||
author_name: GiteaBot
|
author_name: GiteaBot
|
||||||
|
|||||||
@@ -15,19 +15,24 @@ on:
|
|||||||
value: ${{ jobs.detect.outputs.templates }}
|
value: ${{ jobs.detect.outputs.templates }}
|
||||||
docker:
|
docker:
|
||||||
value: ${{ jobs.detect.outputs.docker }}
|
value: ${{ jobs.detect.outputs.docker }}
|
||||||
|
dockerfile:
|
||||||
|
value: ${{ jobs.detect.outputs.dockerfile }}
|
||||||
swagger:
|
swagger:
|
||||||
value: ${{ jobs.detect.outputs.swagger }}
|
value: ${{ jobs.detect.outputs.swagger }}
|
||||||
yaml:
|
yaml:
|
||||||
value: ${{ jobs.detect.outputs.yaml }}
|
value: ${{ jobs.detect.outputs.yaml }}
|
||||||
json:
|
json:
|
||||||
value: ${{ jobs.detect.outputs.json }}
|
value: ${{ jobs.detect.outputs.json }}
|
||||||
|
e2e:
|
||||||
|
value: ${{ jobs.detect.outputs.e2e }}
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
detect:
|
detect:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 3
|
timeout-minutes: 3
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
outputs:
|
outputs:
|
||||||
backend: ${{ steps.changes.outputs.backend }}
|
backend: ${{ steps.changes.outputs.backend }}
|
||||||
frontend: ${{ steps.changes.outputs.frontend }}
|
frontend: ${{ steps.changes.outputs.frontend }}
|
||||||
@@ -35,12 +40,14 @@ jobs:
|
|||||||
actions: ${{ steps.changes.outputs.actions }}
|
actions: ${{ steps.changes.outputs.actions }}
|
||||||
templates: ${{ steps.changes.outputs.templates }}
|
templates: ${{ steps.changes.outputs.templates }}
|
||||||
docker: ${{ steps.changes.outputs.docker }}
|
docker: ${{ steps.changes.outputs.docker }}
|
||||||
|
dockerfile: ${{ steps.changes.outputs.dockerfile }}
|
||||||
swagger: ${{ steps.changes.outputs.swagger }}
|
swagger: ${{ steps.changes.outputs.swagger }}
|
||||||
yaml: ${{ steps.changes.outputs.yaml }}
|
yaml: ${{ steps.changes.outputs.yaml }}
|
||||||
json: ${{ steps.changes.outputs.json }}
|
json: ${{ steps.changes.outputs.json }}
|
||||||
|
e2e: ${{ steps.changes.outputs.e2e }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: dorny/paths-filter@v4
|
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
|
||||||
id: changes
|
id: changes
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
@@ -91,6 +98,10 @@ jobs:
|
|||||||
- "docker/**"
|
- "docker/**"
|
||||||
- "Makefile"
|
- "Makefile"
|
||||||
|
|
||||||
|
dockerfile:
|
||||||
|
- "Dockerfile"
|
||||||
|
- "Dockerfile.rootless"
|
||||||
|
|
||||||
swagger:
|
swagger:
|
||||||
- "templates/swagger/v1_json.tmpl"
|
- "templates/swagger/v1_json.tmpl"
|
||||||
- "templates/swagger/v1_input.json"
|
- "templates/swagger/v1_input.json"
|
||||||
@@ -107,3 +118,8 @@ jobs:
|
|||||||
|
|
||||||
json:
|
json:
|
||||||
- "**/*.json"
|
- "**/*.json"
|
||||||
|
|
||||||
|
e2e:
|
||||||
|
- "tests/e2e/**"
|
||||||
|
- "tools/test-e2e.sh"
|
||||||
|
- "playwright.config.ts"
|
||||||
|
|||||||
@@ -7,124 +7,82 @@ concurrency:
|
|||||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
files-changed:
|
files-changed:
|
||||||
uses: ./.github/workflows/files-changed.yml
|
uses: ./.github/workflows/files-changed.yml
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
lint-backend:
|
lint-backend:
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
cache: false
|
||||||
|
- uses: ./.github/actions/go-cache
|
||||||
|
with:
|
||||||
|
cache-name: lint-backend
|
||||||
|
lint-cache: "true"
|
||||||
- run: make deps-backend deps-tools
|
- run: make deps-backend deps-tools
|
||||||
- run: make lint-backend
|
- run: make lint-backend
|
||||||
env:
|
env:
|
||||||
TAGS: bindata sqlite sqlite_unlock_notify
|
TAGS: bindata
|
||||||
|
|
||||||
lint-templates:
|
lint-on-demand:
|
||||||
if: needs.files-changed.outputs.templates == 'true'
|
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: astral-sh/setup-uv@v8.0.0
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
- run: uv python install 3.14
|
|
||||||
- uses: pnpm/action-setup@v5
|
|
||||||
- uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version: 24
|
|
||||||
cache: pnpm
|
|
||||||
cache-dependency-path: pnpm-lock.yaml
|
|
||||||
- run: make deps-py
|
|
||||||
- run: make deps-frontend
|
|
||||||
- run: make lint-templates
|
|
||||||
|
|
||||||
lint-yaml:
|
|
||||||
if: needs.files-changed.outputs.yaml == 'true'
|
|
||||||
needs: files-changed
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
- uses: astral-sh/setup-uv@v8.0.0
|
|
||||||
- run: uv python install 3.14
|
|
||||||
- run: make deps-py
|
|
||||||
- run: make lint-yaml
|
|
||||||
|
|
||||||
lint-json:
|
|
||||||
if: needs.files-changed.outputs.json == 'true'
|
|
||||||
needs: files-changed
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
- uses: pnpm/action-setup@v5
|
|
||||||
- uses: actions/setup-node@v5
|
|
||||||
with:
|
|
||||||
node-version: 24
|
|
||||||
- run: make deps-frontend
|
|
||||||
- run: make lint-json
|
|
||||||
|
|
||||||
lint-swagger:
|
|
||||||
if: needs.files-changed.outputs.swagger == 'true'
|
|
||||||
needs: files-changed
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
- uses: pnpm/action-setup@v5
|
|
||||||
- uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version: 24
|
|
||||||
cache: pnpm
|
|
||||||
cache-dependency-path: pnpm-lock.yaml
|
|
||||||
- run: make deps-frontend
|
|
||||||
- run: make lint-swagger
|
|
||||||
|
|
||||||
lint-spell:
|
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.templates == 'true'
|
|
||||||
needs: files-changed
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
- uses: actions/setup-go@v6
|
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
cache: false
|
||||||
|
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||||
|
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
cache: pnpm
|
||||||
|
cache-dependency-path: pnpm-lock.yaml
|
||||||
|
|
||||||
- run: make lint-spell
|
- run: make lint-spell
|
||||||
|
|
||||||
|
- if: needs.files-changed.outputs.templates == 'true' || needs.files-changed.outputs.yaml == 'true'
|
||||||
|
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
||||||
|
- if: needs.files-changed.outputs.templates == 'true' || needs.files-changed.outputs.yaml == 'true'
|
||||||
|
run: uv python install 3.14 && make deps-py lint-templates lint-yaml
|
||||||
|
|
||||||
|
- if: needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.swagger == 'true' || needs.files-changed.outputs.json == 'true'
|
||||||
|
run: make deps-frontend lint-md lint-swagger lint-json
|
||||||
|
|
||||||
|
- if: needs.files-changed.outputs.actions == 'true'
|
||||||
|
run: make lint-actions
|
||||||
|
|
||||||
lint-go-windows:
|
lint-go-windows:
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
cache: false
|
||||||
|
- uses: ./.github/actions/go-cache
|
||||||
|
with:
|
||||||
|
cache-name: lint-go-windows
|
||||||
|
lint-cache: "true"
|
||||||
- run: make deps-backend deps-tools
|
- run: make deps-backend deps-tools
|
||||||
- run: make lint-go-windows lint-go-gitea-vet
|
- run: make lint-go-windows
|
||||||
env:
|
env:
|
||||||
TAGS: bindata sqlite sqlite_unlock_notify
|
TAGS: bindata
|
||||||
GOOS: windows
|
GOOS: windows
|
||||||
GOARCH: amd64
|
GOARCH: amd64
|
||||||
|
|
||||||
@@ -132,31 +90,37 @@ jobs:
|
|||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
cache: false
|
||||||
|
- uses: ./.github/actions/go-cache
|
||||||
|
with:
|
||||||
|
cache-name: lint-go-gogit
|
||||||
|
lint-cache: "true"
|
||||||
- run: make deps-backend deps-tools
|
- run: make deps-backend deps-tools
|
||||||
- run: make lint-go
|
- run: make lint-go
|
||||||
env:
|
env:
|
||||||
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
TAGS: bindata gogit
|
||||||
|
|
||||||
checks-backend:
|
checks-backend:
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
cache: false
|
||||||
|
- uses: ./.github/actions/go-cache
|
||||||
|
with:
|
||||||
|
cache-name: checks-backend
|
||||||
|
build-cache: "false"
|
||||||
- run: make deps-backend deps-tools
|
- run: make deps-backend deps-tools
|
||||||
- run: make --always-make checks-backend # ensure the "go-licenses" make target runs
|
- run: make --always-make checks-backend # ensure the "go-licenses" make target runs
|
||||||
|
|
||||||
@@ -164,12 +128,10 @@ jobs:
|
|||||||
if: needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: pnpm/action-setup@v5
|
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 24
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
@@ -184,20 +146,21 @@ jobs:
|
|||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
# no frontend build here as backend should be able to build
|
cache: false
|
||||||
# even without any frontend files
|
- uses: ./.github/actions/go-cache
|
||||||
- run: make deps-backend
|
with:
|
||||||
- run: go build -o gitea_no_gcc # test if build succeeds without the sqlite tag
|
cache-name: compliance-backend
|
||||||
|
- run: make deps-backend generate-go
|
||||||
|
# no frontend build here as backend should be able to build, even without any frontend files
|
||||||
|
# CGO is not used when cross-compile, so these steps also test if the code is compatible with CGO disabled
|
||||||
- name: build-backend-arm64
|
- name: build-backend-arm64
|
||||||
run: make backend # test cross compile
|
run: go build -o gitea_linux_arm64
|
||||||
env:
|
env:
|
||||||
GOOS: linux
|
GOOS: linux
|
||||||
GOARCH: arm64
|
GOARCH: arm64
|
||||||
@@ -209,38 +172,7 @@ jobs:
|
|||||||
GOARCH: amd64
|
GOARCH: amd64
|
||||||
TAGS: bindata gogit
|
TAGS: bindata gogit
|
||||||
- name: build-backend-386
|
- name: build-backend-386
|
||||||
run: go build -o gitea_linux_386 # test if compatible with 32 bit
|
run: go build -o gitea_linux_386
|
||||||
env:
|
env:
|
||||||
GOOS: linux
|
GOOS: linux
|
||||||
GOARCH: 386
|
GOARCH: 386
|
||||||
|
|
||||||
docs:
|
|
||||||
if: needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.actions == 'true'
|
|
||||||
needs: files-changed
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
- uses: pnpm/action-setup@v5
|
|
||||||
- uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version: 24
|
|
||||||
cache: pnpm
|
|
||||||
cache-dependency-path: pnpm-lock.yaml
|
|
||||||
- run: make deps-frontend
|
|
||||||
- run: make lint-md
|
|
||||||
|
|
||||||
actions:
|
|
||||||
if: needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.actions == 'true'
|
|
||||||
needs: files-changed
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
- uses: actions/setup-go@v6
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
check-latest: true
|
|
||||||
- run: make lint-actions
|
|
||||||
|
|||||||
@@ -7,18 +7,17 @@ concurrency:
|
|||||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
files-changed:
|
files-changed:
|
||||||
uses: ./.github/workflows/files-changed.yml
|
uses: ./.github/workflows/files-changed.yml
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
test-pgsql:
|
test-pgsql:
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
services:
|
services:
|
||||||
pgsql:
|
pgsql:
|
||||||
image: postgres:14
|
image: postgres:14
|
||||||
@@ -35,18 +34,22 @@ jobs:
|
|||||||
minio:
|
minio:
|
||||||
# as github actions doesn't support "entrypoint", we need to use a non-official image
|
# as github actions doesn't support "entrypoint", we need to use a non-official image
|
||||||
# that has a custom entrypoint set to "minio server /data"
|
# that has a custom entrypoint set to "minio server /data"
|
||||||
image: bitnamilegacy/minio:2023.8.31
|
image: bitnamilegacy/minio:2023.12.23
|
||||||
env:
|
env:
|
||||||
MINIO_ROOT_USER: 123456
|
MINIO_ROOT_USER: 123456
|
||||||
MINIO_ROOT_PASSWORD: 12345678
|
MINIO_ROOT_PASSWORD: 12345678
|
||||||
ports:
|
ports:
|
||||||
- "9000:9000"
|
- "9000:9000"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
cache: false
|
||||||
|
- uses: ./.github/actions/go-cache
|
||||||
|
with:
|
||||||
|
cache-name: pgsql
|
||||||
- name: Add hosts to /etc/hosts
|
- name: Add hosts to /etc/hosts
|
||||||
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 pgsql ldap minio" | sudo tee -a /etc/hosts'
|
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 pgsql ldap minio" | sudo tee -a /etc/hosts'
|
||||||
- run: make deps-backend
|
- run: make deps-backend
|
||||||
@@ -54,53 +57,58 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
- name: run migration tests
|
- name: run migration tests
|
||||||
run: make test-pgsql-migration
|
run: GITEA_TEST_DATABASE=pgsql make test-migration
|
||||||
- name: run tests
|
- name: run tests
|
||||||
run: make test-pgsql
|
run: GITEA_TEST_DATABASE=pgsql make test-integration
|
||||||
timeout-minutes: 50
|
timeout-minutes: 50
|
||||||
env:
|
env:
|
||||||
|
# pgsql is chosen to be the unlucky one to run with the slow "race detector", it is about 60% slower.
|
||||||
|
GOTEST_FLAGS: -race -timeout=40m
|
||||||
TAGS: bindata gogit
|
TAGS: bindata gogit
|
||||||
RACE_ENABLED: true
|
|
||||||
TEST_TAGS: gogit
|
|
||||||
TEST_LDAP: 1
|
TEST_LDAP: 1
|
||||||
|
|
||||||
test-sqlite:
|
test-sqlite:
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
cache: false
|
||||||
|
- uses: ./.github/actions/go-cache
|
||||||
|
with:
|
||||||
|
cache-name: sqlite
|
||||||
- run: make deps-backend
|
- run: make deps-backend
|
||||||
- run: GOEXPERIMENT='' make backend
|
- run: make backend
|
||||||
env:
|
env:
|
||||||
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
TAGS: bindata gogit
|
||||||
|
GOEXPERIMENT:
|
||||||
- name: run migration tests
|
- name: run migration tests
|
||||||
run: make test-sqlite-migration
|
run: GITEA_TEST_DATABASE=sqlite make test-migration
|
||||||
|
env:
|
||||||
|
TAGS: bindata gogit
|
||||||
- name: run tests
|
- name: run tests
|
||||||
run: GOEXPERIMENT='' make test-sqlite
|
run: GITEA_TEST_DATABASE=sqlite make test-integration
|
||||||
timeout-minutes: 50
|
timeout-minutes: 50
|
||||||
env:
|
env:
|
||||||
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
# sqlite driver can contain large amount of Golang code, so don't use race detector for it, otherwise, extremely slow
|
||||||
RACE_ENABLED: true
|
GOTEST_FLAGS: -timeout=40m
|
||||||
TEST_TAGS: gogit sqlite sqlite_unlock_notify
|
TAGS: bindata gogit
|
||||||
|
GOEXPERIMENT:
|
||||||
|
|
||||||
test-unit:
|
test-unit:
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
services:
|
services:
|
||||||
elasticsearch:
|
elasticsearch:
|
||||||
image: elasticsearch:7.5.0
|
image: docker.elastic.co/elasticsearch/elasticsearch:8.19.14
|
||||||
env:
|
env:
|
||||||
discovery.type: single-node
|
discovery.type: single-node
|
||||||
|
xpack.security.enabled: false
|
||||||
ports:
|
ports:
|
||||||
- "9200:9200"
|
- "9200:9200"
|
||||||
meilisearch:
|
meilisearch:
|
||||||
@@ -119,7 +127,7 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- 6379:6379
|
- 6379:6379
|
||||||
minio:
|
minio:
|
||||||
image: bitnamilegacy/minio:2021.3.17
|
image: bitnamilegacy/minio:2021.12.29
|
||||||
env:
|
env:
|
||||||
MINIO_ACCESS_KEY: 123456
|
MINIO_ACCESS_KEY: 123456
|
||||||
MINIO_SECRET_KEY: 12345678
|
MINIO_SECRET_KEY: 12345678
|
||||||
@@ -130,11 +138,16 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- 10000:10000
|
- 10000:10000
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
cache: false
|
||||||
|
- uses: ./.github/actions/go-cache
|
||||||
|
with:
|
||||||
|
cache-name: unit
|
||||||
|
build-cache-rotate: "true"
|
||||||
- name: Add hosts to /etc/hosts
|
- name: Add hosts to /etc/hosts
|
||||||
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 minio devstoreaccount1.azurite.local mysql elasticsearch meilisearch smtpimap" | sudo tee -a /etc/hosts'
|
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 minio devstoreaccount1.azurite.local mysql elasticsearch meilisearch smtpimap" | sudo tee -a /etc/hosts'
|
||||||
- run: make deps-backend
|
- run: make deps-backend
|
||||||
@@ -142,28 +155,27 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
- name: unit-tests
|
- name: unit-tests
|
||||||
run: make unit-test-coverage test-check
|
run: make test-backend test-check
|
||||||
env:
|
env:
|
||||||
|
GOTEST_FLAGS: -race -timeout=20m
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
RACE_ENABLED: true
|
|
||||||
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
|
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
|
||||||
- name: unit-tests-gogit
|
- name: unit-tests-gogit
|
||||||
run: GOEXPERIMENT='' make unit-test-coverage test-check
|
run: make test-backend test-check
|
||||||
env:
|
env:
|
||||||
|
GOTEST_FLAGS: -race -timeout=20m
|
||||||
TAGS: bindata gogit
|
TAGS: bindata gogit
|
||||||
RACE_ENABLED: true
|
GOEXPERIMENT:
|
||||||
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
|
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
|
||||||
|
|
||||||
test-mysql:
|
test-mysql:
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
services:
|
services:
|
||||||
mysql:
|
mysql:
|
||||||
# the bitnami mysql image has more options than the official one, it's easier to customize
|
# the bitnami mysql image has more options than the official one, it's easier to customize
|
||||||
image: bitnamilegacy/mysql:8.0
|
image: bitnamilegacy/mysql:8.4
|
||||||
env:
|
env:
|
||||||
ALLOW_EMPTY_PASSWORD: true
|
ALLOW_EMPTY_PASSWORD: true
|
||||||
MYSQL_DATABASE: testgitea
|
MYSQL_DATABASE: testgitea
|
||||||
@@ -172,9 +184,10 @@ jobs:
|
|||||||
options: >-
|
options: >-
|
||||||
--mount type=tmpfs,destination=/bitnami/mysql/data
|
--mount type=tmpfs,destination=/bitnami/mysql/data
|
||||||
elasticsearch:
|
elasticsearch:
|
||||||
image: elasticsearch:7.5.0
|
image: docker.elastic.co/elasticsearch/elasticsearch:8.19.14
|
||||||
env:
|
env:
|
||||||
discovery.type: single-node
|
discovery.type: single-node
|
||||||
|
xpack.security.enabled: false
|
||||||
ports:
|
ports:
|
||||||
- "9200:9200"
|
- "9200:9200"
|
||||||
smtpimap:
|
smtpimap:
|
||||||
@@ -185,11 +198,15 @@ jobs:
|
|||||||
- "587:587"
|
- "587:587"
|
||||||
- "993:993"
|
- "993:993"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
cache: false
|
||||||
|
- uses: ./.github/actions/go-cache
|
||||||
|
with:
|
||||||
|
cache-name: mysql
|
||||||
- name: Add hosts to /etc/hosts
|
- name: Add hosts to /etc/hosts
|
||||||
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mysql elasticsearch smtpimap" | sudo tee -a /etc/hosts'
|
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mysql elasticsearch smtpimap" | sudo tee -a /etc/hosts'
|
||||||
- run: make deps-backend
|
- run: make deps-backend
|
||||||
@@ -197,21 +214,17 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
- name: run migration tests
|
- name: run migration tests
|
||||||
run: make test-mysql-migration
|
run: GITEA_TEST_DATABASE=mysql make test-migration
|
||||||
- name: run tests
|
- name: run tests
|
||||||
# run: make integration-test-coverage (at the moment, no coverage is really handled)
|
run: GITEA_TEST_DATABASE=mysql make test-integration
|
||||||
run: make test-mysql
|
|
||||||
env:
|
env:
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
RACE_ENABLED: true
|
|
||||||
TEST_INDEXER_CODE_ES_URL: "http://elastic:changeme@elasticsearch:9200"
|
TEST_INDEXER_CODE_ES_URL: "http://elastic:changeme@elasticsearch:9200"
|
||||||
|
|
||||||
test-mssql:
|
test-mssql:
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
services:
|
services:
|
||||||
mssql:
|
mssql:
|
||||||
image: mcr.microsoft.com/mssql/server:2019-latest
|
image: mcr.microsoft.com/mssql/server:2019-latest
|
||||||
@@ -226,20 +239,24 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- 10000:10000
|
- 10000:10000
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
cache: false
|
||||||
|
- uses: ./.github/actions/go-cache
|
||||||
|
with:
|
||||||
|
cache-name: mssql
|
||||||
- name: Add hosts to /etc/hosts
|
- name: Add hosts to /etc/hosts
|
||||||
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mssql devstoreaccount1.azurite.local" | sudo tee -a /etc/hosts'
|
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mssql devstoreaccount1.azurite.local" | sudo tee -a /etc/hosts'
|
||||||
- run: make deps-backend
|
- run: make deps-backend
|
||||||
- run: make backend
|
- run: make backend
|
||||||
env:
|
env:
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
- run: make test-mssql-migration
|
- run: GITEA_TEST_DATABASE=mssql make test-migration
|
||||||
- name: run tests
|
- name: run tests
|
||||||
run: make test-mssql
|
run: GITEA_TEST_DATABASE=mssql make test-integration
|
||||||
timeout-minutes: 50
|
timeout-minutes: 50
|
||||||
env:
|
env:
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
|
|||||||
@@ -7,34 +7,41 @@ concurrency:
|
|||||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
files-changed:
|
files-changed:
|
||||||
uses: ./.github/workflows/files-changed.yml
|
uses: ./.github/workflows/files-changed.yml
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
container:
|
# QEMU-based build is slow (40-50 minutes), so run arm64 and riscv64 when dockerfile changes.
|
||||||
|
# Run amd64 when any docker-related files change, which is fast (4 minutes).
|
||||||
|
container-amd64:
|
||||||
if: needs.files-changed.outputs.docker == 'true'
|
if: needs.files-changed.outputs.docker == 'true'
|
||||||
needs: files-changed
|
needs: [files-changed]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: docker/setup-qemu-action@v4
|
- uses: ./.github/actions/docker-dryrun
|
||||||
- uses: docker/setup-buildx-action@v4
|
|
||||||
- name: Build regular container image
|
|
||||||
uses: docker/build-push-action@v7
|
|
||||||
with:
|
with:
|
||||||
context: .
|
platform: linux/amd64
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
|
||||||
push: false
|
container-arm64:
|
||||||
cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful
|
if: needs.files-changed.outputs.dockerfile == 'true'
|
||||||
- name: Build rootless container image
|
needs: [files-changed]
|
||||||
uses: docker/build-push-action@v7
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
- uses: ./.github/actions/docker-dryrun
|
||||||
with:
|
with:
|
||||||
context: .
|
platform: linux/arm64
|
||||||
push: false
|
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
container-riscv64:
|
||||||
file: Dockerfile.rootless
|
if: needs.files-changed.outputs.dockerfile == 'true'
|
||||||
cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootless
|
needs: [files-changed]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
- uses: ./.github/actions/docker-dryrun
|
||||||
|
with:
|
||||||
|
platform: linux/riscv64
|
||||||
|
|||||||
@@ -7,26 +7,30 @@ concurrency:
|
|||||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
files-changed:
|
files-changed:
|
||||||
uses: ./.github/workflows/files-changed.yml
|
uses: ./.github/workflows/files-changed.yml
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
test-e2e:
|
test-e2e:
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.e2e == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: pnpm/action-setup@v5
|
cache: false
|
||||||
- uses: actions/setup-node@v6
|
- uses: ./.github/actions/go-cache
|
||||||
|
with:
|
||||||
|
cache-name: e2e
|
||||||
|
build-cache: "false"
|
||||||
|
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||||
|
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 24
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
@@ -34,10 +38,13 @@ jobs:
|
|||||||
- run: make deps-frontend
|
- run: make deps-frontend
|
||||||
- run: make frontend
|
- run: make frontend
|
||||||
- run: make deps-backend
|
- run: make deps-backend
|
||||||
- run: make gitea-e2e
|
- run: make backend
|
||||||
|
env:
|
||||||
|
TAGS: bindata
|
||||||
- run: make playwright
|
- run: make playwright
|
||||||
- run: make test-e2e
|
- run: make test-e2e
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
env:
|
env:
|
||||||
|
TAGS: bindata
|
||||||
FORCE_COLOR: 1
|
FORCE_COLOR: 1
|
||||||
GITEA_TEST_E2E_DEBUG: 1
|
GITEA_TEST_E2E_DEBUG: 1
|
||||||
|
|||||||
@@ -15,6 +15,6 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/labeler@v6
|
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
|
||||||
with:
|
with:
|
||||||
sync-labels: true
|
sync-labels: true
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
name: pr-title
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- edited
|
||||||
|
- reopened
|
||||||
|
- synchronize
|
||||||
|
- ready_for_review
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint-pr-title:
|
||||||
|
if: github.event.pull_request.draft == false
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
- run: make lint-pr-title
|
||||||
|
env:
|
||||||
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||||
@@ -14,16 +14,16 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||||
- run: git fetch --unshallow --quiet --tags --force
|
- run: git fetch --unshallow --quiet --tags --force
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: pnpm/action-setup@v5
|
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 24
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
@@ -32,10 +32,10 @@ jobs:
|
|||||||
# xgo build
|
# xgo build
|
||||||
- run: make release
|
- run: make release
|
||||||
env:
|
env:
|
||||||
TAGS: bindata sqlite sqlite_unlock_notify
|
TAGS: bindata
|
||||||
- name: import gpg key
|
- name: import gpg key
|
||||||
id: import_gpg
|
id: import_gpg
|
||||||
uses: crazy-max/ghaction-import-gpg@v7
|
uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0
|
||||||
with:
|
with:
|
||||||
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
|
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
|
||||||
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
|
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
|
||||||
@@ -52,7 +52,7 @@ jobs:
|
|||||||
echo "Cleaned name is ${REF_NAME}"
|
echo "Cleaned name is ${REF_NAME}"
|
||||||
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
|
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
|
||||||
- name: configure aws
|
- name: configure aws
|
||||||
uses: aws-actions/configure-aws-credentials@v6
|
uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6.1.0
|
||||||
with:
|
with:
|
||||||
aws-region: ${{ secrets.AWS_REGION }}
|
aws-region: ${{ secrets.AWS_REGION }}
|
||||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
@@ -67,18 +67,18 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
packages: write # to publish to ghcr.io
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||||
- run: git fetch --unshallow --quiet --tags --force
|
- run: git fetch --unshallow --quiet --tags --force
|
||||||
- uses: docker/setup-qemu-action@v4
|
- uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
||||||
- uses: docker/setup-buildx-action@v4
|
- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||||
- name: Get cleaned branch name
|
- name: Get cleaned branch name
|
||||||
id: clean_name
|
id: clean_name
|
||||||
run: |
|
run: |
|
||||||
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
|
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
|
||||||
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
|
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
|
||||||
- uses: docker/metadata-action@v6
|
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||||
id: meta
|
id: meta
|
||||||
with:
|
with:
|
||||||
images: |-
|
images: |-
|
||||||
@@ -88,7 +88,7 @@ jobs:
|
|||||||
type=raw,value=${{ steps.clean_name.outputs.branch }}
|
type=raw,value=${{ steps.clean_name.outputs.branch }}
|
||||||
annotations: |
|
annotations: |
|
||||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
org.opencontainers.image.authors="maintainers@gitea.io"
|
||||||
- uses: docker/metadata-action@v6
|
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||||
id: meta_rootless
|
id: meta_rootless
|
||||||
with:
|
with:
|
||||||
images: |-
|
images: |-
|
||||||
@@ -102,18 +102,18 @@ jobs:
|
|||||||
annotations: |
|
annotations: |
|
||||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
org.opencontainers.image.authors="maintainers@gitea.io"
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Login to GHCR using PAT
|
- name: Login to GHCR using PAT
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: build regular docker image
|
- name: build regular docker image
|
||||||
uses: docker/build-push-action@v7
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
@@ -123,7 +123,7 @@ jobs:
|
|||||||
cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful
|
cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful
|
||||||
cache-to: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful,mode=max
|
cache-to: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful,mode=max
|
||||||
- name: build rootless docker image
|
- name: build rootless docker image
|
||||||
uses: docker/build-push-action@v7
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
|
|||||||
@@ -15,16 +15,16 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||||
- run: git fetch --unshallow --quiet --tags --force
|
- run: git fetch --unshallow --quiet --tags --force
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: pnpm/action-setup@v5
|
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 24
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
@@ -33,10 +33,10 @@ jobs:
|
|||||||
# xgo build
|
# xgo build
|
||||||
- run: make release
|
- run: make release
|
||||||
env:
|
env:
|
||||||
TAGS: bindata sqlite sqlite_unlock_notify
|
TAGS: bindata
|
||||||
- name: import gpg key
|
- name: import gpg key
|
||||||
id: import_gpg
|
id: import_gpg
|
||||||
uses: crazy-max/ghaction-import-gpg@v7
|
uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0
|
||||||
with:
|
with:
|
||||||
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
|
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
|
||||||
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
|
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
|
||||||
@@ -53,7 +53,7 @@ jobs:
|
|||||||
echo "Cleaned name is ${REF_NAME}"
|
echo "Cleaned name is ${REF_NAME}"
|
||||||
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
|
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
|
||||||
- name: configure aws
|
- name: configure aws
|
||||||
uses: aws-actions/configure-aws-credentials@v6
|
uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6.1.0
|
||||||
with:
|
with:
|
||||||
aws-region: ${{ secrets.AWS_REGION }}
|
aws-region: ${{ secrets.AWS_REGION }}
|
||||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
@@ -62,7 +62,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
||||||
- name: Install GH CLI
|
- name: Install GH CLI
|
||||||
uses: dev-hanz-ops/install-gh-cli-action@v0.2.1
|
uses: dev-hanz-ops/install-gh-cli-action@af38ce09b1ec248aeb08eea2b16bbecea9e059f8 # v0.2.1
|
||||||
with:
|
with:
|
||||||
gh-cli-version: 2.39.1
|
gh-cli-version: 2.39.1
|
||||||
- name: create github release
|
- name: create github release
|
||||||
@@ -77,13 +77,13 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
packages: write # to publish to ghcr.io
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||||
- run: git fetch --unshallow --quiet --tags --force
|
- run: git fetch --unshallow --quiet --tags --force
|
||||||
- uses: docker/setup-qemu-action@v4
|
- uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
||||||
- uses: docker/setup-buildx-action@v4
|
- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||||
- uses: docker/metadata-action@v6
|
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||||
id: meta
|
id: meta
|
||||||
with:
|
with:
|
||||||
images: |-
|
images: |-
|
||||||
@@ -96,7 +96,7 @@ jobs:
|
|||||||
type=semver,pattern={{version}}
|
type=semver,pattern={{version}}
|
||||||
annotations: |
|
annotations: |
|
||||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
org.opencontainers.image.authors="maintainers@gitea.io"
|
||||||
- uses: docker/metadata-action@v6
|
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||||
id: meta_rootless
|
id: meta_rootless
|
||||||
with:
|
with:
|
||||||
images: |-
|
images: |-
|
||||||
@@ -112,18 +112,18 @@ jobs:
|
|||||||
annotations: |
|
annotations: |
|
||||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
org.opencontainers.image.authors="maintainers@gitea.io"
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Login to GHCR using PAT
|
- name: Login to GHCR using PAT
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: build regular container image
|
- name: build regular container image
|
||||||
uses: docker/build-push-action@v7
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
@@ -131,7 +131,7 @@ jobs:
|
|||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
annotations: ${{ steps.meta.outputs.annotations }}
|
annotations: ${{ steps.meta.outputs.annotations }}
|
||||||
- name: build rootless container image
|
- name: build rootless container image
|
||||||
uses: docker/build-push-action@v7
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
|
|||||||
@@ -18,16 +18,16 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
packages: write # to publish to ghcr.io
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||||
- run: git fetch --unshallow --quiet --tags --force
|
- run: git fetch --unshallow --quiet --tags --force
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: pnpm/action-setup@v5
|
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 24
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
@@ -36,10 +36,10 @@ jobs:
|
|||||||
# xgo build
|
# xgo build
|
||||||
- run: make release
|
- run: make release
|
||||||
env:
|
env:
|
||||||
TAGS: bindata sqlite sqlite_unlock_notify
|
TAGS: bindata
|
||||||
- name: import gpg key
|
- name: import gpg key
|
||||||
id: import_gpg
|
id: import_gpg
|
||||||
uses: crazy-max/ghaction-import-gpg@v7
|
uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0
|
||||||
with:
|
with:
|
||||||
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
|
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
|
||||||
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
|
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
|
||||||
@@ -56,7 +56,7 @@ jobs:
|
|||||||
echo "Cleaned name is ${REF_NAME}"
|
echo "Cleaned name is ${REF_NAME}"
|
||||||
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
|
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
|
||||||
- name: configure aws
|
- name: configure aws
|
||||||
uses: aws-actions/configure-aws-credentials@v6
|
uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6.1.0
|
||||||
with:
|
with:
|
||||||
aws-region: ${{ secrets.AWS_REGION }}
|
aws-region: ${{ secrets.AWS_REGION }}
|
||||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
@@ -65,7 +65,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
||||||
- name: Install GH CLI
|
- name: Install GH CLI
|
||||||
uses: dev-hanz-ops/install-gh-cli-action@v0.2.1
|
uses: dev-hanz-ops/install-gh-cli-action@af38ce09b1ec248aeb08eea2b16bbecea9e059f8 # v0.2.1
|
||||||
with:
|
with:
|
||||||
gh-cli-version: 2.39.1
|
gh-cli-version: 2.39.1
|
||||||
- name: create github release
|
- name: create github release
|
||||||
@@ -80,13 +80,13 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
packages: write # to publish to ghcr.io
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||||
- run: git fetch --unshallow --quiet --tags --force
|
- run: git fetch --unshallow --quiet --tags --force
|
||||||
- uses: docker/setup-qemu-action@v4
|
- uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
||||||
- uses: docker/setup-buildx-action@v4
|
- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||||
- uses: docker/metadata-action@v6
|
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||||
id: meta
|
id: meta
|
||||||
with:
|
with:
|
||||||
images: |-
|
images: |-
|
||||||
@@ -103,7 +103,7 @@ jobs:
|
|||||||
type=semver,pattern={{major}}.{{minor}}
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
annotations: |
|
annotations: |
|
||||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
org.opencontainers.image.authors="maintainers@gitea.io"
|
||||||
- uses: docker/metadata-action@v6
|
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||||
id: meta_rootless
|
id: meta_rootless
|
||||||
with:
|
with:
|
||||||
images: |-
|
images: |-
|
||||||
@@ -124,18 +124,18 @@ jobs:
|
|||||||
annotations: |
|
annotations: |
|
||||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
org.opencontainers.image.authors="maintainers@gitea.io"
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Login to GHCR using PAT
|
- name: Login to GHCR using PAT
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: build regular container image
|
- name: build regular container image
|
||||||
uses: docker/build-push-action@v7
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
@@ -143,7 +143,7 @@ jobs:
|
|||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
annotations: ${{ steps.meta.outputs.annotations }}
|
annotations: ${{ steps.meta.outputs.annotations }}
|
||||||
- name: build rootless container image
|
- name: build rootless container image
|
||||||
uses: docker/build-push-action@v7
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
|
|||||||
@@ -55,10 +55,7 @@ cpu.out
|
|||||||
*.log.*.gz
|
*.log.*.gz
|
||||||
|
|
||||||
/gitea
|
/gitea
|
||||||
/gitea-e2e
|
|
||||||
/gitea-vet
|
|
||||||
/debug
|
/debug
|
||||||
/integrations.test
|
|
||||||
|
|
||||||
/bin
|
/bin
|
||||||
/dist
|
/dist
|
||||||
|
|||||||
+23
-5
@@ -13,6 +13,7 @@ linters:
|
|||||||
- forbidigo
|
- forbidigo
|
||||||
- gocheckcompilerdirectives
|
- gocheckcompilerdirectives
|
||||||
- gocritic
|
- gocritic
|
||||||
|
- goheader
|
||||||
- govet
|
- govet
|
||||||
- ineffassign
|
- ineffassign
|
||||||
- mirror
|
- mirror
|
||||||
@@ -43,7 +44,7 @@ linters:
|
|||||||
desc: use os or io instead
|
desc: use os or io instead
|
||||||
- pkg: golang.org/x/exp
|
- pkg: golang.org/x/exp
|
||||||
desc: it's experimental and unreliable
|
desc: it's experimental and unreliable
|
||||||
- pkg: code.gitea.io/gitea/modules/git/internal
|
- pkg: git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/git/internal
|
||||||
desc: do not use the internal package, use AddXxx function instead
|
desc: do not use the internal package, use AddXxx function instead
|
||||||
- pkg: gopkg.in/ini.v1
|
- pkg: gopkg.in/ini.v1
|
||||||
desc: do not use the ini package, use gitea's config system instead
|
desc: do not use the ini package, use gitea's config system instead
|
||||||
@@ -51,6 +52,14 @@ linters:
|
|||||||
desc: do not use the go-chi cache package, use gitea's cache system
|
desc: do not use the go-chi cache package, use gitea's cache system
|
||||||
- pkg: github.com/pkg/errors
|
- pkg: github.com/pkg/errors
|
||||||
desc: use builtin errors package instead
|
desc: use builtin errors package instead
|
||||||
|
migrations:
|
||||||
|
files:
|
||||||
|
- '**/models/migrations/**/*.go'
|
||||||
|
deny:
|
||||||
|
- pkg: git.mokoconsulting.tech/MokoConsulting/MokoGitea/models$
|
||||||
|
desc: migrations must not depend on the models package
|
||||||
|
- pkg: git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs
|
||||||
|
desc: migrations must not depend on modules/structs (API structures change over time)
|
||||||
nolintlint:
|
nolintlint:
|
||||||
allow-unused: false
|
allow-unused: false
|
||||||
require-explanation: true
|
require-explanation: true
|
||||||
@@ -109,6 +118,11 @@ linters:
|
|||||||
enable:
|
enable:
|
||||||
- nilness
|
- nilness
|
||||||
- unusedwrite
|
- unusedwrite
|
||||||
|
goheader:
|
||||||
|
values:
|
||||||
|
regexp:
|
||||||
|
HEADER: '((Copyright [^\n]+|All rights reserved\.)\n)*Copyright \d{4} (The (Gogs|Gitea) Authors|Gitea Authors|Gitea)\.( All rights reserved\.)?(\n(Copyright [^\n]+|All rights reserved\.))*\nSPDX-License-Identifier: [\w.-]+'
|
||||||
|
template: '{{ HEADER }}'
|
||||||
exclusions:
|
exclusions:
|
||||||
generated: lax
|
generated: lax
|
||||||
presets:
|
presets:
|
||||||
@@ -158,9 +172,16 @@ issues:
|
|||||||
max-same-issues: 0
|
max-same-issues: 0
|
||||||
formatters:
|
formatters:
|
||||||
enable:
|
enable:
|
||||||
- gofmt
|
- gci
|
||||||
- gofumpt
|
- gofumpt
|
||||||
settings:
|
settings:
|
||||||
|
gci:
|
||||||
|
custom-order: true
|
||||||
|
sections:
|
||||||
|
- standard
|
||||||
|
- prefix(git.mokoconsulting.tech/MokoConsulting/MokoGitea)
|
||||||
|
- blank
|
||||||
|
- default
|
||||||
gofumpt:
|
gofumpt:
|
||||||
extra-rules: true
|
extra-rules: true
|
||||||
exclusions:
|
exclusions:
|
||||||
@@ -170,9 +191,6 @@ formatters:
|
|||||||
- .venv
|
- .venv
|
||||||
- public
|
- public
|
||||||
- web_src
|
- web_src
|
||||||
- third_party$
|
|
||||||
- builtin$
|
|
||||||
- examples$
|
|
||||||
|
|
||||||
run:
|
run:
|
||||||
timeout: 10m
|
timeout: 10m
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"ssh": {
|
||||||
|
"type": "stdio",
|
||||||
|
"command": "node",
|
||||||
|
"args": [
|
||||||
|
"A:/ssh-mcp/src/index.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"wiki": {
|
||||||
|
"type": "stdio",
|
||||||
|
"command": "node",
|
||||||
|
"args": [
|
||||||
|
"A:/wiki-mcp/dist/index.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"project": {
|
||||||
|
"type": "stdio",
|
||||||
|
"command": "node",
|
||||||
|
"args": [
|
||||||
|
"A:/project-mcp/dist/index.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
---
|
||||||
|
name: Architecture Decision Record (ADR)
|
||||||
|
about: Propose or document an architectural decision
|
||||||
|
title: '[ADR] '
|
||||||
|
labels: 'architecture, decision'
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## ADR Number
|
||||||
|
ADR-XXXX
|
||||||
|
|
||||||
|
## Status
|
||||||
|
- [ ] Proposed
|
||||||
|
- [ ] Accepted
|
||||||
|
- [ ] Deprecated
|
||||||
|
- [ ] Superseded by ADR-XXXX
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Describe the issue or problem that motivates this decision.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
State the architecture decision and provide rationale.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
### Positive
|
||||||
|
- List positive consequences
|
||||||
|
|
||||||
|
### Negative
|
||||||
|
- List negative consequences or trade-offs
|
||||||
|
|
||||||
|
### Neutral
|
||||||
|
- List neutral aspects
|
||||||
|
|
||||||
|
## Alternatives Considered
|
||||||
|
### Alternative 1
|
||||||
|
- Description
|
||||||
|
- Pros
|
||||||
|
- Cons
|
||||||
|
- Why not chosen
|
||||||
|
|
||||||
|
### Alternative 2
|
||||||
|
- Description
|
||||||
|
- Pros
|
||||||
|
- Cons
|
||||||
|
- Why not chosen
|
||||||
|
|
||||||
|
## Implementation Plan
|
||||||
|
1. Step 1
|
||||||
|
2. Step 2
|
||||||
|
3. Step 3
|
||||||
|
|
||||||
|
## Stakeholders
|
||||||
|
- **Decision Makers**: @user1, @user2
|
||||||
|
- **Consulted**: @user3, @user4
|
||||||
|
- **Informed**: team-name
|
||||||
|
|
||||||
|
## Technical Details
|
||||||
|
### Architecture Diagram
|
||||||
|
```
|
||||||
|
[Add diagram or link]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
- Dependency 1
|
||||||
|
- Dependency 2
|
||||||
|
|
||||||
|
### Impact Analysis
|
||||||
|
- **Performance**: [Impact description]
|
||||||
|
- **Security**: [Impact description]
|
||||||
|
- **Scalability**: [Impact description]
|
||||||
|
- **Maintainability**: [Impact description]
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
- [ ] Unit tests
|
||||||
|
- [ ] Integration tests
|
||||||
|
- [ ] Performance tests
|
||||||
|
- [ ] Security tests
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
- [ ] Architecture documentation updated
|
||||||
|
- [ ] API documentation updated
|
||||||
|
- [ ] Developer guide updated
|
||||||
|
- [ ] Runbook created
|
||||||
|
|
||||||
|
## Migration Path
|
||||||
|
Describe how to migrate from current state to new architecture.
|
||||||
|
|
||||||
|
## Rollback Plan
|
||||||
|
Describe how to rollback if issues occur.
|
||||||
|
|
||||||
|
## Timeline
|
||||||
|
- **Proposal Date**:
|
||||||
|
- **Decision Date**:
|
||||||
|
- **Implementation Start**:
|
||||||
|
- **Expected Completion**:
|
||||||
|
|
||||||
|
## References
|
||||||
|
- Related ADRs:
|
||||||
|
- External resources:
|
||||||
|
- RFCs:
|
||||||
|
|
||||||
|
## Review Checklist
|
||||||
|
- [ ] Aligns with enterprise architecture principles
|
||||||
|
- [ ] Security implications reviewed
|
||||||
|
- [ ] Performance implications reviewed
|
||||||
|
- [ ] Cost implications reviewed
|
||||||
|
- [ ] Compliance requirements met
|
||||||
|
- [ ] Team consensus achieved
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
name: Bug Report
|
||||||
|
about: Report a bug or issue with the project
|
||||||
|
title: '[BUG] '
|
||||||
|
labels: 'bug'
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## Bug Description
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
## Steps to Reproduce
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '...'
|
||||||
|
3. Scroll down to '...'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
## Expected Behavior
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
## Actual Behavior
|
||||||
|
A clear and concise description of what actually happened.
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
- **Project**: [e.g., MokoDoliTools, moko-cassiopeia]
|
||||||
|
- **Version**: [e.g., 1.2.3]
|
||||||
|
- **Platform**: [e.g., Dolibarr 18.0, Joomla 5.0]
|
||||||
|
- **PHP Version**: [e.g., 8.1]
|
||||||
|
- **Database**: [e.g., MySQL 8.0, PostgreSQL 14]
|
||||||
|
- **Browser** (if applicable): [e.g., Chrome 120, Firefox 121]
|
||||||
|
- **OS**: [e.g., Ubuntu 22.04, Windows 11]
|
||||||
|
|
||||||
|
## Additional Context
|
||||||
|
Add any other context about the problem here.
|
||||||
|
|
||||||
|
## Possible Solution
|
||||||
|
If you have suggestions on how to fix the issue, please describe them here.
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
- [ ] I have searched for similar issues before creating this one
|
||||||
|
- [ ] I have provided all the requested information
|
||||||
|
- [ ] I have tested this on the latest stable version
|
||||||
|
- [ ] I have checked the documentation and couldn't find a solution
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
blank_issues_enabled: true
|
||||||
|
contact_links:
|
||||||
|
- name: 💼 Enterprise Support
|
||||||
|
url: https://mokoconsulting.tech/enterprise
|
||||||
|
about: Enterprise-level support and consultation services
|
||||||
|
- name: 💬 Ask a Question
|
||||||
|
url: https://mokoconsulting.tech/
|
||||||
|
about: Get help or ask questions through our website
|
||||||
|
- name: 📚 MokoStandards Documentation
|
||||||
|
url: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
|
about: View our coding standards and best practices
|
||||||
|
- name: 🔒 Report a Security Vulnerability
|
||||||
|
url: https://git.mokoconsulting.tech/mokoconsulting-tech/.github-private/security/advisories/new
|
||||||
|
about: Report security vulnerabilities privately (for critical issues)
|
||||||
|
- name: 💡 Community Discussions
|
||||||
|
url: https://github.com/orgs/mokoconsulting-tech/discussions
|
||||||
|
about: Join community discussions and Q&A
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
name: Documentation Issue
|
||||||
|
about: Report an issue with documentation
|
||||||
|
title: '[DOCS] '
|
||||||
|
labels: 'documentation'
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## Documentation Issue
|
||||||
|
|
||||||
|
**Location**:
|
||||||
|
<!-- Specify the file, page, or section with the issue -->
|
||||||
|
|
||||||
|
## Issue Type
|
||||||
|
<!-- Mark the relevant option with an "x" -->
|
||||||
|
- [ ] Typo or grammar error
|
||||||
|
- [ ] Outdated information
|
||||||
|
- [ ] Missing documentation
|
||||||
|
- [ ] Unclear explanation
|
||||||
|
- [ ] Broken links
|
||||||
|
- [ ] Missing examples
|
||||||
|
- [ ] Other (specify below)
|
||||||
|
|
||||||
|
## Description
|
||||||
|
<!-- Clearly describe the documentation issue -->
|
||||||
|
|
||||||
|
## Current Content
|
||||||
|
<!-- Quote or describe the current documentation (if applicable) -->
|
||||||
|
```
|
||||||
|
Current text here
|
||||||
|
```
|
||||||
|
|
||||||
|
## Suggested Improvement
|
||||||
|
<!-- Provide your suggestion for how to improve the documentation -->
|
||||||
|
```
|
||||||
|
Suggested text here
|
||||||
|
```
|
||||||
|
|
||||||
|
## Additional Context
|
||||||
|
<!-- Add any other context, screenshots, or references -->
|
||||||
|
|
||||||
|
## Standards Alignment
|
||||||
|
- [ ] Follows MokoStandards documentation guidelines
|
||||||
|
- [ ] Uses en_US/en_GB localization
|
||||||
|
- [ ] Includes proper SPDX headers where applicable
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
- [ ] I have searched for similar documentation issues
|
||||||
|
- [ ] I have provided a clear description
|
||||||
|
- [ ] I have suggested an improvement (if applicable)
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
name: Feature Request
|
||||||
|
about: Suggest a new feature or enhancement
|
||||||
|
title: '[FEATURE] '
|
||||||
|
labels: 'enhancement'
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## Feature Description
|
||||||
|
A clear and concise description of the feature you'd like to see.
|
||||||
|
|
||||||
|
## Problem or Use Case
|
||||||
|
Describe the problem this feature would solve or the use case it addresses.
|
||||||
|
Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
## Proposed Solution
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
## Alternative Solutions
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
Describe how this feature would benefit users:
|
||||||
|
- Who would use this feature?
|
||||||
|
- What problems does it solve?
|
||||||
|
- What value does it add?
|
||||||
|
|
||||||
|
## Implementation Details (Optional)
|
||||||
|
If you have ideas about how this could be implemented, share them here:
|
||||||
|
- Technical approach
|
||||||
|
- Files/components that might need changes
|
||||||
|
- Any concerns or challenges you foresee
|
||||||
|
|
||||||
|
## Additional Context
|
||||||
|
Add any other context, mockups, or screenshots about the feature request here.
|
||||||
|
|
||||||
|
## Relevant Standards
|
||||||
|
Does this relate to any standards in [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards)?
|
||||||
|
- [ ] Accessibility (WCAG 2.1 AA)
|
||||||
|
- [ ] Localization (en_US/en_GB)
|
||||||
|
- [ ] Security best practices
|
||||||
|
- [ ] Code quality standards
|
||||||
|
- [ ] Other: [specify]
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
- [ ] I have searched for similar feature requests before creating this one
|
||||||
|
- [ ] I have clearly described the use case and benefits
|
||||||
|
- [ ] I have considered alternative solutions
|
||||||
|
- [ ] This feature aligns with the project's goals and scope
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
---
|
||||||
|
name: Question
|
||||||
|
about: Ask a question about usage, features, or best practices
|
||||||
|
title: '[QUESTION] '
|
||||||
|
labels: ['question']
|
||||||
|
assignees: ['jmiller']
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## Question
|
||||||
|
|
||||||
|
**Your question:**
|
||||||
|
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
**What are you trying to accomplish?**
|
||||||
|
|
||||||
|
|
||||||
|
**What have you already tried?**
|
||||||
|
|
||||||
|
|
||||||
|
**Category**:
|
||||||
|
- [ ] Script usage
|
||||||
|
- [ ] Configuration
|
||||||
|
- [ ] Workflow setup
|
||||||
|
- [ ] Documentation interpretation
|
||||||
|
- [ ] Best practices
|
||||||
|
- [ ] Integration
|
||||||
|
- [ ] Other: __________
|
||||||
|
|
||||||
|
## Environment (if relevant)
|
||||||
|
|
||||||
|
**Your setup**:
|
||||||
|
- Operating System:
|
||||||
|
- Version:
|
||||||
|
|
||||||
|
## What You've Researched
|
||||||
|
|
||||||
|
**Documentation reviewed**:
|
||||||
|
- [ ] README.md
|
||||||
|
- [ ] Project documentation
|
||||||
|
- [ ] Other (specify): __________
|
||||||
|
|
||||||
|
**Similar issues/questions found**:
|
||||||
|
- #
|
||||||
|
- #
|
||||||
|
|
||||||
|
## Expected Outcome
|
||||||
|
|
||||||
|
**What result are you hoping for?**
|
||||||
|
|
||||||
|
|
||||||
|
## Code/Configuration Samples
|
||||||
|
|
||||||
|
**Relevant code or configuration** (if applicable):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Your code here
|
||||||
|
```
|
||||||
|
|
||||||
|
## Additional Context
|
||||||
|
|
||||||
|
**Any other relevant information:**
|
||||||
|
|
||||||
|
|
||||||
|
**Screenshots** (if helpful):
|
||||||
|
|
||||||
|
|
||||||
|
## Urgency
|
||||||
|
|
||||||
|
- [ ] Urgent (blocking work)
|
||||||
|
- [ ] Normal (can work on other things meanwhile)
|
||||||
|
- [ ] Low priority (just curious)
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
- [ ] I have searched existing issues and discussions
|
||||||
|
- [ ] I have reviewed relevant documentation
|
||||||
|
- [ ] I have provided sufficient context
|
||||||
|
- [ ] I have included code/configuration samples if relevant
|
||||||
|
- [ ] This is a genuine question (not a bug report or feature request)
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
---
|
||||||
|
name: Request for Comments (RFC)
|
||||||
|
about: Propose a significant change for community discussion
|
||||||
|
title: '[RFC] '
|
||||||
|
labels: 'rfc, discussion'
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## RFC Summary
|
||||||
|
One-paragraph summary of the proposal.
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
Why are we doing this? What use cases does it support? What is the expected outcome?
|
||||||
|
|
||||||
|
## Detailed Design
|
||||||
|
### Overview
|
||||||
|
Provide a detailed explanation of the proposed change.
|
||||||
|
|
||||||
|
### API Changes (if applicable)
|
||||||
|
```php
|
||||||
|
// Before
|
||||||
|
function oldApi($param1) { }
|
||||||
|
|
||||||
|
// After
|
||||||
|
function newApi($param1, $param2) { }
|
||||||
|
```
|
||||||
|
|
||||||
|
### User Experience Changes
|
||||||
|
Describe how users will interact with this change.
|
||||||
|
|
||||||
|
### Implementation Approach
|
||||||
|
High-level implementation strategy.
|
||||||
|
|
||||||
|
## Drawbacks
|
||||||
|
Why should we *not* do this?
|
||||||
|
|
||||||
|
## Alternatives
|
||||||
|
What other designs have been considered? What is the impact of not doing this?
|
||||||
|
|
||||||
|
### Alternative 1
|
||||||
|
- Description
|
||||||
|
- Trade-offs
|
||||||
|
|
||||||
|
### Alternative 2
|
||||||
|
- Description
|
||||||
|
- Trade-offs
|
||||||
|
|
||||||
|
## Adoption Strategy
|
||||||
|
How will existing users adopt this? Is this a breaking change?
|
||||||
|
|
||||||
|
### Migration Guide
|
||||||
|
```bash
|
||||||
|
# Steps to migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deprecation Timeline
|
||||||
|
- **Announcement**:
|
||||||
|
- **Deprecation**:
|
||||||
|
- **Removal**:
|
||||||
|
|
||||||
|
## Unresolved Questions
|
||||||
|
- Question 1
|
||||||
|
- Question 2
|
||||||
|
|
||||||
|
## Future Possibilities
|
||||||
|
What future work does this enable?
|
||||||
|
|
||||||
|
## Impact Assessment
|
||||||
|
### Performance
|
||||||
|
Expected performance impact.
|
||||||
|
|
||||||
|
### Security
|
||||||
|
Security considerations and implications.
|
||||||
|
|
||||||
|
### Compatibility
|
||||||
|
- **Backward Compatible**: [Yes / No]
|
||||||
|
- **Breaking Changes**: [List]
|
||||||
|
|
||||||
|
### Maintenance
|
||||||
|
Long-term maintenance considerations.
|
||||||
|
|
||||||
|
## Community Input
|
||||||
|
### Stakeholders
|
||||||
|
- [ ] Core team
|
||||||
|
- [ ] Module developers
|
||||||
|
- [ ] End users
|
||||||
|
- [ ] Enterprise customers
|
||||||
|
|
||||||
|
### Feedback Period
|
||||||
|
**Duration**: [e.g., 2 weeks]
|
||||||
|
**Deadline**: [date]
|
||||||
|
|
||||||
|
## Implementation Timeline
|
||||||
|
### Phase 1: Design
|
||||||
|
- [ ] RFC discussion
|
||||||
|
- [ ] Design finalization
|
||||||
|
- [ ] Approval
|
||||||
|
|
||||||
|
### Phase 2: Implementation
|
||||||
|
- [ ] Core implementation
|
||||||
|
- [ ] Tests
|
||||||
|
- [ ] Documentation
|
||||||
|
|
||||||
|
### Phase 3: Release
|
||||||
|
- [ ] Beta release
|
||||||
|
- [ ] Feedback collection
|
||||||
|
- [ ] Stable release
|
||||||
|
|
||||||
|
## Success Metrics
|
||||||
|
How will we measure success?
|
||||||
|
- Metric 1
|
||||||
|
- Metric 2
|
||||||
|
|
||||||
|
## References
|
||||||
|
- Related RFCs:
|
||||||
|
- External documentation:
|
||||||
|
- Prior art:
|
||||||
|
|
||||||
|
## Open Questions for Community
|
||||||
|
1. Question 1?
|
||||||
|
2. Question 2?
|
||||||
|
|
||||||
|
---
|
||||||
|
**Note**: This RFC is open for community discussion. Please provide feedback in the comments below.
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
name: Security Vulnerability Report
|
||||||
|
about: Report a security vulnerability (use only for non-critical issues)
|
||||||
|
title: '[SECURITY] '
|
||||||
|
labels: 'security'
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## ⚠️ IMPORTANT: Private Disclosure Required
|
||||||
|
|
||||||
|
**For critical security vulnerabilities, DO NOT use this template.**
|
||||||
|
Follow the process in [SECURITY.md](../SECURITY.md) for responsible disclosure.
|
||||||
|
|
||||||
|
Use this template only for:
|
||||||
|
- Security improvements
|
||||||
|
- Non-critical security suggestions
|
||||||
|
- Security documentation updates
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security Issue
|
||||||
|
|
||||||
|
**Severity**:
|
||||||
|
<!-- Low, Medium, or informational only -->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
<!-- Describe the security concern or improvement suggestion -->
|
||||||
|
|
||||||
|
## Affected Components
|
||||||
|
<!-- List the affected files, features, or components -->
|
||||||
|
|
||||||
|
## Suggested Mitigation
|
||||||
|
<!-- Describe how this could be addressed -->
|
||||||
|
|
||||||
|
## Standards Reference
|
||||||
|
Does this relate to security standards in [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards)?
|
||||||
|
- [ ] SPDX license identifiers
|
||||||
|
- [ ] Secret management
|
||||||
|
- [ ] Dependency security
|
||||||
|
- [ ] Access control
|
||||||
|
- [ ] Other: [specify]
|
||||||
|
|
||||||
|
## Additional Context
|
||||||
|
<!-- Add any other context about the security concern -->
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
- [ ] This is NOT a critical vulnerability requiring private disclosure
|
||||||
|
- [ ] I have reviewed the SECURITY.md policy
|
||||||
|
- [ ] I have provided sufficient detail for evaluation
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
name: ".mokogitea Test Template"
|
||||||
|
about: "Verify .mokogitea issue templates work"
|
||||||
|
labels: ["test"]
|
||||||
|
---
|
||||||
|
|
||||||
|
This template was loaded from `.mokogitea/ISSUE_TEMPLATE/`.
|
||||||
|
|
||||||
|
If you can see this, the `.mokogitea` dot-folder feature is working.
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
name: Version Bump
|
||||||
|
about: Request or track a version change
|
||||||
|
title: '[VERSION] '
|
||||||
|
labels: 'version, type: version'
|
||||||
|
assignees: 'jmiller'
|
||||||
|
---
|
||||||
|
|
||||||
|
## Version Change
|
||||||
|
|
||||||
|
**Current version**: <!-- e.g., 01.02.03 -->
|
||||||
|
**Requested version**: <!-- e.g., 01.03.00 -->
|
||||||
|
**Change type**: <!-- patch / minor / major -->
|
||||||
|
|
||||||
|
## Reason
|
||||||
|
|
||||||
|
<!-- Why is this version bump needed? -->
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
- [ ] README.md `VERSION:` field updated
|
||||||
|
- [ ] CHANGELOG.md entry added
|
||||||
|
- [ ] Module descriptor version updated (Dolibarr: `$this->version`, Joomla: `<version>`)
|
||||||
|
- [ ] All file headers will be auto-propagated by `sync-version-on-merge` workflow
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: Gitea.Workflow
|
||||||
|
# INGROUP: moko-platform.Automation
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
|
# PATH: /.gitea/workflows/branch-protection.yml
|
||||||
|
# BRIEF: Apply standardised branch protection rules to all governed repositories
|
||||||
|
#
|
||||||
|
# +========================================================================+
|
||||||
|
# | BRANCH PROTECTION SETUP |
|
||||||
|
# +========================================================================+
|
||||||
|
# | |
|
||||||
|
# | Applies protection rules for: main, dev, rc, beta, alpha |
|
||||||
|
# | |
|
||||||
|
# | main — Require PR, block rejected reviews, no force push |
|
||||||
|
# | dev — Allow push, no force push, no delete |
|
||||||
|
# | rc — Allow push, no force push, no delete |
|
||||||
|
# | beta — Allow push, no force push, no delete |
|
||||||
|
# | alpha — Allow push, no force push, no delete |
|
||||||
|
# | |
|
||||||
|
# | jmiller has override authority on all branches. |
|
||||||
|
# | |
|
||||||
|
# +========================================================================+
|
||||||
|
|
||||||
|
name: Branch Protection Setup
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 2 * * 1' # Weekly Monday 02:00 UTC
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
dry_run:
|
||||||
|
description: 'Preview mode (no changes)'
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
repos:
|
||||||
|
description: 'Comma-separated repo names (empty = all governed repos)'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
|
|
||||||
|
env:
|
||||||
|
GITEA_URL: https://git.mokoconsulting.tech
|
||||||
|
GITEA_ORG: MokoConsulting
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
protect:
|
||||||
|
name: Apply Branch Protection Rules
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Determine target repos
|
||||||
|
id: repos
|
||||||
|
env:
|
||||||
|
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||||
|
run: |
|
||||||
|
API="${GITEA_URL}/api/v1"
|
||||||
|
|
||||||
|
# Platform/standards/infra repos to exclude
|
||||||
|
EXCLUDE="gitea-org-config org-profile gitea-private .mokogitea-private MokoStandards moko-platform MokoTesting"
|
||||||
|
EXCLUDE="$EXCLUDE MokoStandards-Template-Client MokoStandards-Template-Dolibarr MokoStandards-Template-Generic MokoStandards-Template-Joomla MokoDoliProjTemplate"
|
||||||
|
|
||||||
|
if [ -n "${{ inputs.repos }}" ]; then
|
||||||
|
# User-specified repos
|
||||||
|
REPOS=$(echo "${{ inputs.repos }}" | tr ',' ' ')
|
||||||
|
else
|
||||||
|
# Fetch all org repos
|
||||||
|
PAGE=1
|
||||||
|
REPOS=""
|
||||||
|
while true; do
|
||||||
|
BATCH=$(curl -sS \
|
||||||
|
-H "Authorization: token ${GA_TOKEN}" \
|
||||||
|
"${API}/orgs/${GITEA_ORG}/repos?page=${PAGE}&limit=50" \
|
||||||
|
| jq -r '.[].name // empty')
|
||||||
|
[ -z "$BATCH" ] && break
|
||||||
|
REPOS="$REPOS $BATCH"
|
||||||
|
PAGE=$((PAGE + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
# Filter out excluded repos
|
||||||
|
FILTERED=""
|
||||||
|
for REPO in $REPOS; do
|
||||||
|
SKIP=false
|
||||||
|
for EX in $EXCLUDE; do
|
||||||
|
if [ "$REPO" = "$EX" ]; then
|
||||||
|
SKIP=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ "$SKIP" = "false" ]; then
|
||||||
|
FILTERED="$FILTERED $REPO"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
REPOS="$FILTERED"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "repos=$REPOS" >> "$GITHUB_OUTPUT"
|
||||||
|
COUNT=$(echo "$REPOS" | wc -w)
|
||||||
|
echo "📋 Target repos (${COUNT}): $REPOS"
|
||||||
|
|
||||||
|
- name: Apply protection rules
|
||||||
|
env:
|
||||||
|
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||||
|
DRY_RUN: ${{ inputs.dry_run || 'false' }}
|
||||||
|
run: |
|
||||||
|
API="${GITEA_URL}/api/v1"
|
||||||
|
REPOS="${{ steps.repos.outputs.repos }}"
|
||||||
|
|
||||||
|
SUCCESS=0
|
||||||
|
FAILED=0
|
||||||
|
SKIPPED=0
|
||||||
|
|
||||||
|
# ── Rule definitions ──────────────────────────────────────
|
||||||
|
# Only the CI bot (jmiller token) can push directly.
|
||||||
|
# All human contributors must use PRs.
|
||||||
|
# Force push disabled on all branches.
|
||||||
|
|
||||||
|
RULE_MAIN='{
|
||||||
|
"rule_name": "main",
|
||||||
|
"enable_push": true,
|
||||||
|
"enable_push_whitelist": true,
|
||||||
|
"push_whitelist_usernames": ["jmiller"],
|
||||||
|
"enable_force_push": false,
|
||||||
|
"enable_force_push_allowlist": false,
|
||||||
|
"force_push_allowlist_usernames": [],
|
||||||
|
"enable_merge_whitelist": false,
|
||||||
|
"required_approvals": 0,
|
||||||
|
"dismiss_stale_approvals": true,
|
||||||
|
"block_on_rejected_reviews": true,
|
||||||
|
"block_on_outdated_branch": false,
|
||||||
|
"priority": 1
|
||||||
|
}'
|
||||||
|
|
||||||
|
RULE_DEV='{
|
||||||
|
"rule_name": "dev",
|
||||||
|
"enable_push": true,
|
||||||
|
"enable_push_whitelist": true,
|
||||||
|
"push_whitelist_usernames": ["jmiller"],
|
||||||
|
"enable_force_push": false,
|
||||||
|
"enable_force_push_allowlist": false,
|
||||||
|
"force_push_allowlist_usernames": [],
|
||||||
|
"enable_merge_whitelist": false,
|
||||||
|
"required_approvals": 0,
|
||||||
|
"block_on_rejected_reviews": false,
|
||||||
|
"priority": 2
|
||||||
|
}'
|
||||||
|
|
||||||
|
RULE_RC='{
|
||||||
|
"rule_name": "rc",
|
||||||
|
"enable_push": true,
|
||||||
|
"enable_push_whitelist": true,
|
||||||
|
"push_whitelist_usernames": ["jmiller"],
|
||||||
|
"enable_force_push": false,
|
||||||
|
"enable_force_push_allowlist": false,
|
||||||
|
"force_push_allowlist_usernames": [],
|
||||||
|
"enable_merge_whitelist": false,
|
||||||
|
"required_approvals": 0,
|
||||||
|
"block_on_rejected_reviews": false,
|
||||||
|
"priority": 3
|
||||||
|
}'
|
||||||
|
|
||||||
|
RULE_BETA='{
|
||||||
|
"rule_name": "beta",
|
||||||
|
"enable_push": true,
|
||||||
|
"enable_push_whitelist": true,
|
||||||
|
"push_whitelist_usernames": ["jmiller"],
|
||||||
|
"enable_force_push": false,
|
||||||
|
"enable_force_push_allowlist": false,
|
||||||
|
"force_push_allowlist_usernames": [],
|
||||||
|
"enable_merge_whitelist": false,
|
||||||
|
"required_approvals": 0,
|
||||||
|
"block_on_rejected_reviews": false,
|
||||||
|
"priority": 4
|
||||||
|
}'
|
||||||
|
|
||||||
|
RULE_ALPHA='{
|
||||||
|
"rule_name": "alpha",
|
||||||
|
"enable_push": true,
|
||||||
|
"enable_push_whitelist": true,
|
||||||
|
"push_whitelist_usernames": ["jmiller"],
|
||||||
|
"enable_force_push": false,
|
||||||
|
"enable_force_push_allowlist": false,
|
||||||
|
"force_push_allowlist_usernames": [],
|
||||||
|
"enable_merge_whitelist": false,
|
||||||
|
"required_approvals": 0,
|
||||||
|
"block_on_rejected_reviews": false,
|
||||||
|
"priority": 5
|
||||||
|
}'
|
||||||
|
|
||||||
|
RULES=("$RULE_MAIN" "$RULE_DEV" "$RULE_RC" "$RULE_BETA" "$RULE_ALPHA")
|
||||||
|
RULE_NAMES=("main" "dev" "rc" "beta" "alpha")
|
||||||
|
|
||||||
|
# ── Apply rules to each repo ──────────────────────────────
|
||||||
|
for REPO in $REPOS; do
|
||||||
|
echo ""
|
||||||
|
echo "═══ ${REPO} ═══"
|
||||||
|
|
||||||
|
for i in "${!RULES[@]}"; do
|
||||||
|
RULE="${RULES[$i]}"
|
||||||
|
NAME="${RULE_NAMES[$i]}"
|
||||||
|
|
||||||
|
if [ "$DRY_RUN" = "true" ]; then
|
||||||
|
echo " [DRY RUN] Would apply rule: ${NAME}"
|
||||||
|
SKIPPED=$((SKIPPED + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Delete existing rule if present (idempotent recreate)
|
||||||
|
ENCODED_NAME=$(echo "$NAME" | sed 's|/|%2F|g')
|
||||||
|
curl -sS -o /dev/null -w "" \
|
||||||
|
-X DELETE \
|
||||||
|
-H "Authorization: token ${GA_TOKEN}" \
|
||||||
|
"${API}/repos/${GITEA_ORG}/${REPO}/branch_protections/${ENCODED_NAME}" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Create rule
|
||||||
|
RESPONSE=$(curl -sS -w "\n%{http_code}" \
|
||||||
|
-X POST \
|
||||||
|
-H "Authorization: token ${GA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$RULE" \
|
||||||
|
"${API}/repos/${GITEA_ORG}/${REPO}/branch_protections")
|
||||||
|
|
||||||
|
HTTP=$(echo "$RESPONSE" | tail -1)
|
||||||
|
BODY=$(echo "$RESPONSE" | sed '$d')
|
||||||
|
|
||||||
|
if [ "$HTTP" = "201" ]; then
|
||||||
|
echo " ✅ ${NAME}"
|
||||||
|
SUCCESS=$((SUCCESS + 1))
|
||||||
|
else
|
||||||
|
echo " ❌ ${NAME} (HTTP ${HTTP}): $(echo "$BODY" | jq -r '.message // .' 2>/dev/null | head -1)"
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
# ── Summary ───────────────────────────────────────────────
|
||||||
|
echo ""
|
||||||
|
echo "════════════════════════════════════════"
|
||||||
|
echo " ✅ Success: ${SUCCESS}"
|
||||||
|
echo " ❌ Failed: ${FAILED}"
|
||||||
|
echo " ⏭️ Skipped: ${SKIPPED}"
|
||||||
|
echo "════════════════════════════════════════"
|
||||||
|
|
||||||
|
if [ "$FAILED" -gt 0 ]; then
|
||||||
|
echo "::warning::${FAILED} rule(s) failed to apply"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<moko-platform xmlns="https://standards.mokoconsulting.tech/moko-platform/1.0" schema-version="1.0">
|
||||||
|
<identity>
|
||||||
|
<name>MokoGitea</name>
|
||||||
|
<org>MokoConsulting</org>
|
||||||
|
<description>Moko fork of Gitea — adding project board REST API endpoints and custom enhancements</description>
|
||||||
|
<version>05.08.00</version>
|
||||||
|
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
|
||||||
|
</identity>
|
||||||
|
<governance>
|
||||||
|
<platform>go</platform>
|
||||||
|
<standards-version>05.00.00</standards-version>
|
||||||
|
<standards-source>https://git.mokoconsulting.tech/MokoConsulting/moko-platform</standards-source>
|
||||||
|
</governance>
|
||||||
|
<build>
|
||||||
|
<language>Go</language>
|
||||||
|
<package-type>application</package-type>
|
||||||
|
<entry-point>./</entry-point>
|
||||||
|
</build>
|
||||||
|
</moko-platform>
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: Gitea.Workflow
|
||||||
|
# INGROUP: moko-platform.Release
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
|
# PATH: /.mokogitea/workflows/auto-bump.yml
|
||||||
|
# VERSION: 09.02.00
|
||||||
|
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
|
||||||
|
|
||||||
|
name: "Universal: Auto Version Bump"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
- rc
|
||||||
|
- 'feature/**'
|
||||||
|
- 'patch/**'
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
bump:
|
||||||
|
name: Version Bump
|
||||||
|
runs-on: release
|
||||||
|
if: >-
|
||||||
|
!contains(github.event.head_commit.message, '[skip ci]') &&
|
||||||
|
!contains(github.event.head_commit.message, '[skip bump]') &&
|
||||||
|
!startsWith(github.event.head_commit.message, 'Merge pull request')
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Setup moko-platform tools
|
||||||
|
run: |
|
||||||
|
if ! command -v composer &> /dev/null; then
|
||||||
|
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
if [ -d "/opt/moko-platform/cli" ]; then
|
||||||
|
echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV"
|
||||||
|
else
|
||||||
|
git clone --depth 1 --branch main --quiet \
|
||||||
|
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \
|
||||||
|
/tmp/moko-platform-api
|
||||||
|
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
|
||||||
|
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Bump version
|
||||||
|
run: |
|
||||||
|
php ${MOKO_CLI}/version_auto_bump.php \
|
||||||
|
--path . --branch "${GITHUB_REF_NAME}" \
|
||||||
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||||
|
--repo-url "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
@@ -0,0 +1,270 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: Gitea.Workflow
|
||||||
|
# INGROUP: moko-platform.Release
|
||||||
|
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
|
||||||
|
# PATH: /templates/workflows/universal/auto-release.yml.template
|
||||||
|
# VERSION: 05.00.00
|
||||||
|
# BRIEF: Universal build & release � detects platform from manifest.xml
|
||||||
|
#
|
||||||
|
# +========================================================================+
|
||||||
|
# | UNIVERSAL BUILD & RELEASE PIPELINE |
|
||||||
|
# +========================================================================+
|
||||||
|
# | |
|
||||||
|
# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. |
|
||||||
|
# | |
|
||||||
|
# | Platform-specific: |
|
||||||
|
# | joomla: XML manifest, updates.xml, type-prefixed packages |
|
||||||
|
# | dolibarr: mod*.class.php, update.txt, dev version reset |
|
||||||
|
# | generic: README-only, no update stream |
|
||||||
|
# | |
|
||||||
|
# +========================================================================+
|
||||||
|
|
||||||
|
name: "Universal: Build & Release"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, closed]
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
action:
|
||||||
|
description: 'Action to perform'
|
||||||
|
required: false
|
||||||
|
type: choice
|
||||||
|
default: release
|
||||||
|
options:
|
||||||
|
- release
|
||||||
|
- promote-rc
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||||
|
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# ── PR Opened → Rename branch to RC and build RC release ─────────────────────
|
||||||
|
promote-rc:
|
||||||
|
name: Promote to RC
|
||||||
|
runs-on: release
|
||||||
|
if: >-
|
||||||
|
(github.event.action == 'opened' && github.event.pull_request.merged != true) ||
|
||||||
|
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Setup moko-platform tools
|
||||||
|
env:
|
||||||
|
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||||
|
run: |
|
||||||
|
if ! command -v composer &> /dev/null; then
|
||||||
|
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
# Always fetch latest CLI tools — never use stale cache from previous runs
|
||||||
|
rm -rf /tmp/moko-platform-api
|
||||||
|
git clone --depth 1 --branch main --quiet \
|
||||||
|
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||||
|
/tmp/moko-platform-api
|
||||||
|
cd /tmp/moko-platform-api
|
||||||
|
composer install --no-dev --no-interaction --quiet
|
||||||
|
|
||||||
|
- name: Rename branch to rc
|
||||||
|
run: |
|
||||||
|
php /tmp/moko-platform-api/cli/branch_rename.php \
|
||||||
|
--from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
|
||||||
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||||
|
--api-base "${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \
|
||||||
|
--pr "${{ github.event.pull_request.number }}"
|
||||||
|
|
||||||
|
- name: Checkout rc and configure git
|
||||||
|
run: |
|
||||||
|
git fetch origin rc
|
||||||
|
git checkout rc
|
||||||
|
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||||
|
git config --local user.name "gitea-actions[bot]"
|
||||||
|
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
|
|
||||||
|
- name: Publish RC release
|
||||||
|
run: |
|
||||||
|
php /tmp/moko-platform-api/cli/release_publish.php \
|
||||||
|
--path . --stability rc --bump minor --branch rc \
|
||||||
|
--token "${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Branch renamed to rc, minor bump, RC + lesser stream releases built, updates.xml synced" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
# ── Merged PR → Build & Release (or promote RC to stable) ────────────────────
|
||||||
|
release:
|
||||||
|
name: Build & Release Pipeline
|
||||||
|
runs-on: release
|
||||||
|
if: >-
|
||||||
|
github.event.pull_request.merged == true ||
|
||||||
|
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc')
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Configure git for bot pushes
|
||||||
|
run: |
|
||||||
|
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||||
|
git config --local user.name "gitea-actions[bot]"
|
||||||
|
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
|
|
||||||
|
- name: Setup moko-platform tools
|
||||||
|
env:
|
||||||
|
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||||
|
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
|
||||||
|
run: |
|
||||||
|
# Ensure PHP + Composer are available
|
||||||
|
if ! command -v composer &> /dev/null; then
|
||||||
|
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
# Always fetch latest CLI tools — never use stale cache from previous runs
|
||||||
|
rm -rf /tmp/moko-platform-api
|
||||||
|
git clone --depth 1 --branch main --quiet \
|
||||||
|
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||||
|
/tmp/moko-platform-api
|
||||||
|
cd /tmp/moko-platform-api
|
||||||
|
composer install --no-dev --no-interaction --quiet
|
||||||
|
|
||||||
|
|
||||||
|
- name: "Publish stable release"
|
||||||
|
run: |
|
||||||
|
php /tmp/moko-platform-api/cli/release_publish.php \
|
||||||
|
--path . --stability stable --bump minor --branch main \
|
||||||
|
--token "${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
|
|
||||||
|
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
|
||||||
|
- name: "Step 9: Mirror release to GitHub"
|
||||||
|
if: >-
|
||||||
|
steps.version.outputs.skip != 'true' &&
|
||||||
|
secrets.GH_MIRROR_TOKEN != ''
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||||
|
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||||
|
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
php /tmp/moko-platform-api/cli/release_mirror.php \
|
||||||
|
--version "$VERSION" --tag "$RELEASE_TAG" \
|
||||||
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||||
|
--gh-token "${{ secrets.GH_MIRROR_TOKEN }}" --gh-repo "$GH_REPO" \
|
||||||
|
--branch main 2>&1 || true
|
||||||
|
echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
# -- STEP 10: Sync main branch to GitHub mirror ----------------------------
|
||||||
|
- name: "Step 10: Push main to GitHub mirror"
|
||||||
|
if: >-
|
||||||
|
steps.version.outputs.skip != 'true' &&
|
||||||
|
secrets.GH_MIRROR_TOKEN != ''
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
||||||
|
GH_ORG=$(echo "$GH_REPO" | cut -d/ -f1)
|
||||||
|
GH_NAME=$(echo "$GH_REPO" | cut -d/ -f2)
|
||||||
|
git remote add github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" 2>/dev/null || \
|
||||||
|
git remote set-url github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git"
|
||||||
|
git fetch origin main --depth=1
|
||||||
|
git push github origin/main:refs/heads/main --force 2>/dev/null \
|
||||||
|
&& echo "main branch pushed to GitHub mirror" \
|
||||||
|
|| echo "WARNING: GitHub mirror push failed"
|
||||||
|
|
||||||
|
- name: "Step 11: Delete rc branch and recreate dev from main"
|
||||||
|
if: steps.version.outputs.skip != 'true'
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
|
|
||||||
|
# Delete rc branch (ephemeral — created by promote-rc)
|
||||||
|
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||||
|
"${API_BASE}/branches/rc" 2>/dev/null \
|
||||||
|
&& echo "Deleted rc branch" || echo "rc branch not found"
|
||||||
|
|
||||||
|
# Delete dev branch
|
||||||
|
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||||
|
"${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch"
|
||||||
|
|
||||||
|
# Recreate dev from main (now includes version bump + changelog promotion)
|
||||||
|
curl -sf -X POST -H "Authorization: token ${TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${API_BASE}/branches" \
|
||||||
|
-d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main"
|
||||||
|
|
||||||
|
echo "Pre-release branches cleaned, dev reset from main" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
- name: "Step 12: Create version branch from main"
|
||||||
|
if: steps.version.outputs.skip != 'true'
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
|
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||||
|
BRANCH_NAME="version/${VERSION}"
|
||||||
|
MAIN_SHA=$(git rev-parse HEAD)
|
||||||
|
|
||||||
|
# Delete old version branch if it exists (same version re-release)
|
||||||
|
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" "${API_BASE}/branches/${BRANCH_NAME}" 2>/dev/null && echo "Deleted old ${BRANCH_NAME}"
|
||||||
|
|
||||||
|
# Create version/XX.YY.ZZ from main
|
||||||
|
curl -sf -X POST -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" "${API_BASE}/branches" -d "{\"new_branch_name\":\"${BRANCH_NAME}\",\"old_branch_name\":\"main\"}" 2>/dev/null && echo "Created ${BRANCH_NAME} from main (${MAIN_SHA})" || echo "WARNING: ${BRANCH_NAME} creation failed"
|
||||||
|
|
||||||
|
echo "Version branch created: ${BRANCH_NAME} (${MAIN_SHA})" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# -- Dolibarr post-release: Reset dev version -----------------------------
|
||||||
|
- name: "Post-release: Reset dev version"
|
||||||
|
if: steps.version.outputs.skip != 'true'
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
php /tmp/moko-platform-api/cli/version_reset_dev.php \
|
||||||
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \
|
||||||
|
--branch dev --path . 2>&1 || true
|
||||||
|
|
||||||
|
# -- Summary --------------------------------------------------------------
|
||||||
|
- name: Pipeline Summary
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||||
|
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||||
|
if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
|
||||||
|
echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY
|
||||||
|
elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then
|
||||||
|
echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "## Build & Release Complete (${PLATFORM})" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Platform | \`${PLATFORM}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Release | [View](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: Gitea.Workflow
|
||||||
|
# INGROUP: MokoStandards.Universal
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
|
# PATH: /.mokogitea/workflows/branch-cleanup.yml
|
||||||
|
# VERSION: 01.00.00
|
||||||
|
# BRIEF: Delete feature branches after PR merge
|
||||||
|
|
||||||
|
name: "Branch Cleanup"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [closed]
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cleanup:
|
||||||
|
name: Delete merged branch
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: >-
|
||||||
|
github.event.pull_request.merged == true &&
|
||||||
|
github.event.pull_request.head.ref != 'dev' &&
|
||||||
|
github.event.pull_request.head.ref != 'main'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Delete source branch
|
||||||
|
run: |
|
||||||
|
BRANCH="${{ github.event.pull_request.head.ref }}"
|
||||||
|
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches"
|
||||||
|
ENCODED=$(php -r "echo rawurlencode('${BRANCH}');")
|
||||||
|
|
||||||
|
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
|
||||||
|
-H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||||
|
"${API}/${ENCODED}" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [ "$STATUS" = "204" ]; then
|
||||||
|
echo "Deleted branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
elif [ "$STATUS" = "404" ]; then
|
||||||
|
echo "Branch already deleted: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "::warning::Failed to delete branch ${BRANCH} (HTTP ${STATUS})"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
# BRIEF: Build MokoGitea Docker image, push to registry, and deploy
|
||||||
|
|
||||||
|
name: Deploy MokoGitea
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: 'Version tag (e.g. v1.26.1-moko.05.01.00)'
|
||||||
|
required: true
|
||||||
|
default: 'latest'
|
||||||
|
environment:
|
||||||
|
description: 'Target environment'
|
||||||
|
required: true
|
||||||
|
default: 'dev'
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- dev
|
||||||
|
- production
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: deploy-mokogitea
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: git.mokoconsulting.tech
|
||||||
|
IMAGE: mokoconsulting/mokogitea
|
||||||
|
DEPLOY_HOST: git.mokoconsulting.tech
|
||||||
|
DEPLOY_PORT: 2918
|
||||||
|
DEPLOY_USER: mokoconsulting
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout source (for version detection)
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Determine settings
|
||||||
|
id: config
|
||||||
|
run: |
|
||||||
|
# On push to main, auto-deploy to production with git-derived version.
|
||||||
|
# On workflow_dispatch, use the provided inputs.
|
||||||
|
if [ "${{ github.event_name }}" = "push" ]; then
|
||||||
|
VERSION=$(git describe --tags --always 2>/dev/null || echo "dev-$(git rev-parse --short HEAD)")
|
||||||
|
ENV="production"
|
||||||
|
else
|
||||||
|
VERSION="${{ github.event.inputs.version }}"
|
||||||
|
ENV="${{ github.event.inputs.environment }}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$ENV" = "production" ]; then
|
||||||
|
echo "compose_dir=/opt/gitea" >> $GITHUB_OUTPUT
|
||||||
|
echo "container=mokogitea" >> $GITHUB_OUTPUT
|
||||||
|
echo "source_dir=/opt/gitea/source" >> $GITHUB_OUTPUT
|
||||||
|
echo "branch=main" >> $GITHUB_OUTPUT
|
||||||
|
echo "tag=${VERSION}" >> $GITHUB_OUTPUT
|
||||||
|
echo "instance_url=https://git.mokoconsulting.tech" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "compose_dir=/opt/gitea-dev" >> $GITHUB_OUTPUT
|
||||||
|
echo "container=mokogitea-dev" >> $GITHUB_OUTPUT
|
||||||
|
echo "source_dir=/opt/gitea-dev/source" >> $GITHUB_OUTPUT
|
||||||
|
echo "branch=dev" >> $GITHUB_OUTPUT
|
||||||
|
echo "tag=${VERSION}-dev" >> $GITHUB_OUTPUT
|
||||||
|
echo "instance_url=https://git.dev.mokoconsulting.tech" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Enable maintenance mode
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
INSTANCE_URL: ${{ steps.config.outputs.instance_url }}
|
||||||
|
run: |
|
||||||
|
echo "Enabling maintenance mode on ${INSTANCE_URL}..."
|
||||||
|
curl -sf -X POST \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
"${INSTANCE_URL}/-/admin/config" \
|
||||||
|
-d 'key=instance.maintenance_mode&value={"AdminWebAccessOnly":true}' \
|
||||||
|
|| echo "WARNING: Could not enable maintenance mode (instance may be down)"
|
||||||
|
|
||||||
|
- name: Build and deploy via SSH
|
||||||
|
env:
|
||||||
|
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||||
|
TAG: ${{ steps.config.outputs.tag }}
|
||||||
|
BRANCH: ${{ steps.config.outputs.branch }}
|
||||||
|
SOURCE_DIR: ${{ steps.config.outputs.source_dir }}
|
||||||
|
COMPOSE_DIR: ${{ steps.config.outputs.compose_dir }}
|
||||||
|
CONTAINER: ${{ steps.config.outputs.container }}
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
echo "$SSH_PRIVATE_KEY" > ~/.ssh/deploy_key
|
||||||
|
chmod 600 ~/.ssh/deploy_key
|
||||||
|
|
||||||
|
SSH_CMD="ssh -i ~/.ssh/deploy_key -p ${{ env.DEPLOY_PORT }} -o ConnectTimeout=30 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }}"
|
||||||
|
|
||||||
|
$SSH_CMD "echo 'SSH connected'"
|
||||||
|
|
||||||
|
# Pre-deploy cleanup: free disk and memory for the build
|
||||||
|
$SSH_CMD "
|
||||||
|
echo 'Cleaning Docker build cache and unused images...'
|
||||||
|
docker builder prune -af 2>/dev/null || true
|
||||||
|
docker image prune -af 2>/dev/null || true
|
||||||
|
echo 'Clearing swap...'
|
||||||
|
sudo swapoff -a && sudo swapon -a 2>/dev/null || true
|
||||||
|
echo 'Cleanup complete'
|
||||||
|
free -m | head -3
|
||||||
|
"
|
||||||
|
|
||||||
|
# Pull latest source
|
||||||
|
$SSH_CMD "
|
||||||
|
set -e
|
||||||
|
if [ ! -d ${SOURCE_DIR}/.git ]; then
|
||||||
|
git clone -b ${BRANCH} https://git.mokoconsulting.tech/MokoConsulting/MokoGitea.git ${SOURCE_DIR}
|
||||||
|
fi
|
||||||
|
cd ${SOURCE_DIR}
|
||||||
|
git fetch origin ${BRANCH}
|
||||||
|
git reset --hard origin/${BRANCH}
|
||||||
|
"
|
||||||
|
|
||||||
|
# Build Docker image
|
||||||
|
$SSH_CMD "
|
||||||
|
set -e
|
||||||
|
cd ${SOURCE_DIR}
|
||||||
|
docker build --no-cache --build-arg GOFLAGS='-p 1' \
|
||||||
|
--tag ${{ env.REGISTRY }}/${{ env.IMAGE }}:${TAG} \
|
||||||
|
--tag ${{ env.REGISTRY }}/${{ env.IMAGE }}:latest \
|
||||||
|
-f Dockerfile .
|
||||||
|
"
|
||||||
|
|
||||||
|
# Push to container registry
|
||||||
|
$SSH_CMD "
|
||||||
|
set -e
|
||||||
|
docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:${TAG}
|
||||||
|
docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:latest
|
||||||
|
"
|
||||||
|
|
||||||
|
# Update compose and restart
|
||||||
|
$SSH_CMD "
|
||||||
|
set -e
|
||||||
|
cd ${COMPOSE_DIR}
|
||||||
|
sed -i 's|${{ env.IMAGE }}:[^ ]*|${{ env.IMAGE }}:${TAG}|' docker-compose.yml
|
||||||
|
docker compose up -d ${CONTAINER}
|
||||||
|
"
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
$SSH_CMD "
|
||||||
|
for i in 1 2 3 4 5 6 7 8; do
|
||||||
|
sleep 15
|
||||||
|
if docker inspect --format='{{.State.Health.Status}}' ${CONTAINER} 2>/dev/null | grep -q healthy; then
|
||||||
|
echo 'Container healthy!'
|
||||||
|
docker inspect --format='Image: {{.Config.Image}}' ${CONTAINER}
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo \"Waiting... (attempt \$i/8)\"
|
||||||
|
done
|
||||||
|
echo 'Health check failed'
|
||||||
|
docker logs ${CONTAINER} --tail 20
|
||||||
|
exit 1
|
||||||
|
"
|
||||||
|
|
||||||
|
- name: Update updates.xml
|
||||||
|
if: success()
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
TAG: ${{ steps.config.outputs.tag }}
|
||||||
|
INSTANCE_URL: ${{ steps.config.outputs.instance_url }}
|
||||||
|
DEPLOY_ENV: ${{ github.event.inputs.environment || 'production' }}
|
||||||
|
run: |
|
||||||
|
# Only update updates.xml for production stable releases
|
||||||
|
if [ "$DEPLOY_ENV" != "production" ]; then
|
||||||
|
echo "Skipping updates.xml — dev deployments don't update stable channel"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract moko version from tag (e.g. v1.26.1-moko.05.01.01 -> 05.01.01)
|
||||||
|
MOKO_VER=$(echo "$TAG" | sed -n 's/.*-moko\.\(.*\)/\1/p')
|
||||||
|
if [ -z "$MOKO_VER" ]; then
|
||||||
|
echo "Could not extract moko version from tag: $TAG"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
RELEASE_URL="https://${REGISTRY}/MokoConsulting/MokoGitea/releases/tag/${TAG}"
|
||||||
|
DOCKER_IMG="${REGISTRY}/${IMAGE}:${TAG}"
|
||||||
|
|
||||||
|
python3 << PYEOF
|
||||||
|
import json, os, re, base64, urllib.request
|
||||||
|
|
||||||
|
token = os.environ["GITEA_TOKEN"]
|
||||||
|
registry = os.environ["REGISTRY"]
|
||||||
|
tag = os.environ["TAG"]
|
||||||
|
moko_ver = os.environ["MOKO_VER"]
|
||||||
|
release_url = os.environ["RELEASE_URL"]
|
||||||
|
docker_img = os.environ["DOCKER_IMG"]
|
||||||
|
api = f"https://{registry}/api/v1/repos/MokoConsulting/MokoGitea"
|
||||||
|
|
||||||
|
# Fetch current updates.xml
|
||||||
|
req = urllib.request.Request(f"{api}/contents/updates.xml?ref=main",
|
||||||
|
headers={"Authorization": f"token {token}"})
|
||||||
|
with urllib.request.urlopen(req) as resp:
|
||||||
|
data = json.loads(resp.read())
|
||||||
|
sha = data["sha"]
|
||||||
|
content = base64.b64decode(data["content"]).decode("utf-8")
|
||||||
|
|
||||||
|
# Update stable channel — match the <update> block containing <tag>stable</tag>
|
||||||
|
def replace_channel(xml, channel, ver, url, docker):
|
||||||
|
pattern = rf"(<update>\s*<name>MokoGitea</name>[\s\S]*?<tags><tag>{channel}</tag></tags>[\s\S]*?</update>)"
|
||||||
|
def replacer(m):
|
||||||
|
block = m.group(1)
|
||||||
|
block = re.sub(r"<version>[^<]*</version>", f"<version>{ver}</version>", block)
|
||||||
|
block = re.sub(r"(<infourl[^>]*>)[^<]*(</infourl>)", rf"\1{url}\2", block)
|
||||||
|
block = re.sub(r"(<downloadurl[^>]*>)[^<]*(</downloadurl>)", rf"\1{docker}\2", block)
|
||||||
|
return block
|
||||||
|
return re.sub(pattern, replacer, xml)
|
||||||
|
|
||||||
|
content = replace_channel(content, "stable", moko_ver, release_url, docker_img)
|
||||||
|
content = re.sub(r"VERSION: [^\n]*", f"VERSION: {moko_ver}", content)
|
||||||
|
|
||||||
|
# Push updated file
|
||||||
|
encoded = base64.b64encode(content.encode()).decode()
|
||||||
|
payload = json.dumps({
|
||||||
|
"message": f"chore(ci): update updates.xml to {moko_ver}",
|
||||||
|
"content": encoded,
|
||||||
|
"sha": sha,
|
||||||
|
"branch": "main",
|
||||||
|
}).encode()
|
||||||
|
req = urllib.request.Request(f"{api}/contents/updates.xml",
|
||||||
|
data=payload, method="PUT",
|
||||||
|
headers={"Authorization": f"token {token}", "Content-Type": "application/json"})
|
||||||
|
with urllib.request.urlopen(req) as resp:
|
||||||
|
print(f"updates.xml updated to {moko_ver}")
|
||||||
|
PYEOF
|
||||||
|
|
||||||
|
- name: Disable maintenance mode
|
||||||
|
if: always()
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
INSTANCE_URL: ${{ steps.config.outputs.instance_url }}
|
||||||
|
run: |
|
||||||
|
echo "Disabling maintenance mode on ${INSTANCE_URL}..."
|
||||||
|
curl -sf -X POST \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
"${INSTANCE_URL}/-/admin/config" \
|
||||||
|
-d 'key=instance.maintenance_mode&value={"AdminWebAccessOnly":false}' \
|
||||||
|
|| echo "WARNING: Could not disable maintenance mode"
|
||||||
|
|
||||||
|
- name: Verify
|
||||||
|
run: |
|
||||||
|
sleep 5
|
||||||
|
curl -sf https://${{ env.DEPLOY_HOST }}/api/healthz && echo " — API healthy"
|
||||||
|
|
||||||
|
- name: Notify on failure
|
||||||
|
if: failure()
|
||||||
|
run: echo "::error::Deploy failed for ${{ steps.config.outputs.tag }}"
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: Gitea.Workflow
|
||||||
|
# INGROUP: moko-platform.Automation
|
||||||
|
# VERSION: 05.08.00
|
||||||
|
# BRIEF: Auto-create feature branch when an issue is opened
|
||||||
|
|
||||||
|
name: "Universal: Issue Branch"
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
create-branch:
|
||||||
|
name: Create feature branch
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Create branch and comment
|
||||||
|
run: |
|
||||||
|
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
|
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||||
|
ISSUE_NUM="${{ github.event.issue.number }}"
|
||||||
|
ISSUE_TITLE="${{ github.event.issue.title }}"
|
||||||
|
|
||||||
|
# Build slug from title: lowercase, replace non-alnum with dash, trim
|
||||||
|
SLUG=$(echo "${ISSUE_TITLE}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//' | cut -c1-40)
|
||||||
|
BRANCH="feature/${ISSUE_NUM}-${SLUG}"
|
||||||
|
|
||||||
|
# Check dev branch exists
|
||||||
|
DEV_EXISTS=$(curl -sf -o /dev/null -w '%{http_code}' \
|
||||||
|
-H "Authorization: token ${TOKEN}" \
|
||||||
|
"${API}/branches/dev" 2>/dev/null || echo "000")
|
||||||
|
|
||||||
|
if [ "${DEV_EXISTS}" != "200" ]; then
|
||||||
|
echo "No dev branch -- skipping"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create branch from dev
|
||||||
|
HTTP=$(curl -sf -o /dev/null -w '%{http_code}' -X POST \
|
||||||
|
-H "Authorization: token ${TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${API}/branches" \
|
||||||
|
-d "{\"new_branch_name\":\"${BRANCH}\",\"old_branch_name\":\"dev\"}" 2>/dev/null || echo "000")
|
||||||
|
|
||||||
|
if [ "${HTTP}" = "201" ]; then
|
||||||
|
echo "Created branch: ${BRANCH}"
|
||||||
|
|
||||||
|
# Comment on issue with branch link
|
||||||
|
REPO_URL="${GITEA_URL}/${{ github.repository }}"
|
||||||
|
BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`"
|
||||||
|
|
||||||
|
curl -sf -X POST \
|
||||||
|
-H "Authorization: token ${TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${API}/issues/${ISSUE_NUM}/comments" \
|
||||||
|
-d "{\"body\":\"${BODY}\"}" > /dev/null 2>&1
|
||||||
|
|
||||||
|
echo "Commented on issue #${ISSUE_NUM}"
|
||||||
|
else
|
||||||
|
echo "Failed to create branch (HTTP ${HTTP}) -- may already exist"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# Enforces branch merge policy:
|
||||||
|
# feature/* → dev only
|
||||||
|
# fix/* → dev only
|
||||||
|
# hotfix/* → dev or main (emergency)
|
||||||
|
# dev → main only
|
||||||
|
# alpha/* → dev only
|
||||||
|
# beta/* → dev only
|
||||||
|
# rc/* → main only
|
||||||
|
|
||||||
|
name: Branch Policy Check
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened, edited]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-target:
|
||||||
|
name: Verify merge target
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check branch policy
|
||||||
|
run: |
|
||||||
|
HEAD="${{ github.head_ref }}"
|
||||||
|
BASE="${{ github.base_ref }}"
|
||||||
|
|
||||||
|
echo "PR: ${HEAD} → ${BASE}"
|
||||||
|
|
||||||
|
ALLOWED=true
|
||||||
|
REASON=""
|
||||||
|
|
||||||
|
case "$HEAD" in
|
||||||
|
feature/*|feat/*)
|
||||||
|
if [ "$BASE" != "dev" ]; then
|
||||||
|
ALLOWED=false
|
||||||
|
REASON="Feature branches must target 'dev', not '${BASE}'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
fix/*|bugfix/*)
|
||||||
|
if [ "$BASE" != "dev" ]; then
|
||||||
|
ALLOWED=false
|
||||||
|
REASON="Fix branches must target 'dev', not '${BASE}'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
hotfix/*)
|
||||||
|
if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then
|
||||||
|
ALLOWED=false
|
||||||
|
REASON="Hotfix branches can only target 'dev' or 'main', not '${BASE}'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
alpha/*|beta/*)
|
||||||
|
if [ "$BASE" != "dev" ]; then
|
||||||
|
ALLOWED=false
|
||||||
|
REASON="Pre-release branches must target 'dev', not '${BASE}'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
rc/*)
|
||||||
|
if [ "$BASE" != "main" ]; then
|
||||||
|
ALLOWED=false
|
||||||
|
REASON="Release candidate branches must target 'main', not '${BASE}'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
dev)
|
||||||
|
if [ "$BASE" != "main" ]; then
|
||||||
|
ALLOWED=false
|
||||||
|
REASON="Dev branch can only merge into 'main', not '${BASE}'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ "$ALLOWED" = false ]; then
|
||||||
|
echo "::error::${REASON}"
|
||||||
|
echo ""
|
||||||
|
echo "## Branch Policy Violation" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "${REASON}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Branch policy: OK (${HEAD} → ${BASE})"
|
||||||
|
echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: Gitea.Workflow
|
||||||
|
# INGROUP: moko-platform.CI
|
||||||
|
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
|
||||||
|
# PATH: /templates/workflows/universal/pr-check.yml.template
|
||||||
|
# VERSION: 05.00.00
|
||||||
|
# BRIEF: PR gate — branch policy + code validation before merge
|
||||||
|
|
||||||
|
name: "Universal: PR Check"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened, edited]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# ── Branch Policy ──────────────────────────────────────────────────────
|
||||||
|
branch-policy:
|
||||||
|
name: Branch Policy
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check branch merge target
|
||||||
|
run: |
|
||||||
|
HEAD="${{ github.head_ref }}"
|
||||||
|
BASE="${{ github.base_ref }}"
|
||||||
|
|
||||||
|
echo "PR: ${HEAD} → ${BASE}"
|
||||||
|
|
||||||
|
ALLOWED=true
|
||||||
|
REASON=""
|
||||||
|
|
||||||
|
case "$HEAD" in
|
||||||
|
feature/*|feat/*)
|
||||||
|
if [ "$BASE" != "dev" ]; then
|
||||||
|
ALLOWED=false
|
||||||
|
REASON="Feature branches must target 'dev', not '${BASE}'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
fix/*|bugfix/*)
|
||||||
|
if [ "$BASE" != "dev" ]; then
|
||||||
|
ALLOWED=false
|
||||||
|
REASON="Fix branches must target 'dev', not '${BASE}'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
patch/*)
|
||||||
|
if [ "$BASE" != "dev" ] && [ "$BASE" != "rc" ]; then
|
||||||
|
ALLOWED=false
|
||||||
|
REASON="Patch branches must target 'dev' or 'rc', not '${BASE}'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
hotfix/*)
|
||||||
|
if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then
|
||||||
|
ALLOWED=false
|
||||||
|
REASON="Hotfix branches can only target 'dev' or 'main', not '${BASE}'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
rc)
|
||||||
|
if [ "$BASE" != "main" ]; then
|
||||||
|
ALLOWED=false
|
||||||
|
REASON="RC branch can only merge into 'main', not '${BASE}'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
dev)
|
||||||
|
if [ "$BASE" != "main" ]; then
|
||||||
|
ALLOWED=false
|
||||||
|
REASON="Dev branch can only merge into 'main', not '${BASE}'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ "$ALLOWED" = false ]; then
|
||||||
|
echo "::error::${REASON}"
|
||||||
|
echo "## Branch Policy Violation" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "${REASON}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Branch policy: OK (${HEAD} → ${BASE})"
|
||||||
|
echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
# ── Code Validation ────────────────────────────────────────────────────
|
||||||
|
validate:
|
||||||
|
name: Validate PR
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Detect platform
|
||||||
|
id: platform
|
||||||
|
run: |
|
||||||
|
# Read platform from XML manifest (<platform> tag) or plain text fallback
|
||||||
|
PLATFORM=$(sed -n 's/.*<platform>\([^<]*\)<\/platform>.*/\1/p' .mokogitea/manifest.xml 2>/dev/null | head -1)
|
||||||
|
[ -z "$PLATFORM" ] && PLATFORM=$(cat .mokogitea/manifest.xml 2>/dev/null | tr -d '[:space:]')
|
||||||
|
[ -z "$PLATFORM" ] && PLATFORM="generic"
|
||||||
|
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr'
|
||||||
|
run: |
|
||||||
|
if ! command -v php &> /dev/null; then
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: PHP syntax check
|
||||||
|
if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr'
|
||||||
|
run: |
|
||||||
|
ERRORS=0
|
||||||
|
while IFS= read -r -d '' file; do
|
||||||
|
if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
done < <(find . -name "*.php" -not -path "./.git/*" -not -path "./vendor/*" -print0)
|
||||||
|
echo "PHP lint: ${ERRORS} error(s)"
|
||||||
|
[ "$ERRORS" -eq 0 ] || { echo "::error::PHP syntax errors found"; exit 1; }
|
||||||
|
|
||||||
|
- name: Validate platform manifest
|
||||||
|
run: |
|
||||||
|
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||||
|
case "$PLATFORM" in
|
||||||
|
joomla)
|
||||||
|
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
||||||
|
if [ -z "$MANIFEST" ]; then
|
||||||
|
echo "::warning::No Joomla manifest found (WaaS site)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Manifest: ${MANIFEST}"
|
||||||
|
if command -v php &> /dev/null; then
|
||||||
|
php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('$MANIFEST'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::Manifest XML is malformed"; exit 1; }
|
||||||
|
fi
|
||||||
|
for ELEMENT in name version description; do
|
||||||
|
grep -q "<${ELEMENT}>" "$MANIFEST" || { echo "::error::Missing <${ELEMENT}> in manifest"; exit 1; }
|
||||||
|
done
|
||||||
|
echo "Joomla manifest valid"
|
||||||
|
;;
|
||||||
|
dolibarr)
|
||||||
|
MOD_FILE=$(find . -maxdepth 4 -name "mod*.class.php" ! -path "./.git/*" -exec grep -l 'extends DolibarrModules' {} \; 2>/dev/null | head -1)
|
||||||
|
if [ -z "$MOD_FILE" ]; then
|
||||||
|
echo "::error::No mod*.class.php found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Dolibarr module: ${MOD_FILE}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Generic platform — no manifest validation"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
- name: Check update stream format
|
||||||
|
run: |
|
||||||
|
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||||
|
case "$PLATFORM" in
|
||||||
|
joomla)
|
||||||
|
if [ -f "updates.xml" ]; then
|
||||||
|
if command -v php &> /dev/null; then
|
||||||
|
php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('updates.xml'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::updates.xml is malformed"; exit 1; }
|
||||||
|
fi
|
||||||
|
echo "updates.xml valid"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
dolibarr)
|
||||||
|
[ -f "update.txt" ] && echo "update.txt present" || echo "::warning::No update.txt"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
- name: Check changelog has unreleased entry
|
||||||
|
run: |
|
||||||
|
if [ ! -f "CHANGELOG.md" ]; then
|
||||||
|
echo "::warning::No CHANGELOG.md found"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
# Check for content under [Unreleased] section
|
||||||
|
if ! grep -q "## \[Unreleased\]" CHANGELOG.md; then
|
||||||
|
echo "::error::CHANGELOG.md missing [Unreleased] section"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# Check there's at least one entry (Added/Changed/Fixed/Removed) under Unreleased
|
||||||
|
UNRELEASED_CONTENT=$(sed -n '/## \[Unreleased\]/,/## \[/p' CHANGELOG.md | grep -cE '^\s*-\s' || true)
|
||||||
|
if [ "$UNRELEASED_CONTENT" -eq 0 ]; then
|
||||||
|
echo "::error::CHANGELOG.md [Unreleased] section has no entries. Add a changelog entry describing your changes."
|
||||||
|
echo "## Changelog Check: Failed" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "The \`[Unreleased]\` section in CHANGELOG.md has no entries." >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Add a line like \`- Description of your change\` under a heading (\`### Added\`, \`### Changed\`, \`### Fixed\`, etc.)" >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Changelog: ${UNRELEASED_CONTENT} entry/entries in [Unreleased]"
|
||||||
|
|
||||||
|
- name: Verify package source
|
||||||
|
run: |
|
||||||
|
SOURCE_DIR="src"
|
||||||
|
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||||
|
if [ ! -d "$SOURCE_DIR" ]; then
|
||||||
|
echo "::warning::No src/ or htdocs/ directory"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
FILE_COUNT=$(find "$SOURCE_DIR" -type f | wc -l)
|
||||||
|
echo "Source: ${FILE_COUNT} files"
|
||||||
|
[ "$FILE_COUNT" -gt 0 ] || { echo "::error::Source directory is empty"; exit 1; }
|
||||||
|
|
||||||
|
# ── Pre-Release RC Build ─────────────────────────────────────────────────
|
||||||
|
pre-release:
|
||||||
|
name: Build RC Package
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [branch-policy, validate]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Trigger RC pre-release
|
||||||
|
env:
|
||||||
|
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
BRANCH: ${{ github.head_ref }}
|
||||||
|
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
run: |
|
||||||
|
curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
|
||||||
|
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
# BRIEF: Auto-build RC release on PR to main, update RC update stream
|
||||||
|
|
||||||
|
name: "PR RC Release"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize]
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
REGISTRY: git.mokoconsulting.tech
|
||||||
|
IMAGE: mokoconsulting/mokogitea
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
rc-release:
|
||||||
|
name: Build RC Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check target branch
|
||||||
|
id: guard
|
||||||
|
env:
|
||||||
|
BASE_BRANCH: ${{ github.base_ref }}
|
||||||
|
run: |
|
||||||
|
echo "PR target: ${BASE_BRANCH}"
|
||||||
|
if [ "$BASE_BRANCH" != "main" ]; then
|
||||||
|
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Skipping RC — only for PRs targeting main"
|
||||||
|
else
|
||||||
|
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Checkout PR branch
|
||||||
|
if: steps.guard.outputs.skip != 'true'
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Determine RC version
|
||||||
|
if: steps.guard.outputs.skip != 'true'
|
||||||
|
id: version
|
||||||
|
env:
|
||||||
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
|
run: |
|
||||||
|
BASE_VERSION=$(sed -n 's/.*<version>\(.*\)<\/version>.*/\1/p' updates.xml | head -1)
|
||||||
|
[ -z "$BASE_VERSION" ] && BASE_VERSION="04.00.00"
|
||||||
|
RC_VERSION="${BASE_VERSION}-rc.${PR_NUMBER}"
|
||||||
|
RC_TAG="v1.26.1-moko.${RC_VERSION}"
|
||||||
|
echo "version=$RC_VERSION" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "tag=$RC_TAG" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "RC version: $RC_VERSION (tag: $RC_TAG)"
|
||||||
|
|
||||||
|
- name: Update updates.xml RC channel
|
||||||
|
if: steps.guard.outputs.skip != 'true'
|
||||||
|
env:
|
||||||
|
RC_VERSION: ${{ steps.version.outputs.version }}
|
||||||
|
RC_TAG: ${{ steps.version.outputs.tag }}
|
||||||
|
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||||
|
PR_NUM: ${{ github.event.pull_request.number }}
|
||||||
|
run: |
|
||||||
|
DOCKER_TAG="${REGISTRY}/${IMAGE}:${RC_TAG}"
|
||||||
|
|
||||||
|
python3 << 'PYEOF'
|
||||||
|
import os, re
|
||||||
|
|
||||||
|
rc_version = os.environ["RC_VERSION"]
|
||||||
|
rc_tag = os.environ["RC_TAG"]
|
||||||
|
pr_url = os.environ["PR_URL"]
|
||||||
|
pr_num = os.environ["PR_NUM"]
|
||||||
|
docker_tag = os.environ["REGISTRY"] + "/" + os.environ["IMAGE"] + ":" + rc_tag
|
||||||
|
|
||||||
|
entry = f""" <update>
|
||||||
|
<name>MokoGitea</name>
|
||||||
|
<description>MokoGitea RC from PR #{pr_num}</description>
|
||||||
|
<element>mokogitea</element>
|
||||||
|
<type>application</type>
|
||||||
|
<version>{rc_version}</version>
|
||||||
|
<client>server</client>
|
||||||
|
<tags><tag>rc</tag></tags>
|
||||||
|
<infourl title="MokoGitea RC">{pr_url}</infourl>
|
||||||
|
<downloads>
|
||||||
|
<downloadurl type="full" format="docker">{docker_tag}</downloadurl>
|
||||||
|
</downloads>
|
||||||
|
<sha256></sha256>
|
||||||
|
<targetplatform name="mokogitea" version="((1\\.25\\.)|(1\\.26\\.))" />
|
||||||
|
<maintainer>Moko Consulting</maintainer>
|
||||||
|
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||||
|
</update>"""
|
||||||
|
|
||||||
|
content = open("updates.xml").read()
|
||||||
|
# Remove existing RC entry
|
||||||
|
content = re.sub(
|
||||||
|
r"\s*<update>[\s\S]*?<tag>rc</tag>[\s\S]*?</update>",
|
||||||
|
"",
|
||||||
|
content,
|
||||||
|
)
|
||||||
|
# Insert before </updates>
|
||||||
|
content = content.replace("</updates>", entry + "\n</updates>")
|
||||||
|
open("updates.xml", "w").write(content)
|
||||||
|
print(f"Updated updates.xml with RC entry: {rc_version}")
|
||||||
|
PYEOF
|
||||||
|
|
||||||
|
- name: Create RC release
|
||||||
|
if: steps.guard.outputs.skip != 'true'
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
RC_TAG: ${{ steps.version.outputs.tag }}
|
||||||
|
RC_VERSION: ${{ steps.version.outputs.version }}
|
||||||
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||||
|
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||||
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
|
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||||
|
API_BASE: https://${{ env.REGISTRY }}/api/v1/repos/${{ github.repository }}
|
||||||
|
run: |
|
||||||
|
# Delete existing RC release/tag if present
|
||||||
|
curl -s -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${API_BASE}/releases/tags/${RC_TAG}" 2>/dev/null || true
|
||||||
|
curl -s -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${API_BASE}/tags/${RC_TAG}" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Create prerelease
|
||||||
|
python3 << PYEOF
|
||||||
|
import json, os, urllib.request
|
||||||
|
|
||||||
|
api = os.environ["API_BASE"]
|
||||||
|
token = os.environ["GITEA_TOKEN"]
|
||||||
|
payload = json.dumps({
|
||||||
|
"tag_name": os.environ["RC_TAG"],
|
||||||
|
"target_commitish": os.environ["HEAD_SHA"],
|
||||||
|
"name": f"RC: {os.environ['PR_TITLE']}",
|
||||||
|
"body": f"Release candidate from PR #{os.environ['PR_NUMBER']}\n\nPR: {os.environ['PR_URL']}\nDocker: docker pull {os.environ['REGISTRY']}/{os.environ['IMAGE']}:{os.environ['RC_TAG']}",
|
||||||
|
"draft": False,
|
||||||
|
"prerelease": True,
|
||||||
|
}).encode()
|
||||||
|
|
||||||
|
req = urllib.request.Request(
|
||||||
|
f"{api}/releases",
|
||||||
|
data=payload,
|
||||||
|
headers={
|
||||||
|
"Authorization": f"token {token}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
method="POST",
|
||||||
|
)
|
||||||
|
with urllib.request.urlopen(req) as resp:
|
||||||
|
result = json.loads(resp.read())
|
||||||
|
print(f"Created RC release: {result.get('tag_name')}")
|
||||||
|
PYEOF
|
||||||
|
|
||||||
|
- name: Commit updates.xml
|
||||||
|
if: steps.guard.outputs.skip != 'true'
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
||||||
|
PR_NUM: ${{ github.event.pull_request.number }}
|
||||||
|
run: |
|
||||||
|
git config user.name "MokoGitea Bot"
|
||||||
|
git config user.email "deploy@mokoconsulting.tech"
|
||||||
|
git add updates.xml
|
||||||
|
if git diff --cached --quiet; then
|
||||||
|
echo "No changes to updates.xml"
|
||||||
|
else
|
||||||
|
git commit -m "chore(ci): update RC stream for PR #${PR_NUM}"
|
||||||
|
git push origin "HEAD:${HEAD_REF}" || echo "Push failed"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: Gitea.Workflow
|
||||||
|
# INGROUP: moko-platform.Release
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
|
# PATH: /templates/workflows/universal/pre-release.yml.template
|
||||||
|
# VERSION: 05.01.00
|
||||||
|
# BRIEF: Manual pre-release -- builds dev/alpha/beta/rc packages from any branch
|
||||||
|
|
||||||
|
name: "Universal: Pre-Release"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [closed]
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
stability:
|
||||||
|
description: 'Pre-release channel'
|
||||||
|
required: true
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- development
|
||||||
|
- alpha
|
||||||
|
- beta
|
||||||
|
- release-candidate
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||||
|
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: "Build Pre-Release (${{ inputs.stability || 'development' }})"
|
||||||
|
runs-on: release
|
||||||
|
if: >-
|
||||||
|
github.event_name == 'workflow_dispatch' ||
|
||||||
|
(github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'dev')
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
|
||||||
|
- name: Setup moko-platform tools
|
||||||
|
env:
|
||||||
|
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||||
|
run: |
|
||||||
|
if ! command -v composer &> /dev/null; then
|
||||||
|
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
# Always fetch latest CLI tools — never use stale cache from previous runs
|
||||||
|
rm -rf /tmp/moko-platform-api
|
||||||
|
git clone --depth 1 --branch main --quiet \
|
||||||
|
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||||
|
/tmp/moko-platform-api
|
||||||
|
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
|
||||||
|
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Detect platform
|
||||||
|
id: platform
|
||||||
|
run: |
|
||||||
|
php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
||||||
|
|
||||||
|
- name: Resolve metadata and bump version
|
||||||
|
id: meta
|
||||||
|
run: |
|
||||||
|
STABILITY="${{ inputs.stability || 'development' }}"
|
||||||
|
|
||||||
|
case "$STABILITY" in
|
||||||
|
development) SUFFIX="-dev"; TAG="development" ;;
|
||||||
|
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
|
||||||
|
beta) SUFFIX="-beta"; TAG="beta" ;;
|
||||||
|
release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Read current version (bump already handled by push workflow)
|
||||||
|
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null)
|
||||||
|
[ -z "$VERSION" ] && VERSION="00.00.01"
|
||||||
|
|
||||||
|
# Strip any existing suffix from version before applying stability
|
||||||
|
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
|
||||||
|
|
||||||
|
php ${MOKO_CLI}/version_set_platform.php \
|
||||||
|
--path . --version "$VERSION" --branch "${{ github.ref_name }}" --stability "$STABILITY" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Verify version consistency across all files
|
||||||
|
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
||||||
|
|
||||||
|
# Update VERSION variable with suffix
|
||||||
|
if [ -n "$SUFFIX" ]; then
|
||||||
|
VERSION="${VERSION}${SUFFIX}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Commit version bump
|
||||||
|
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||||
|
git config --local user.name "gitea-actions[bot]"
|
||||||
|
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
|
git add -A
|
||||||
|
git diff --cached --quiet || {
|
||||||
|
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
|
||||||
|
git push origin HEAD 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Auto-detect element via manifest_element.php
|
||||||
|
php ${MOKO_CLI}/manifest_element.php \
|
||||||
|
--path . --version "$VERSION" --stability "$STABILITY" \
|
||||||
|
--repo "${GITEA_REPO}" --github-output
|
||||||
|
|
||||||
|
# Read back element outputs
|
||||||
|
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
||||||
|
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
||||||
|
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
||||||
|
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
|
||||||
|
|
||||||
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
|
||||||
|
|
||||||
|
- name: Create release
|
||||||
|
id: release
|
||||||
|
run: |
|
||||||
|
TAG="${{ steps.meta.outputs.tag }}"
|
||||||
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
php ${MOKO_CLI}/release_create.php \
|
||||||
|
--path . --version "$VERSION" --tag "$TAG" \
|
||||||
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||||
|
--repo "${GITEA_REPO}" --branch dev --prerelease
|
||||||
|
|
||||||
|
- name: Build package and upload
|
||||||
|
id: package
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
|
TAG="${{ steps.meta.outputs.tag }}"
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
php ${MOKO_CLI}/release_package.php \
|
||||||
|
--path . --version "$VERSION" --tag "$TAG" \
|
||||||
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||||
|
--repo "${GITEA_REPO}" --output /tmp || true
|
||||||
|
|
||||||
|
- name: Update updates.xml
|
||||||
|
if: steps.platform.outputs.platform == 'joomla'
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
|
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||||
|
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
||||||
|
|
||||||
|
if [ ! -f "updates.xml" ]; then
|
||||||
|
echo "No updates.xml -- skipping"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
SHA_FLAG=""
|
||||||
|
[ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}"
|
||||||
|
|
||||||
|
php ${MOKO_CLI}/updates_xml_build.php \
|
||||||
|
--path . --version "${VERSION}" --stability "${STABILITY}" \
|
||||||
|
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
|
||||||
|
${SHA_FLAG}
|
||||||
|
|
||||||
|
# Commit and push
|
||||||
|
if ! git diff --quiet updates.xml 2>/dev/null; then
|
||||||
|
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||||
|
git config --local user.name "gitea-actions[bot]"
|
||||||
|
git add updates.xml
|
||||||
|
git commit -m "chore: update ${STABILITY} channel ${VERSION} [skip ci]"
|
||||||
|
git push origin HEAD 2>&1 || echo "WARNING: push failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: "Sync updates.xml to all branches"
|
||||||
|
if: steps.platform.outputs.platform == 'joomla'
|
||||||
|
run: |
|
||||||
|
CURRENT_BRANCH="${{ github.ref_name }}"
|
||||||
|
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||||
|
git config --local user.name "gitea-actions[bot]"
|
||||||
|
|
||||||
|
for BRANCH in main dev; do
|
||||||
|
[ "$BRANCH" = "$CURRENT_BRANCH" ] && continue
|
||||||
|
echo "Syncing updates.xml -> ${BRANCH}"
|
||||||
|
git fetch origin "${BRANCH}" 2>/dev/null || continue
|
||||||
|
git checkout "origin/${BRANCH}" -- updates.xml 2>/dev/null || continue
|
||||||
|
git checkout "${CURRENT_BRANCH}" -- updates.xml
|
||||||
|
if ! git diff --quiet updates.xml 2>/dev/null; then
|
||||||
|
git add updates.xml
|
||||||
|
git commit -m "chore: sync updates.xml from ${CURRENT_BRANCH} [skip ci]"
|
||||||
|
git push origin HEAD:refs/heads/${BRANCH} 2>&1 || echo "WARNING: push to ${BRANCH} failed"
|
||||||
|
fi
|
||||||
|
git checkout "${CURRENT_BRANCH}" 2>/dev/null
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: "Delete lesser pre-release channels (cascade)"
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
|
|
||||||
|
php ${MOKO_CLI}/release_cascade.php \
|
||||||
|
--stability "${{ steps.meta.outputs.stability }}" \
|
||||||
|
--token "${TOKEN}" \
|
||||||
|
--api-base "${API_BASE}"
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
|
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||||
|
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
||||||
|
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
||||||
|
echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Test workflow to verify .mokogitea/ directory is discovered
|
||||||
|
name: Test .mokogitea workflows
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Verify .mokogitea
|
||||||
|
run: echo "This workflow ran from .mokogitea/workflows/ — feature works!"
|
||||||
@@ -0,0 +1,312 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: Gitea.Workflow
|
||||||
|
# INGROUP: moko-platform.Universal
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
|
# PATH: /templates/workflows/update-server.yml
|
||||||
|
# VERSION: 05.00.00
|
||||||
|
# BRIEF: Pre-release build + update server XML for dev/alpha/beta/rc branches
|
||||||
|
#
|
||||||
|
# Thin wrapper around moko-platform CLI tools.
|
||||||
|
# Builds packages, updates updates.xml, and optionally deploys via SFTP.
|
||||||
|
#
|
||||||
|
# Joomla filters update entries by the user's "Minimum Stability" setting.
|
||||||
|
|
||||||
|
name: "Update Server"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'dev'
|
||||||
|
- 'dev/**'
|
||||||
|
- 'alpha/**'
|
||||||
|
- 'beta/**'
|
||||||
|
- 'rc/**'
|
||||||
|
paths:
|
||||||
|
- 'src/**'
|
||||||
|
- 'htdocs/**'
|
||||||
|
pull_request:
|
||||||
|
types: [closed]
|
||||||
|
branches:
|
||||||
|
- 'dev'
|
||||||
|
- 'dev/**'
|
||||||
|
- 'alpha/**'
|
||||||
|
- 'beta/**'
|
||||||
|
- 'rc/**'
|
||||||
|
paths:
|
||||||
|
- 'src/**'
|
||||||
|
- 'htdocs/**'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
stability:
|
||||||
|
description: 'Stability tag'
|
||||||
|
required: true
|
||||||
|
default: 'development'
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- development
|
||||||
|
- alpha
|
||||||
|
- beta
|
||||||
|
- rc
|
||||||
|
- stable
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||||
|
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-xml:
|
||||||
|
name: Update Server
|
||||||
|
runs-on: release
|
||||||
|
if: >-
|
||||||
|
github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' || github.event_name == 'push'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup moko-platform tools
|
||||||
|
env:
|
||||||
|
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||||
|
COMPOSER_AUTH: '{"http-basic":{"git.mokoconsulting.tech":{"username":"token","password":"${{ secrets.MOKOGITEA_TOKEN }}"}}}'
|
||||||
|
run: |
|
||||||
|
if ! command -v composer &> /dev/null; then
|
||||||
|
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
# Always fetch latest CLI tools — never use stale cache from previous runs
|
||||||
|
rm -rf /tmp/moko-platform
|
||||||
|
git clone --depth 1 --branch main --quiet \
|
||||||
|
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||||
|
/tmp/moko-platform 2>/dev/null || true
|
||||||
|
if [ -d "/tmp/moko-platform" ] && [ -f "/tmp/moko-platform/composer.json" ]; then
|
||||||
|
cd /tmp/moko-platform && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
echo "MOKO_CLI=/tmp/moko-platform/cli" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Detect platform
|
||||||
|
id: platform
|
||||||
|
run: php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
||||||
|
|
||||||
|
- name: Resolve stability and bump version
|
||||||
|
id: meta
|
||||||
|
run: |
|
||||||
|
BRANCH="${{ github.ref_name }}"
|
||||||
|
|
||||||
|
# Configure git for bot pushes
|
||||||
|
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||||
|
git config --local user.name "gitea-actions[bot]"
|
||||||
|
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
|
|
||||||
|
# Auto-bump patch version
|
||||||
|
php ${MOKO_CLI}/version_bump.php --path . 2>/dev/null || true
|
||||||
|
|
||||||
|
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "0.0.0")
|
||||||
|
|
||||||
|
# Strip any existing suffix before applying stability
|
||||||
|
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
|
||||||
|
|
||||||
|
# Determine stability from branch or manual input
|
||||||
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||||
|
STABILITY="${{ inputs.stability }}"
|
||||||
|
elif [[ "$BRANCH" == rc/* ]]; then
|
||||||
|
STABILITY="rc"
|
||||||
|
elif [[ "$BRANCH" == beta/* ]]; then
|
||||||
|
STABILITY="beta"
|
||||||
|
elif [[ "$BRANCH" == alpha/* ]]; then
|
||||||
|
STABILITY="alpha"
|
||||||
|
else
|
||||||
|
STABILITY="development"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Version suffix per stability stream
|
||||||
|
case "$STABILITY" in
|
||||||
|
development) SUFFIX="-dev"; TAG="development" ;;
|
||||||
|
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
|
||||||
|
beta) SUFFIX="-beta"; TAG="beta" ;;
|
||||||
|
rc) SUFFIX="-rc"; TAG="release-candidate" ;;
|
||||||
|
*) SUFFIX=""; TAG="stable" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Propagate version with stability suffix to all manifest files
|
||||||
|
php ${MOKO_CLI}/version_set_platform.php \
|
||||||
|
--path . --version "$VERSION" --branch "$BRANCH" --stability "$STABILITY" 2>/dev/null || true
|
||||||
|
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
||||||
|
|
||||||
|
# Re-read version (now includes suffix from version_set_platform)
|
||||||
|
if [ -n "$SUFFIX" ]; then
|
||||||
|
VERSION="${VERSION}${SUFFIX}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "display_version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
# Commit version bump if changed
|
||||||
|
git add -A
|
||||||
|
git diff --cached --quiet || {
|
||||||
|
git commit -m "chore(version): auto-bump ${VERSION} [skip ci]" \
|
||||||
|
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||||
|
git push
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Create release and upload package
|
||||||
|
id: package
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
|
TAG="${{ steps.meta.outputs.tag }}"
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
|
||||||
|
# Create or update Gitea release
|
||||||
|
php ${MOKO_CLI}/release_create.php \
|
||||||
|
--path . --version "$VERSION" --tag "$TAG" \
|
||||||
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||||
|
--repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease
|
||||||
|
|
||||||
|
# Build package and upload
|
||||||
|
php ${MOKO_CLI}/release_package.php \
|
||||||
|
--path . --version "$VERSION" --tag "$TAG" \
|
||||||
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||||
|
--repo "${GITEA_REPO}" --output /tmp || true
|
||||||
|
|
||||||
|
- name: Update updates.xml
|
||||||
|
if: steps.platform.outputs.platform == 'joomla'
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
|
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||||
|
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
||||||
|
|
||||||
|
if [ ! -f "updates.xml" ]; then
|
||||||
|
echo "No updates.xml — skipping"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
SHA_FLAG=""
|
||||||
|
[ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}"
|
||||||
|
|
||||||
|
php ${MOKO_CLI}/updates_xml_build.php \
|
||||||
|
--path . --version "${VERSION}" --stability "${STABILITY}" \
|
||||||
|
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
|
||||||
|
${SHA_FLAG}
|
||||||
|
|
||||||
|
# Commit and push updates.xml
|
||||||
|
git add updates.xml
|
||||||
|
git diff --cached --quiet || {
|
||||||
|
git commit -m "chore: update ${STABILITY} channel ${VERSION} [skip ci]"
|
||||||
|
git push
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Sync updates.xml to main
|
||||||
|
if: github.ref_name != 'main' && steps.platform.outputs.platform == 'joomla'
|
||||||
|
run: |
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
GITEA_TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
|
|
||||||
|
FILE_SHA=$(curl -sf -H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${API_BASE}/contents/updates.xml?ref=main" | python3 -c "import sys,json; print(json.load(sys.stdin).get('sha',''))" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [ -n "$FILE_SHA" ] && [ -f "updates.xml" ]; then
|
||||||
|
python3 -c "
|
||||||
|
import base64, json, urllib.request, sys
|
||||||
|
with open('updates.xml', 'rb') as f:
|
||||||
|
content = base64.b64encode(f.read()).decode()
|
||||||
|
payload = json.dumps({
|
||||||
|
'content': content,
|
||||||
|
'sha': '${FILE_SHA}',
|
||||||
|
'message': 'chore: sync updates.xml from ${{ steps.meta.outputs.stability }} [skip ci]',
|
||||||
|
'branch': 'main'
|
||||||
|
}).encode()
|
||||||
|
req = urllib.request.Request(
|
||||||
|
'${API_BASE}/contents/updates.xml',
|
||||||
|
data=payload, method='PUT',
|
||||||
|
headers={
|
||||||
|
'Authorization': 'token ${GITEA_TOKEN}',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
})
|
||||||
|
try:
|
||||||
|
urllib.request.urlopen(req)
|
||||||
|
print('updates.xml synced to main')
|
||||||
|
except Exception as e:
|
||||||
|
print(f'WARNING: sync to main failed: {e}', file=sys.stderr)
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: SFTP deploy to dev server
|
||||||
|
if: contains(github.ref, 'dev/') || github.ref == 'refs/heads/dev'
|
||||||
|
env:
|
||||||
|
DEV_HOST: ${{ vars.DEV_FTP_HOST }}
|
||||||
|
DEV_PATH: ${{ vars.DEV_FTP_PATH }}
|
||||||
|
DEV_SUFFIX: ${{ vars.DEV_FTP_SUFFIX }}
|
||||||
|
DEV_USER: ${{ vars.DEV_FTP_USERNAME }}
|
||||||
|
DEV_PORT: ${{ vars.DEV_FTP_PORT }}
|
||||||
|
DEV_KEY: ${{ secrets.DEV_FTP_KEY }}
|
||||||
|
DEV_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
# Permission check: admin or maintain role required
|
||||||
|
ACTOR="${{ github.actor }}"
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
|
||||||
|
PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||||
|
"${API_BASE}/collaborators/${ACTOR}/permission" 2>/dev/null | \
|
||||||
|
python3 -c "import sys,json; print(json.load(sys.stdin).get('permission','read'))" 2>/dev/null || echo "read")
|
||||||
|
case "$PERMISSION" in
|
||||||
|
admin|maintain|write) ;;
|
||||||
|
*)
|
||||||
|
echo "Deploy denied: ${ACTOR} has '${PERMISSION}' — requires admin, maintain, or write"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
[ -z "$DEV_HOST" ] || [ -z "$DEV_PATH" ] && { echo "DEV FTP not configured — skipping SFTP"; exit 0; }
|
||||||
|
|
||||||
|
SOURCE_DIR="src"
|
||||||
|
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||||
|
[ ! -d "$SOURCE_DIR" ] && exit 0
|
||||||
|
|
||||||
|
PORT="${DEV_PORT:-22}"
|
||||||
|
REMOTE="${DEV_PATH%/}"
|
||||||
|
[ -n "$DEV_SUFFIX" ] && REMOTE="${REMOTE}/${DEV_SUFFIX#/}"
|
||||||
|
|
||||||
|
printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \
|
||||||
|
"$DEV_HOST" "$PORT" "$DEV_USER" "$REMOTE" > /tmp/sftp-config.json
|
||||||
|
if [ -n "$DEV_KEY" ]; then
|
||||||
|
echo "$DEV_KEY" > /tmp/deploy_key && chmod 600 /tmp/deploy_key
|
||||||
|
printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json
|
||||||
|
else
|
||||||
|
printf ',"password":"%s"}' "$DEV_PASS" >> /tmp/sftp-config.json
|
||||||
|
fi
|
||||||
|
|
||||||
|
PLATFORM=$(php ${MOKO_CLI}/platform_detect.php --path . 2>/dev/null || true)
|
||||||
|
if [ "$PLATFORM" = "waas-component" ] && [ -f "${MOKO_CLI}/../deploy/deploy-joomla.php" ]; then
|
||||||
|
php ${MOKO_CLI}/../deploy/deploy-joomla.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
||||||
|
elif [ -f "${MOKO_CLI}/../deploy/deploy-sftp.php" ]; then
|
||||||
|
php ${MOKO_CLI}/../deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
||||||
|
fi
|
||||||
|
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
||||||
|
echo "SFTP deploy to dev complete" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
|
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||||
|
DISPLAY="${{ steps.meta.outputs.display_version }}"
|
||||||
|
echo "## Update Server" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Stability | \`${STABILITY}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Version | \`${DISPLAY}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
# BRIEF: Sync upstream Gitea bug fixes into MokoGitea issue tracker
|
||||||
|
|
||||||
|
name: Upstream Bug Sync
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 8 * * *' # daily at 08:00 UTC
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
days_back:
|
||||||
|
description: 'How many days back to scan (default: 7)'
|
||||||
|
required: false
|
||||||
|
default: '7'
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
sync:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Sync upstream bugs
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GH_MIRROR_TOKEN }}
|
||||||
|
MOKOGITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
MOKOGITEA_URL: https://git.mokoconsulting.tech
|
||||||
|
MOKOGITEA_REPO: MokoConsulting/MokoGitea
|
||||||
|
UPSTREAM_BRANCH: release/v1.26
|
||||||
|
DAYS_BACK: ${{ github.event.inputs.days_back || '7' }}
|
||||||
|
run: |
|
||||||
|
python3 << 'PYEOF'
|
||||||
|
import json, os, re, sys, urllib.parse, urllib.request
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
|
GH_TOKEN = os.environ["GH_TOKEN"]
|
||||||
|
MOKO_TOKEN = os.environ["MOKOGITEA_TOKEN"]
|
||||||
|
MOKO_URL = os.environ["MOKOGITEA_URL"]
|
||||||
|
MOKO_REPO = os.environ["MOKOGITEA_REPO"]
|
||||||
|
BRANCH = os.environ["UPSTREAM_BRANCH"]
|
||||||
|
DAYS = int(os.environ.get("DAYS_BACK", "7"))
|
||||||
|
|
||||||
|
# Label IDs in MokoGitea
|
||||||
|
LABELS = {
|
||||||
|
"type_bug": 5757, "upstream": 5758, "security": 5032,
|
||||||
|
"critical": 5018, "high": 5019, "medium": 5020, "low": 5021,
|
||||||
|
}
|
||||||
|
|
||||||
|
def gh_get(url):
|
||||||
|
req = urllib.request.Request(url, headers={
|
||||||
|
"Authorization": f"token {GH_TOKEN}",
|
||||||
|
"Accept": "application/vnd.github.v3+json",
|
||||||
|
})
|
||||||
|
with urllib.request.urlopen(req) as r:
|
||||||
|
return json.loads(r.read())
|
||||||
|
|
||||||
|
def moko_get(path):
|
||||||
|
req = urllib.request.Request(f"{MOKO_URL}/api/v1/{path}", headers={
|
||||||
|
"Authorization": f"token {MOKO_TOKEN}",
|
||||||
|
})
|
||||||
|
with urllib.request.urlopen(req) as r:
|
||||||
|
return json.loads(r.read())
|
||||||
|
|
||||||
|
def moko_post(path, data):
|
||||||
|
payload = json.dumps(data).encode()
|
||||||
|
req = urllib.request.Request(f"{MOKO_URL}/api/v1/{path}",
|
||||||
|
data=payload, method="POST", headers={
|
||||||
|
"Authorization": f"token {MOKO_TOKEN}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
})
|
||||||
|
with urllib.request.urlopen(req) as r:
|
||||||
|
return json.loads(r.read())
|
||||||
|
|
||||||
|
# ── Step 1: Find recently merged upstream PRs ──
|
||||||
|
since = (datetime.now(timezone.utc) - timedelta(days=DAYS)).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
query = f"repo:go-gitea/gitea is:pr is:merged base:{BRANCH} merged:>={since}"
|
||||||
|
encoded = urllib.parse.quote(query)
|
||||||
|
print(f"Scanning: {query}")
|
||||||
|
|
||||||
|
result = gh_get(f"https://api.github.com/search/issues?q={encoded}&per_page=100&sort=updated&order=desc")
|
||||||
|
total = result["total_count"]
|
||||||
|
print(f"Found {total} merged PRs in the last {DAYS} days")
|
||||||
|
|
||||||
|
if total == 0:
|
||||||
|
print("Nothing to sync.")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# ── Step 2: Filter for bug/security fixes ──
|
||||||
|
bugs = []
|
||||||
|
for pr in result["items"]:
|
||||||
|
title = pr["title"]
|
||||||
|
label_names = [l["name"].lower() for l in pr.get("labels", [])]
|
||||||
|
is_fix = title.lower().startswith("fix")
|
||||||
|
is_security = any("security" in l for l in label_names) or "[security]" in title.lower()
|
||||||
|
is_bug = any("bug" in l for l in label_names)
|
||||||
|
|
||||||
|
if not (is_fix or is_security or is_bug):
|
||||||
|
continue
|
||||||
|
|
||||||
|
refs = re.findall(r"#(\d+)", title)
|
||||||
|
severity = "critical" if is_security and "[security]" in title.lower() else \
|
||||||
|
"high" if is_security else "medium"
|
||||||
|
|
||||||
|
bugs.append({
|
||||||
|
"number": pr["number"], "title": title, "url": pr["html_url"],
|
||||||
|
"severity": severity, "is_security": is_security, "refs": refs,
|
||||||
|
"merged": pr.get("pull_request", {}).get("merged_at", "")[:10],
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f"Filtered to {len(bugs)} bug/security fixes")
|
||||||
|
if not bugs:
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# ── Step 3: Collect already-tracked PR numbers ──
|
||||||
|
tracked = set()
|
||||||
|
for state in ["open", "closed"]:
|
||||||
|
try:
|
||||||
|
issues = moko_get(f"repos/{MOKO_REPO}/issues?state={state}&type=issues&limit=50&labels=upstream")
|
||||||
|
for iss in issues:
|
||||||
|
text = (iss.get("body") or "") + " " + (iss.get("title") or "")
|
||||||
|
tracked.update(re.findall(r"(?:#|/pull/)(\d{4,})", text))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print(f"Already tracked: {len(tracked)} upstream PRs")
|
||||||
|
|
||||||
|
# ── Step 4: Create issues for new bugs ──
|
||||||
|
created = skipped = errors = 0
|
||||||
|
for bug in bugs:
|
||||||
|
if any(r in tracked for r in bug["refs"]):
|
||||||
|
print(f" SKIP #{bug['number']}: {bug['title'][:55]} (tracked)")
|
||||||
|
skipped += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
labels = [LABELS["type_bug"], LABELS["upstream"], LABELS[bug["severity"]]]
|
||||||
|
if bug["is_security"]:
|
||||||
|
labels.append(LABELS["security"])
|
||||||
|
|
||||||
|
body = (
|
||||||
|
f"## Summary\n\n"
|
||||||
|
f"Upstream bug fix merged into `{BRANCH}`.\n\n"
|
||||||
|
f"## Upstream Reference\n\n"
|
||||||
|
f"- PR: {bug['url']}\n"
|
||||||
|
f"- Merged: {bug['merged']}\n"
|
||||||
|
f"- Branch: {BRANCH}\n\n"
|
||||||
|
f"## Severity: {bug['severity'].title()}"
|
||||||
|
f"{' (security)' if bug['is_security'] else ''}\n\n"
|
||||||
|
f"## Action\n\n"
|
||||||
|
f"Cherry-pick from upstream `{BRANCH}` branch.\n\n"
|
||||||
|
f"---\n"
|
||||||
|
f"*Auto-created by upstream-bug-sync workflow*\n"
|
||||||
|
f"*Authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>*"
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
iss = moko_post(f"repos/{MOKO_REPO}/issues", {
|
||||||
|
"title": bug["title"], "body": body, "labels": labels,
|
||||||
|
})
|
||||||
|
print(f" CREATED #{iss['number']}: {bug['title'][:55]}")
|
||||||
|
created += 1
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ERROR #{bug['number']}: {e}")
|
||||||
|
errors += 1
|
||||||
|
|
||||||
|
print(f"\n=== Done: {created} created, {skipped} skipped, {errors} errors ===")
|
||||||
|
PYEOF
|
||||||
@@ -2,10 +2,15 @@
|
|||||||
- Run `make fmt` to format `.go` files, and run `make lint-go` to lint them
|
- Run `make fmt` to format `.go` files, and run `make lint-go` to lint them
|
||||||
- Run `make lint-js` to lint `.ts` files
|
- Run `make lint-js` to lint `.ts` files
|
||||||
- Run `make tidy` after any `go.mod` changes
|
- Run `make tidy` after any `go.mod` changes
|
||||||
|
- Run single go tests with `go test -run '^TestName$' ./modulepath/`
|
||||||
|
- Run single js test files with `pnpm exec vitest <path-filter>`
|
||||||
|
- Run single playwright e2e test files with `GITEA_TEST_E2E_FLAGS='<filepath>' make test-e2e`
|
||||||
- Add the current year into the copyright header of new `.go` files
|
- Add the current year into the copyright header of new `.go` files
|
||||||
- Ensure no trailing whitespace in edited files
|
- Ensure no trailing whitespace in edited files
|
||||||
|
- Use Conventional Commits format for commit messages and PR titles (e.g. `type(scope): subject`)
|
||||||
- Never force-push, amend, or squash unless asked. Use new commits and normal push for pull request updates
|
- Never force-push, amend, or squash unless asked. Use new commits and normal push for pull request updates
|
||||||
- Preserve existing code comments, do not remove or rewrite comments that are still relevant
|
- Preserve existing code comments, do not remove or rewrite comments that are still relevant
|
||||||
- In TypeScript, use `!` (non-null assertion) instead of `?.`/`??` when a value is known to always exist
|
- In TypeScript, use `!` (non-null assertion) instead of `?.`/`??` when a value is known to always exist
|
||||||
|
- For CSS layout, prefer `flex-*` helpers over per-child `tw-ml-*` / `tw-mr-*` margins; fall back to `tw-*` utilities when specificity requires `!important`
|
||||||
- Include authorship attribution in issue and pull request comments
|
- Include authorship attribution in issue and pull request comments
|
||||||
- Add `Co-Authored-By` lines to all commits, indicating name and model used
|
- Add `Co-Authored-By` lines to all commits, indicating name and model used
|
||||||
|
|||||||
+69
-5374
File diff suppressed because it is too large
Load Diff
+109
-578
@@ -1,630 +1,161 @@
|
|||||||
# Contribution Guidelines
|
# Contributing to Moko Consulting Projects
|
||||||
|
|
||||||
<details><summary>Table of Contents</summary>
|
Thank you for your interest in contributing. All Moko Consulting repositories follow this universal workflow and version policy.
|
||||||
|
|
||||||
- [Contribution Guidelines](#contribution-guidelines)
|
## Branching Workflow
|
||||||
- [Introduction](#introduction)
|
|
||||||
- [AI Contribution Policy](#ai-contribution-policy)
|
|
||||||
- [Issues](#issues)
|
|
||||||
- [How to report issues](#how-to-report-issues)
|
|
||||||
- [Types of issues](#types-of-issues)
|
|
||||||
- [Discuss your design before the implementation](#discuss-your-design-before-the-implementation)
|
|
||||||
- [Issue locking](#issue-locking)
|
|
||||||
- [Building Gitea](#building-gitea)
|
|
||||||
- [Dependencies](#dependencies)
|
|
||||||
- [Backend](#backend)
|
|
||||||
- [Frontend](#frontend)
|
|
||||||
- [Design guideline](#design-guideline)
|
|
||||||
- [Styleguide](#styleguide)
|
|
||||||
- [Copyright](#copyright)
|
|
||||||
- [Testing](#testing)
|
|
||||||
- [Translation](#translation)
|
|
||||||
- [Code review](#code-review)
|
|
||||||
- [Pull request format](#pull-request-format)
|
|
||||||
- [PR title and summary](#pr-title-and-summary)
|
|
||||||
- [Milestone](#milestone)
|
|
||||||
- [Labels](#labels)
|
|
||||||
- [Breaking PRs](#breaking-prs)
|
|
||||||
- [What is a breaking PR?](#what-is-a-breaking-pr)
|
|
||||||
- [How to handle breaking PRs?](#how-to-handle-breaking-prs)
|
|
||||||
- [Maintaining open PRs](#maintaining-open-prs)
|
|
||||||
- [Getting PRs merged](#getting-prs-merged)
|
|
||||||
- [Final call](#final-call)
|
|
||||||
- [Commit messages](#commit-messages)
|
|
||||||
- [PR Co-authors](#pr-co-authors)
|
|
||||||
- [PRs targeting `main`](#prs-targeting-main)
|
|
||||||
- [Backport PRs](#backport-prs)
|
|
||||||
- [Documentation](#documentation)
|
|
||||||
- [API v1](#api-v1)
|
|
||||||
- [GitHub API compatibility](#github-api-compatibility)
|
|
||||||
- [Adding/Maintaining API routes](#addingmaintaining-api-routes)
|
|
||||||
- [When to use what HTTP method](#when-to-use-what-http-method)
|
|
||||||
- [Requirements for API routes](#requirements-for-api-routes)
|
|
||||||
- [Backports and Frontports](#backports-and-frontports)
|
|
||||||
- [What is backported?](#what-is-backported)
|
|
||||||
- [How to backport?](#how-to-backport)
|
|
||||||
- [Format of backport PRs](#format-of-backport-prs)
|
|
||||||
- [Frontports](#frontports)
|
|
||||||
- [Developer Certificate of Origin (DCO)](#developer-certificate-of-origin-dco)
|
|
||||||
- [Release Cycle](#release-cycle)
|
|
||||||
- [Maintainers](#maintainers)
|
|
||||||
- [Technical Oversight Committee (TOC)](#technical-oversight-committee-toc)
|
|
||||||
- [TOC election process](#toc-election-process)
|
|
||||||
- [Current TOC members](#current-toc-members)
|
|
||||||
- [Previous TOC/owners members](#previous-tocowners-members)
|
|
||||||
- [Governance Compensation](#governance-compensation)
|
|
||||||
- [TOC \& Working groups](#toc--working-groups)
|
|
||||||
- [Roadmap](#roadmap)
|
|
||||||
- [Versions](#versions)
|
|
||||||
- [Releasing Gitea](#releasing-gitea)
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Introduction
|
|
||||||
|
|
||||||
This document explains how to contribute changes to the Gitea project. \
|
|
||||||
It assumes you have followed the [installation instructions](https://docs.gitea.com/category/installation). \
|
|
||||||
Sensitive security-related issues should be reported to [security@gitea.io](mailto:security@gitea.io).
|
|
||||||
|
|
||||||
For configuring IDEs for Gitea development, see the [contributed IDE configurations](contrib/ide/).
|
|
||||||
|
|
||||||
## AI Contribution Policy
|
|
||||||
|
|
||||||
Contributions made with the assistance of AI tools are welcome, but contributors must use them responsibly and disclose that use clearly.
|
|
||||||
|
|
||||||
1. Review AI-generated code closely before marking a pull request ready for review.
|
|
||||||
2. Manually test the changes and add appropriate automated tests where feasible.
|
|
||||||
3. Only use AI to assist in contributions that you understand well enough to explain, defend, and revise yourself during review.
|
|
||||||
4. Disclose AI-assisted content clearly.
|
|
||||||
5. Do not use AI to reply to questions about your issue or pull request. The questions are for you, not an AI model.
|
|
||||||
6. AI may be used to help draft issues and pull requests, but contributors remain responsible for the accuracy, completeness, and intent of what they submit.
|
|
||||||
|
|
||||||
Maintainers reserve the right to close pull requests and issues that do not disclose AI assistance, that appear to be low-quality AI-generated content, or where the contributor cannot explain or defend the proposed changes themselves.
|
|
||||||
|
|
||||||
We welcome new contributors, but cannot sustain the effort of supporting contributors who primarily defer to AI rather than engaging substantively with the review process.
|
|
||||||
|
|
||||||
## Issues
|
|
||||||
|
|
||||||
### How to report issues
|
|
||||||
|
|
||||||
Please search the issues on the issue tracker with a variety of related keywords to ensure that your issue has not already been reported.
|
|
||||||
|
|
||||||
If your issue has not been reported yet, [open an issue](https://github.com/go-gitea/gitea/issues/new)
|
|
||||||
and answer the questions so we can understand and reproduce the problematic behavior. \
|
|
||||||
Please write clear and concise instructions so that we can reproduce the behavior — even if it seems obvious. \
|
|
||||||
The more detailed and specific you are, the faster we can fix the issue. \
|
|
||||||
It is really helpful if you can reproduce your problem on a site running on the latest commits, i.e. <https://demo.gitea.com>, as perhaps your problem has already been fixed on a current version. \
|
|
||||||
Please follow the guidelines described in [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html) for your report.
|
|
||||||
|
|
||||||
Please be kind—remember that Gitea comes at no cost to you, and you're getting free help.
|
|
||||||
|
|
||||||
### Types of issues
|
|
||||||
|
|
||||||
Typically, issues fall in one of the following categories:
|
|
||||||
|
|
||||||
- `bug`: Something in the frontend or backend behaves unexpectedly
|
|
||||||
- `security issue`: bug that has serious implications such as leaking another users data. Please do not file such issues on the public tracker and send a mail to security@gitea.io instead
|
|
||||||
- `feature`: Completely new functionality. You should describe this feature in enough detail that anyone who reads the issue can understand how it is supposed to be implemented
|
|
||||||
- `enhancement`: An existing feature should get an upgrade
|
|
||||||
- `refactoring`: Parts of the code base don't conform with other parts and should be changed to improve Gitea's maintainability
|
|
||||||
|
|
||||||
### Discuss your design before the implementation
|
|
||||||
|
|
||||||
We welcome submissions. \
|
|
||||||
If you want to change or add something, please let everyone know what you're working on — [file an issue](https://github.com/go-gitea/gitea/issues/new) or comment on an existing one before starting your work!
|
|
||||||
|
|
||||||
Significant changes such as new features must go through the change proposal process before they can be accepted. \
|
|
||||||
This is mainly to save yourself the trouble of implementing it, only to find out that your proposed implementation has some potential problems. \
|
|
||||||
Furthermore, this process gives everyone a chance to validate the design, helps prevent duplication of effort, and ensures that the idea fits inside
|
|
||||||
the goals for the project and tools.
|
|
||||||
|
|
||||||
Pull requests should not be the place for architecture discussions.
|
|
||||||
|
|
||||||
### Issue locking
|
|
||||||
|
|
||||||
Commenting on closed or merged issues/PRs is strongly discouraged.
|
|
||||||
Such comments will likely be overlooked as some maintainers may not view notifications on closed issues, thinking that the item is resolved.
|
|
||||||
As such, commenting on closed/merged issues/PRs may be disabled prior to the scheduled auto-locking if a discussion starts or if unrelated comments are posted.
|
|
||||||
If further discussion is needed, we encourage you to open a new issue instead and we recommend linking to the issue/PR in question for context.
|
|
||||||
|
|
||||||
## Building Gitea
|
|
||||||
|
|
||||||
See the [development setup instructions](https://docs.gitea.com/development/hacking-on-gitea).
|
|
||||||
|
|
||||||
## Dependencies
|
|
||||||
|
|
||||||
### Backend
|
|
||||||
|
|
||||||
Go dependencies are managed using [Go Modules](https://go.dev/cmd/go/#hdr-Module_maintenance). \
|
|
||||||
You can find more details in the [go mod documentation](https://go.dev/ref/mod) and the [Go Modules Wiki](https://github.com/golang/go/wiki/Modules).
|
|
||||||
|
|
||||||
Pull requests should only modify `go.mod` and `go.sum` where it is related to your change, be it a bugfix or a new feature. \
|
|
||||||
Apart from that, these files should only be modified by Pull Requests whose only purpose is to update dependencies.
|
|
||||||
|
|
||||||
The `go.mod`, `go.sum` update needs to be justified as part of the PR description,
|
|
||||||
and must be verified by the reviewers and/or merger to always reference
|
|
||||||
an existing upstream commit.
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
|
|
||||||
For the frontend, we use [npm](https://www.npmjs.com/).
|
|
||||||
|
|
||||||
The same restrictions apply for frontend dependencies as for backend dependencies, with the exceptions that the files for it are `package.json` and `package-lock.json`, and that new versions must always reference an existing version.
|
|
||||||
|
|
||||||
## Design guideline
|
|
||||||
|
|
||||||
Depending on your change, please read the
|
|
||||||
|
|
||||||
- [backend development guideline](https://docs.gitea.com/contributing/guidelines-backend)
|
|
||||||
- [frontend development guideline](https://docs.gitea.com/contributing/guidelines-frontend)
|
|
||||||
- [refactoring guideline](https://docs.gitea.com/contributing/guidelines-refactoring)
|
|
||||||
|
|
||||||
## Styleguide
|
|
||||||
|
|
||||||
You should always run `make fmt` before committing to conform to Gitea's styleguide.
|
|
||||||
|
|
||||||
## Copyright
|
|
||||||
|
|
||||||
New code files that you contribute should use the standard copyright header:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
// Copyright <current year> The Gitea Authors. All rights reserved.
|
feature/* ──PR──> dev ──draft PR──> (renamed to rc) ──merge──> main
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Afterwards, copyright should only be modified when the copyright author changes.
|
### Step by step
|
||||||
|
|
||||||
## Testing
|
1. **Create a feature branch** from `dev`:
|
||||||
|
```bash
|
||||||
|
git checkout dev && git pull
|
||||||
|
git checkout -b feature/my-change
|
||||||
|
```
|
||||||
|
|
||||||
Before submitting a pull request, run all tests to make sure your changes don't cause a regression elsewhere.
|
2. **Work and commit** on your feature branch. Push to origin.
|
||||||
|
|
||||||
Here's how to run the test suite:
|
3. **Open a PR**: `feature/my-change` → `dev`. After review and checks, merge it.
|
||||||
|
|
||||||
- code lint
|
4. **When ready for release**, open a **draft PR**: `dev` → `main`.
|
||||||
|
- This automatically renames the source branch to `rc` (release candidate)
|
||||||
|
- An RC pre-release is built and uploaded
|
||||||
|
|
||||||
| | |
|
5. **Alpha and beta branches** are created by manually renaming the branch before the RC stage:
|
||||||
| :-------------------- | :--------------------------------------------------------------------------- |
|
- Rename `dev` to `alpha` for early testing → alpha pre-release is built
|
||||||
|``make lint`` | lint everything (not needed if you only change the front- **or** backend) |
|
- Rename `alpha` to `beta` for feature-complete testing → beta pre-release is built
|
||||||
|``make lint-frontend`` | lint frontend files |
|
- When the draft PR is created, the branch is renamed to `rc`
|
||||||
|``make lint-backend`` | lint backend files |
|
|
||||||
|
|
||||||
- run tests (we suggest running them on Linux)
|
6. **Once PR checks pass** on the `rc` branch, mark the PR as ready and merge to `main`.
|
||||||
|
|
||||||
| Command | Action | |
|
7. **Merging to main** triggers the stable release pipeline:
|
||||||
| :------------------------------------------ | :------------------------------------------------------- | ------------------------------------------- |
|
- Minor version bump (e.g., `02.09.xx` → `02.10.00`)
|
||||||
|``make test[\#SpecificTestName]`` | run unit test(s) | |
|
- Stability suffix stripped (clean version)
|
||||||
|``make test-sqlite[\#SpecificTestName]`` | run [integration](tests/integration) test(s) for SQLite | [More details](tests/integration/README.md) |
|
- Gitea release created with ZIP/tar.gz packages
|
||||||
|``make test-e2e`` | run [end-to-end](tests/e2e) test(s) using Playwright | |
|
- `updates.xml` updated (Joomla extensions)
|
||||||
|
- `dev` branch recreated from `main`
|
||||||
|
|
||||||
- E2E test environment variables
|
### Branch summary
|
||||||
|
|
||||||
| Variable | Description |
|
| Branch | Purpose | Created by |
|
||||||
| :-------------------------------- | :---------------------------------------------------------- |
|
|--------|---------|-----------|
|
||||||
| ``GITEA_TEST_E2E_DEBUG`` | When set, show Gitea server output |
|
| `feature/*` | New features and fixes | Developer |
|
||||||
| ``GITEA_TEST_E2E_FLAGS`` | Additional flags passed to Playwright, for example ``--ui`` |
|
| `dev` | Integration branch | Auto-recreated after release |
|
||||||
| ``GITEA_TEST_E2E_TIMEOUT_FACTOR`` | Timeout multiplier (default: 3 on CI, 1 locally) |
|
| `alpha` | Alpha pre-release testing | Manual rename from `dev` |
|
||||||
|
| `beta` | Beta pre-release testing | Manual rename from `alpha` |
|
||||||
|
| `rc` | Release candidate | Auto-renamed on draft PR to main |
|
||||||
|
| `main` | Stable releases | Protected, merge only |
|
||||||
|
| `version/XX.YY.ZZ` | Archived release snapshots | Auto-created by CI |
|
||||||
|
|
||||||
## Translation
|
### Protected branches
|
||||||
|
|
||||||
All translation work happens on [Crowdin](https://translate.gitea.com).
|
| Branch | Direct push | Merge via |
|
||||||
The only translation that is maintained in this repository is [the English translation](https://github.com/go-gitea/gitea/blob/main/options/locale/locale_en-US.json).
|
|--------|------------|-----------|
|
||||||
It is synced regularly with Crowdin. \
|
| `main` | Blocked (CI bot whitelisted) | PR merge only |
|
||||||
Other locales on main branch **should not** be updated manually as they will be overwritten with each sync. \
|
| `dev` | Blocked (CI bot whitelisted) | PR merge from feature/* |
|
||||||
Once a language has reached a **satisfactory percentage** of translated keys (~25%), it will be synced back into this repo and included in the next released version.
|
| `rc` | Blocked (CI bot whitelisted) | Auto-created on draft PR |
|
||||||
|
| `alpha` | Blocked (CI bot whitelisted) | Manual rename |
|
||||||
|
| `beta` | Blocked (CI bot whitelisted) | Manual rename |
|
||||||
|
| `feature/*` | Open | N/A (source branch) |
|
||||||
|
|
||||||
The tool `go run build/backport-locale.go` can be used to backport locales from the main branch to release branches that were missed.
|
## Version Policy
|
||||||
|
|
||||||
## Code review
|
### Format
|
||||||
|
|
||||||
### Pull request format
|
All versions use `XX.YY.ZZ` — three two-digit segments, zero-padded:
|
||||||
|
|
||||||
Please try to make your pull request easy to review for us. \
|
- **XX** — Major version (breaking changes)
|
||||||
For that, please read the [*Best Practices for Faster Reviews*](https://github.com/kubernetes/community/blob/261cb0fd089b64002c91e8eddceebf032462ccd6/contributors/guide/pull-requests.md#best-practices-for-faster-reviews) guide. \
|
- **YY** — Minor version (new features, bumped on release to main)
|
||||||
It has lots of useful tips for any project you may want to contribute to. \
|
- **ZZ** — Patch version (auto-incremented on every push to dev/feature branches)
|
||||||
Some of the key points:
|
|
||||||
|
|
||||||
- Make small pull requests. \
|
Rollover: patch `99` → `00` increments minor; minor `99` → `00` increments major.
|
||||||
The smaller, the faster to review and the more likely it will be merged soon.
|
|
||||||
- Don't make changes unrelated to your PR. \
|
|
||||||
Maybe there are typos on some comments, maybe refactoring would be welcome on a function... \
|
|
||||||
but if that is not related to your PR, please make *another* PR for that.
|
|
||||||
- Split big pull requests into multiple small ones. \
|
|
||||||
An incremental change will be faster to review than a huge PR.
|
|
||||||
- Allow edits by maintainers. This way, the maintainers will take care of merging the PR later on instead of you.
|
|
||||||
|
|
||||||
### PR title and summary
|
### Stability suffixes
|
||||||
|
|
||||||
In the PR title, describe the problem you are fixing, not how you are fixing it. \
|
Each branch appends a suffix to indicate stability:
|
||||||
Use the first comment as a summary of your PR. \
|
|
||||||
In the PR summary, you can describe exactly how you are fixing this problem.
|
|
||||||
|
|
||||||
Keep this summary up-to-date as the PR evolves. \
|
| Branch | Suffix | Example |
|
||||||
If your PR changes the UI, you must add **after** screenshots in the PR summary. \
|
|--------|--------|---------|
|
||||||
If you are not implementing a new feature, you should also post **before** screenshots for comparison.
|
| `main` | (none) | `02.09.00` |
|
||||||
|
| `dev` | `-dev` | `02.09.01-dev` |
|
||||||
|
| `feature/*` | `-dev` | `02.09.01-dev` |
|
||||||
|
| `alpha` | `-alpha` | `02.09.01-alpha` |
|
||||||
|
| `beta` | `-beta` | `02.09.01-beta` |
|
||||||
|
| `rc` | `-rc` | `02.09.01-rc` |
|
||||||
|
|
||||||
If you are implementing a new feature, your PR will only be merged if your screenshots are up to date.\
|
### Auto version bump
|
||||||
Furthermore, feature PRs will only be merged if their summary contains a clear usage description (understandable for users) and testing description (understandable for reviewers).
|
|
||||||
You should strive to combine both into a single description.
|
|
||||||
|
|
||||||
Another requirement for merging PRs is that the PR is labeled correctly.\
|
On every push to `dev`, `feature/*`, or `patch/*`:
|
||||||
However, this is not your job as a contributor, but the job of the person merging your PR.\
|
|
||||||
If you think that your PR was labeled incorrectly, or notice that it was merged without labels, please let us know.
|
|
||||||
|
|
||||||
If your PR closes some issues, you must note that in a way that both GitHub and Gitea understand, i.e. by appending a paragraph like
|
1. Patch version incremented
|
||||||
|
2. Stability suffix `-dev` applied
|
||||||
|
3. All version-bearing files updated (manifests, CHANGELOG, PHP headers, etc.)
|
||||||
|
4. Commit created with `[skip ci]` to avoid loops
|
||||||
|
|
||||||
```text
|
### Release version flow
|
||||||
Fixes/Closes/Resolves #<ISSUE_NR_X>.
|
|
||||||
Fixes/Closes/Resolves #<ISSUE_NR_Y>.
|
|
||||||
```
|
|
||||||
|
|
||||||
to your summary. \
|
Version bumps happen at specific release events:
|
||||||
Each issue that will be closed must stand on a separate line.
|
|
||||||
|
|
||||||
### Milestone
|
| Event | Bump | Example |
|
||||||
|
|-------|------|---------|
|
||||||
|
| Feature merged to dev | Patch bump after dev release | `02.09.01-dev` → release → `02.09.02-dev` |
|
||||||
|
| Dev promoted to RC | Minor bump | `02.09.02-dev` → `02.10.00-rc` |
|
||||||
|
| RC merged to main | Minor bump | `02.10.00-rc` → `02.11.00` (stable) |
|
||||||
|
| Dev recreated from main | Patch bump | `02.11.00` → `02.11.01-dev` |
|
||||||
|
|
||||||
A PR should only be assigned to a milestone if it will likely be merged into the given version. \
|
### Release stream copies
|
||||||
As a rule of thumb, assume that a PR will stay open for an additional month for every 100 added lines. \
|
|
||||||
PRs without a milestone may not be merged.
|
|
||||||
|
|
||||||
### Labels
|
When a higher-stability release is published, copies are created for all lesser streams with the same base version:
|
||||||
|
|
||||||
Almost all labels used inside Gitea can be classified as one of the following:
|
- **RC `02.10.00-rc`** also creates: `02.10.00-dev`, `02.10.00-alpha`, `02.10.00-beta`
|
||||||
|
- **Stable `02.11.00`** also creates: `02.11.00-dev`, `02.11.00-alpha`, `02.11.00-beta`, `02.11.00-rc`
|
||||||
|
|
||||||
- `modifies/…`: Determines which parts of the codebase are affected. These labels will be set through the CI.
|
This ensures Joomla sites on ANY stability channel see the update (Joomla only shows versions higher than what's installed).
|
||||||
- `topic/…`: Determines the conceptual component of Gitea that is affected, i.e. issues, projects, or authentication. At best, PRs should only target one component but there might be overlap. Must be set manually.
|
|
||||||
- `type/…`: Determines the type of an issue or PR (feature, refactoring, docs, bug, …). If GitHub supported scoped labels, these labels would be exclusive, so you should set **exactly** one, not more or less (every PR should fall into one of the provided categories, and only one).
|
|
||||||
- `issue/…` / `pr/…`: Labels that are specific to issues or PRs respectively and that are only necessary in a given context, i.e. `issue/not-a-bug` or `pr/need-2-approvals`
|
|
||||||
|
|
||||||
Every PR should be labeled correctly with every label that applies.
|
### Version files
|
||||||
|
|
||||||
There are also some labels that will be managed automatically.\
|
The version tools update all files containing version stamps:
|
||||||
In particular, these are
|
|
||||||
|
|
||||||
- the amount of pending required approvals
|
- `.mokogitea/manifest.xml` (canonical source)
|
||||||
- has all `backport`s or needs a manual backport
|
- Joomla XML manifests (`<version>` tag)
|
||||||
|
- `README.md`, `CHANGELOG.md` (`VERSION:` pattern)
|
||||||
|
- `package.json`, `pyproject.toml`
|
||||||
|
- Any text file with a `VERSION: XX.YY.ZZ` label
|
||||||
|
|
||||||
### Breaking PRs
|
Files synced from other repos (with a `# REPO:` header) are not touched.
|
||||||
|
|
||||||
#### What is a breaking PR?
|
## Code Standards
|
||||||
|
|
||||||
A PR is breaking if it meets one of the following criteria:
|
- **PHP**: PSR-12, tabs for indentation
|
||||||
|
- **Copyright**: all files must include the Moko Consulting copyright header
|
||||||
|
- **License**: SPDX identifier `GPL-3.0-or-later` (or as specified per repo)
|
||||||
|
- **Attribution**: use `Authored-by: Moko Consulting` in commits, not individual names
|
||||||
|
|
||||||
- It changes API output in an incompatible way for existing users
|
## Commit Messages
|
||||||
- It removes a setting that an admin could previously set (i.e. via `app.ini`)
|
|
||||||
- An admin must do something manually to restore the old behavior
|
|
||||||
|
|
||||||
In particular, this means that adding new settings is not breaking.\
|
Use conventional commit format:
|
||||||
Changing the default value of a setting or replacing the setting with another one is breaking, however.
|
|
||||||
|
|
||||||
#### How to handle breaking PRs?
|
|
||||||
|
|
||||||
If your PR has a breaking change, you must add two things to the summary of your PR:
|
|
||||||
|
|
||||||
1. A reasoning why this breaking change is necessary
|
|
||||||
2. A `BREAKING` section explaining in simple terms (understandable for a typical user) how this PR affects users and how to mitigate these changes. This section can look for example like
|
|
||||||
|
|
||||||
```md
|
|
||||||
## :warning: BREAKING :warning:
|
|
||||||
```
|
|
||||||
|
|
||||||
Breaking PRs will not be merged as long as not both of these requirements are met.
|
|
||||||
|
|
||||||
### Maintaining open PRs
|
|
||||||
|
|
||||||
The moment you create a non-draft PR or the moment you convert a draft PR to a non-draft PR is the moment code review starts for it. \
|
|
||||||
Once that happens, do not rebase or squash your branch anymore as it makes it difficult to review the new changes. \
|
|
||||||
Merge the base branch into your branch only when you really need to, i.e. because of conflicting changes in the mean time. \
|
|
||||||
This reduces unnecessary CI runs. \
|
|
||||||
Don't worry about merge commits messing up your commit history as every PR will be squash merged. \
|
|
||||||
This means that all changes are joined into a single new commit whose message is as described below.
|
|
||||||
|
|
||||||
### Getting PRs merged
|
|
||||||
|
|
||||||
Changes to Gitea must be reviewed before they are accepted — no matter who
|
|
||||||
makes the change, even if they are an owner or a maintainer. \
|
|
||||||
The only exception are critical bugs that prevent Gitea from being compiled or started. \
|
|
||||||
Specifically, we require two approvals from maintainers for every PR. \
|
|
||||||
Once this criteria has been met, your PR receives the `lgtm/done` label. \
|
|
||||||
From this point on, your only responsibility is to fix merge conflicts or respond to/implement requests by maintainers. \
|
|
||||||
It is the responsibility of the maintainers from this point to get your PR merged.
|
|
||||||
|
|
||||||
If a PR has the `lgtm/done` label and there are no open discussions or merge conflicts anymore, any maintainer can add the `reviewed/wait-merge` label. \
|
|
||||||
This label means that the PR is part of the merge queue and will be merged as soon as possible. \
|
|
||||||
The merge queue will be cleared in the order of the list below:
|
|
||||||
|
|
||||||
<https://github.com/go-gitea/gitea/pulls?q=is%3Apr+label%3Areviewed%2Fwait-merge+sort%3Acreated-asc+is%3Aopen>
|
|
||||||
|
|
||||||
Gitea uses it's own tool, the <https://github.com/GiteaBot/gitea-backporter> to automate parts of the review process. \
|
|
||||||
This tool does the things listed below automatically:
|
|
||||||
|
|
||||||
- create a backport PR if needed once the initial PR was merged
|
|
||||||
- remove the PR from the merge queue after the PR merged
|
|
||||||
- keep the oldest branch in the merge queue up to date with merges
|
|
||||||
|
|
||||||
### Final call
|
|
||||||
|
|
||||||
If a PR has been ignored for more than 7 days with no comments or reviews, and the author or any maintainer believes it will not survive a long wait (such as a refactoring PR), they can send "final call" to the TOC by mentioning them in a comment.
|
|
||||||
|
|
||||||
After another 7 days, if there is still zero approval, this is considered a polite refusal, and the PR will be closed to avoid wasting further time. Therefore, the "final call" has a cost, and should be used cautiously.
|
|
||||||
|
|
||||||
However, if there are no objections from maintainers, the PR can be merged with only one approval from the TOC (not the author).
|
|
||||||
|
|
||||||
### Commit messages
|
|
||||||
|
|
||||||
Mergers are able and required to rewrite the PR title and summary (the first comment of a PR) so that it can produce an easily understandable commit message if necessary. \
|
|
||||||
The final commit message should no longer contain any uncertainty such as `hopefully, <x> won't happen anymore`. Replace uncertainty with certainty.
|
|
||||||
|
|
||||||
#### PR Co-authors
|
|
||||||
|
|
||||||
A person counts as a PR co-author the moment they (co-)authored a commit that is not simply a `Merge base branch into branch` commit. \
|
|
||||||
Mergers are required to remove such "false-positive" co-authors when writing the commit message. \
|
|
||||||
The true co-authors must remain in the commit message.
|
|
||||||
|
|
||||||
#### PRs targeting `main`
|
|
||||||
|
|
||||||
The commit message of PRs targeting `main` is always
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$PR_TITLE ($PR_INDEX)
|
|
||||||
|
|
||||||
$REWRITTEN_PR_SUMMARY
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Backport PRs
|
|
||||||
|
|
||||||
The commit message of backport PRs is always
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$PR_TITLE ($INITIAL_PR_INDEX) ($BACKPORT_PR_INDEX)
|
|
||||||
|
|
||||||
$REWRITTEN_PR_SUMMARY
|
|
||||||
```
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
If you add a new feature or change an existing aspect of Gitea, the documentation for that feature must be created or updated in another PR at [https://gitea.com/gitea/docs](https://gitea.com/gitea/docs).
|
|
||||||
**The docs directory on main repository will be removed at some time. We will have a yaml file to store configuration file's meta data. After that completed, configuration documentation should be in the main repository.**
|
|
||||||
|
|
||||||
## API v1
|
|
||||||
|
|
||||||
The API is documented by [swagger](https://gitea.com/api/swagger) and is based on [the GitHub API](https://docs.github.com/en/rest).
|
|
||||||
|
|
||||||
### GitHub API compatibility
|
|
||||||
|
|
||||||
Gitea's API should use the same endpoints and fields as the GitHub API as far as possible, unless there are good reasons to deviate. \
|
|
||||||
If Gitea provides functionality that GitHub does not, a new endpoint can be created. \
|
|
||||||
If information is provided by Gitea that is not provided by the GitHub API, a new field can be used that doesn't collide with any GitHub fields. \
|
|
||||||
Updating an existing API should not remove existing fields unless there is a really good reason to do so. \
|
|
||||||
The same applies to status responses. If you notice a problem, feel free to leave a comment in the code for future refactoring to API v2 (which is currently not planned).
|
|
||||||
|
|
||||||
### Adding/Maintaining API routes
|
|
||||||
|
|
||||||
All expected results (errors, success, fail messages) must be documented ([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/repo/issue.go#L319-L327)). \
|
|
||||||
All JSON input types must be defined as a struct in [modules/structs/](modules/structs/) ([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/modules/structs/issue.go#L76-L91)) \
|
|
||||||
and referenced in [routers/api/v1/swagger/options.go](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/swagger/options.go). \
|
|
||||||
They can then be used like [this example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/repo/issue.go#L318). \
|
|
||||||
All JSON responses must be defined as a struct in [modules/structs/](modules/structs/) ([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/modules/structs/issue.go#L36-L68)) \
|
|
||||||
and referenced in its category in [routers/api/v1/swagger/](routers/api/v1/swagger/) ([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/swagger/issue.go#L11-L16)) \
|
|
||||||
They can be used like [this example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/repo/issue.go#L277-L279).
|
|
||||||
|
|
||||||
### When to use what HTTP method
|
|
||||||
|
|
||||||
In general, HTTP methods are chosen as follows:
|
|
||||||
|
|
||||||
- **GET** endpoints return the requested object(s) and status **OK (200)**
|
|
||||||
- **DELETE** endpoints return the status **No Content (204)** and no content either
|
|
||||||
- **POST** endpoints are used to **create** new objects (e.g. a User) and return the status **Created (201)** and the created object
|
|
||||||
- **PUT** endpoints are used to **add/assign** existing Objects (e.g. a user to a team) and return the status **No Content (204)** and no content either
|
|
||||||
- **PATCH** endpoints are used to **edit/change** an existing object and return the changed object and the status **OK (200)**
|
|
||||||
|
|
||||||
### Requirements for API routes
|
|
||||||
|
|
||||||
All parameters of endpoints changing/editing an object must be optional (except the ones to identify the object, which are required).
|
|
||||||
|
|
||||||
Endpoints returning lists must
|
|
||||||
|
|
||||||
- support pagination (`page` & `limit` options in query)
|
|
||||||
- set `X-Total-Count` header via **SetTotalCountHeader** ([example](https://github.com/go-gitea/gitea/blob/7aae98cc5d4113f1e9918b7ee7dd09f67c189e3e/routers/api/v1/repo/issue.go#L444))
|
|
||||||
|
|
||||||
## Backports and Frontports
|
|
||||||
|
|
||||||
### What is backported?
|
|
||||||
|
|
||||||
We backport PRs given the following circumstances:
|
|
||||||
|
|
||||||
1. Feature freeze is active, but `<version>-rc0` has not been released yet. Here, we backport as much as possible. <!-- TODO: Is that our definition with the new backport bot? -->
|
|
||||||
2. `rc0` has been released. Here, we only backport bug- and security-fixes, and small enhancements. Large PRs such as refactors are not backported anymore. <!-- TODO: Is that our definition with the new backport bot? -->
|
|
||||||
3. We never backport new features.
|
|
||||||
4. We never backport breaking changes except when
|
|
||||||
1. The breaking change has no effect on the vast majority of users
|
|
||||||
2. The component triggering the breaking change is marked as experimental
|
|
||||||
|
|
||||||
### How to backport?
|
|
||||||
|
|
||||||
In the past, it was necessary to manually backport your PRs. \
|
|
||||||
Now, that's not a requirement anymore as our [backport bot](https://github.com/GiteaBot) tries to create backports automatically once the PR is merged when the PR
|
|
||||||
|
|
||||||
- does not have the label `backport/manual`
|
|
||||||
- has the label `backport/<version>`
|
|
||||||
|
|
||||||
The `backport/manual` label signifies either that you want to backport the change yourself, or that there were conflicts when backporting, thus you **must** do it yourself.
|
|
||||||
|
|
||||||
### Format of backport PRs
|
|
||||||
|
|
||||||
The title of backport PRs should be
|
|
||||||
|
|
||||||
```
|
```
|
||||||
<original PR title> (#<original pr number>)
|
type(scope): short description
|
||||||
|
|
||||||
|
Optional body with context.
|
||||||
|
|
||||||
|
Authored-by: Moko Consulting
|
||||||
```
|
```
|
||||||
|
|
||||||
The first two lines of the summary of the backporting PR should be
|
Types: `feat`, `fix`, `chore`, `docs`, `style`, `refactor`, `test`, `ci`
|
||||||
|
|
||||||
```
|
Special flags in commit messages:
|
||||||
Backport #<original pr number>
|
- `[skip ci]` — skip all CI workflows
|
||||||
|
- `[skip bump]` — skip auto version bump only
|
||||||
|
|
||||||
```
|
## Reporting Issues
|
||||||
|
|
||||||
with the rest of the summary and labels matching the original PR.
|
Use the repository's issue tracker with the appropriate template.
|
||||||
|
|
||||||
### Frontports
|
---
|
||||||
|
|
||||||
Frontports behave exactly as described above for backports.
|
*Moko Consulting <hello@mokoconsulting.tech>*
|
||||||
|
|
||||||
## Developer Certificate of Origin (DCO)
|
|
||||||
|
|
||||||
We consider the act of contributing to the code by submitting a Pull Request as the "Sign off" or agreement to the certifications and terms of the [DCO](DCO) and [MIT license](LICENSE). \
|
|
||||||
No further action is required. \
|
|
||||||
You can also decide to sign off your commits by adding the following line at the end of your commit messages:
|
|
||||||
|
|
||||||
```
|
|
||||||
Signed-off-by: Joe Smith <joe.smith@email.com>
|
|
||||||
```
|
|
||||||
|
|
||||||
If you set the `user.name` and `user.email` Git config options, you can add the line to the end of your commits automatically with `git commit -s`.
|
|
||||||
|
|
||||||
We assume in good faith that the information you provide is legally binding.
|
|
||||||
|
|
||||||
## Release Cycle
|
|
||||||
|
|
||||||
We adopted a release schedule to streamline the process of working on, finishing, and issuing releases. \
|
|
||||||
The overall goal is to make a major release every three or four months, which breaks down into two or three months of general development followed by one month of testing and polishing known as the release freeze. \
|
|
||||||
All the feature pull requests should be
|
|
||||||
merged before feature freeze. All feature pull requests haven't been merged before this feature freeze will be moved to next milestone, please notice our feature freeze announcement on discord. And, during the frozen period, a corresponding
|
|
||||||
release branch is open for fixes backported from main branch. Release candidates
|
|
||||||
are made during this period for user testing to
|
|
||||||
obtain a final version that is maintained in this branch.
|
|
||||||
|
|
||||||
During a development cycle, we may also publish any necessary minor releases
|
|
||||||
for the previous version. For example, if the latest, published release is
|
|
||||||
v1.2, then minor changes for the previous release—e.g., v1.1.0 -> v1.1.1—are
|
|
||||||
still possible.
|
|
||||||
|
|
||||||
## Maintainers
|
|
||||||
|
|
||||||
To make sure every PR is checked, we have [maintainers](MAINTAINERS). \
|
|
||||||
Every PR **must** be reviewed by at least two maintainers (or owners) before it can get merged. \
|
|
||||||
For refactoring PRs after a week and documentation only PRs, the approval of only one maintainer is enough. \
|
|
||||||
A maintainer should be a contributor of Gitea and contributed at least
|
|
||||||
4 accepted PRs. A contributor should apply as a maintainer in the
|
|
||||||
[Discord](https://discord.gg/Gitea) `#develop` channel. The team maintainers may invite the contributor. A maintainer
|
|
||||||
should spend some time on code reviews. If a maintainer has no
|
|
||||||
time to do that, they should apply to leave the maintainers team
|
|
||||||
and we will give them the honor of being a member of the [advisors
|
|
||||||
team](https://github.com/orgs/go-gitea/teams/advisors). Of course, if
|
|
||||||
an advisor has time to code review, we will gladly welcome them back
|
|
||||||
to the maintainers team. If a maintainer is inactive for more than 3
|
|
||||||
months and forgets to leave the maintainers team, the owners may move
|
|
||||||
him or her from the maintainers team to the advisors team.
|
|
||||||
For security reasons, Maintainers should use 2FA for their accounts and
|
|
||||||
if possible provide GPG signed commits.
|
|
||||||
https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/
|
|
||||||
https://help.github.com/articles/signing-commits-with-gpg/
|
|
||||||
|
|
||||||
Furthermore, any account with write access (like bots and TOC members) **must** use 2FA.
|
|
||||||
https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/
|
|
||||||
|
|
||||||
## Technical Oversight Committee (TOC)
|
|
||||||
|
|
||||||
At the start of 2023, the `Owners` team was dissolved. Instead, the governance charter proposed a technical oversight committee (TOC) which expands the ownership team of the Gitea project from three elected positions to six positions. Three positions are elected as it has been over the past years, and the other three consist of appointed members from the Gitea company.
|
|
||||||
https://blog.gitea.com/quarterly-23q1/
|
|
||||||
|
|
||||||
### TOC election process
|
|
||||||
|
|
||||||
Any maintainer is eligible to be part of the community TOC if they are not associated with the Gitea company.
|
|
||||||
A maintainer can either nominate themselves, or can be nominated by other maintainers to be a candidate for the TOC election.
|
|
||||||
If you are nominated by someone else, you must first accept your nomination before the vote starts to be a candidate.
|
|
||||||
|
|
||||||
The TOC is elected for one year, the TOC election happens yearly.
|
|
||||||
After the announcement of the results of the TOC election, elected members have two weeks time to confirm or refuse the seat.
|
|
||||||
If an elected member does not answer within this timeframe, they are automatically assumed to refuse the seat.
|
|
||||||
Refusals result in the person with the next highest vote getting the same choice.
|
|
||||||
As long as seats are empty in the TOC, members of the previous TOC can fill them until an elected member accepts the seat.
|
|
||||||
|
|
||||||
If an elected member that accepts the seat does not have 2FA configured yet, they will be temporarily counted as `answer pending` until they manage to configure 2FA, thus leaving their seat empty for this duration.
|
|
||||||
|
|
||||||
### Current TOC members
|
|
||||||
|
|
||||||
- 2024-01-01 ~ 2024-12-31
|
|
||||||
- Company
|
|
||||||
- [Jason Song](https://gitea.com/wolfogre) <i@wolfogre.com>
|
|
||||||
- [Lunny Xiao](https://gitea.com/lunny) <xiaolunwen@gmail.com>
|
|
||||||
- [Matti Ranta](https://gitea.com/techknowlogick) <techknowlogick@gitea.com>
|
|
||||||
- Community
|
|
||||||
- [6543](https://gitea.com/6543) <6543@obermui.de>
|
|
||||||
- [delvh](https://gitea.com/delvh) <dev.lh@web.de>
|
|
||||||
- [John Olheiser](https://gitea.com/jolheiser) <john.olheiser@gmail.com>
|
|
||||||
|
|
||||||
### Previous TOC/owners members
|
|
||||||
|
|
||||||
Here's the history of the owners and the time they served:
|
|
||||||
|
|
||||||
- [Lunny Xiao](https://gitea.com/lunny) - 2016, 2017, [2018](https://github.com/go-gitea/gitea/issues/3255), [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872), 2023
|
|
||||||
- [Kim Carlbäcker](https://github.com/bkcsoft) - 2016, 2017
|
|
||||||
- [Thomas Boerger](https://gitea.com/tboerger) - 2016, 2017
|
|
||||||
- [Lauris Bukšis-Haberkorns](https://gitea.com/lafriks) - [2018](https://github.com/go-gitea/gitea/issues/3255), [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801)
|
|
||||||
- [Matti Ranta](https://gitea.com/techknowlogick) - [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872), 2023
|
|
||||||
- [Andrew Thornton](https://gitea.com/zeripath) - [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872), 2023
|
|
||||||
- [6543](https://gitea.com/6543) - 2023
|
|
||||||
- [John Olheiser](https://gitea.com/jolheiser) - 2023
|
|
||||||
- [Jason Song](https://gitea.com/wolfogre) - 2023
|
|
||||||
|
|
||||||
## Governance Compensation
|
|
||||||
|
|
||||||
Each member of the community elected TOC will be granted $500 each month as compensation for their work.
|
|
||||||
|
|
||||||
Furthermore, any community release manager for a specific release or LTS will be compensated $500 for the delivery of said release.
|
|
||||||
|
|
||||||
These funds will come from community sources like the OpenCollective rather than directly from the company.
|
|
||||||
Only non-company members are eligible for this compensation, and if a member of the community TOC takes the responsibility of release manager, they would only be compensated for their TOC duties.
|
|
||||||
Gitea Ltd employees are not eligible to receive any funds from the OpenCollective unless it is reimbursement for a purchase made for the Gitea project itself.
|
|
||||||
|
|
||||||
## TOC & Working groups
|
|
||||||
|
|
||||||
With Gitea covering many projects outside of the main repository, several groups will be created to help focus on specific areas instead of requiring maintainers to be a jack-of-all-trades. Maintainers are of course more than welcome to be part of multiple groups should they wish to contribute in multiple places.
|
|
||||||
|
|
||||||
The currently proposed groups are:
|
|
||||||
|
|
||||||
- **Core Group**: maintain the primary Gitea repository
|
|
||||||
- **Integration Group**: maintain the Gitea ecosystem's related tools, including go-sdk/tea/changelog/bots etc.
|
|
||||||
- **Documentation Group**: maintain related documents and repositories
|
|
||||||
- **Translation Group**: coordinate with translators and maintain translations
|
|
||||||
- **Security Group**: managed by TOC directly, members are decided by TOC, maintains security patches/responsible for security items
|
|
||||||
|
|
||||||
## Roadmap
|
|
||||||
|
|
||||||
Each year a roadmap will be discussed with the entire Gitea maintainers team, and feedback will be solicited from various stakeholders.
|
|
||||||
TOC members need to review the roadmap every year and work together on the direction of the project.
|
|
||||||
|
|
||||||
When a vote is required for a proposal or other change, the vote of community elected TOC members count slightly more than the vote of company elected TOC members. With this approach, we both avoid ties and ensure that changes align with the mission statement and community opinion.
|
|
||||||
|
|
||||||
You can visit our roadmap on the wiki.
|
|
||||||
|
|
||||||
## Versions
|
|
||||||
|
|
||||||
Gitea has the `main` branch as a tip branch and has version branches
|
|
||||||
such as `release/v1.19`. `release/v1.19` is a release branch and we will
|
|
||||||
tag `v1.19.0` for binary download. If `v1.19.0` has bugs, we will accept
|
|
||||||
pull requests on the `release/v1.19` branch and publish a `v1.19.1` tag,
|
|
||||||
after bringing the bug fix also to the main branch.
|
|
||||||
|
|
||||||
Since the `main` branch is a tip version, if you wish to use Gitea
|
|
||||||
in production, please download the latest release tag version. All the
|
|
||||||
branches will be protected via GitHub, all the PRs to every branch must
|
|
||||||
be reviewed by two maintainers and must pass the automatic tests.
|
|
||||||
|
|
||||||
## Releasing Gitea
|
|
||||||
|
|
||||||
- Let $vmaj, $vmin and $vpat be Major, Minor and Patch version numbers, $vpat should be rc1, rc2, 0, 1, ...... $vmaj.$vmin will be kept the same as milestones on github or gitea in future.
|
|
||||||
- Before releasing, confirm all the version's milestone issues or PRs has been resolved. Then discuss the release on Discord channel #maintainers and get agreed with almost all the owners and mergers. Or you can declare the version and if nobody is against it in about several hours.
|
|
||||||
- If this is a big version first you have to create PR for changelog on branch `main` with PRs with label `changelog` and after it has been merged do following steps:
|
|
||||||
- Create `-dev` tag as `git tag -s -F release.notes v$vmaj.$vmin.0-dev` and push the tag as `git push origin v$vmaj.$vmin.0-dev`.
|
|
||||||
- When CI has finished building tag then you have to create a new branch named `release/v$vmaj.$vmin`
|
|
||||||
- If it is bugfix version create PR for changelog on branch `release/v$vmaj.$vmin` and wait till it is reviewed and merged.
|
|
||||||
- Add a tag as `git tag -s -F release.notes v$vmaj.$vmin.$`, release.notes file could be a temporary file to only include the changelog this version which you added to `CHANGELOG.md`.
|
|
||||||
- And then push the tag as `git push origin v$vmaj.$vmin.$`. Drone CI will automatically create a release and upload all the compiled binary. (But currently it doesn't add the release notes automatically. Maybe we should fix that.)
|
|
||||||
- If needed send a frontport PR for the changelog to branch `main` and update the version in `docs/config.yaml` to refer to the new version.
|
|
||||||
- Send PR to [blog repository](https://gitea.com/gitea/blog) announcing the release.
|
|
||||||
- Verify all release assets were correctly published through CI on dl.gitea.com and GitHub releases. Once ACKed:
|
|
||||||
- bump the version of https://dl.gitea.com/gitea/version.json
|
|
||||||
- merge the blog post PR
|
|
||||||
- announce the release in discord `#announcements`
|
|
||||||
|
|||||||
+8
-7
@@ -12,7 +12,7 @@ RUN make frontend
|
|||||||
FROM docker.io/library/golang:1.26-alpine3.23 AS build-env
|
FROM docker.io/library/golang:1.26-alpine3.23 AS build-env
|
||||||
|
|
||||||
ARG GITEA_VERSION
|
ARG GITEA_VERSION
|
||||||
ARG TAGS="sqlite sqlite_unlock_notify"
|
ARG TAGS=""
|
||||||
ENV TAGS="bindata timetzdata $TAGS"
|
ENV TAGS="bindata timetzdata $TAGS"
|
||||||
ARG CGO_EXTRA_CFLAGS
|
ARG CGO_EXTRA_CFLAGS
|
||||||
|
|
||||||
@@ -21,18 +21,19 @@ RUN apk --no-cache add \
|
|||||||
build-base \
|
build-base \
|
||||||
git
|
git
|
||||||
|
|
||||||
WORKDIR ${GOPATH}/src/code.gitea.io/gitea
|
WORKDIR ${GOPATH}/src/git.mokoconsulting.tech/MokoConsulting/MokoGitea
|
||||||
COPY go.mod go.sum ./
|
COPY go.mod go.sum ./
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
# Use COPY instead of bind mount as read-only one breaks makefile state tracking and read-write one needs binary to be moved as it's discarded.
|
# Use COPY instead of bind mount as read-only one breaks makefile state tracking
|
||||||
# ".git" directory is mounted separately later only for version data extraction.
|
|
||||||
COPY --exclude=.git/ . .
|
COPY --exclude=.git/ . .
|
||||||
COPY --from=frontend-build /src/public/assets public/assets
|
COPY --from=frontend-build /src/public/assets public/assets
|
||||||
|
|
||||||
# Build gitea, .git mount is required for version data
|
# Build gitea, .git mount is required for version data
|
||||||
|
# GOFLAGS=-p 1 serializes compilation to prevent OOM on low-memory servers
|
||||||
|
ARG GOFLAGS="-p 1"
|
||||||
RUN --mount=type=cache,target="/root/.cache/go-build" \
|
RUN --mount=type=cache,target="/root/.cache/go-build" \
|
||||||
--mount=type=bind,source=".git/",target=".git/" \
|
--mount=type=bind,source=".git/",target=".git/" \
|
||||||
make backend
|
GOFLAGS="${GOFLAGS}" make backend
|
||||||
|
|
||||||
COPY docker/root /tmp/local
|
COPY docker/root /tmp/local
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
|
|||||||
/tmp/local/etc/s6/gitea/* \
|
/tmp/local/etc/s6/gitea/* \
|
||||||
/tmp/local/etc/s6/openssh/* \
|
/tmp/local/etc/s6/openssh/* \
|
||||||
/tmp/local/etc/s6/.s6-svscan/* \
|
/tmp/local/etc/s6/.s6-svscan/* \
|
||||||
/go/src/code.gitea.io/gitea/gitea
|
/go/src/git.mokoconsulting.tech/MokoConsulting/MokoGitea/gitea
|
||||||
|
|
||||||
FROM docker.io/library/alpine:3.23 AS gitea
|
FROM docker.io/library/alpine:3.23 AS gitea
|
||||||
|
|
||||||
@@ -74,7 +75,7 @@ RUN addgroup \
|
|||||||
echo "git:*" | chpasswd -e
|
echo "git:*" | chpasswd -e
|
||||||
|
|
||||||
COPY --from=build-env /tmp/local /
|
COPY --from=build-env /tmp/local /
|
||||||
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
|
COPY --from=build-env /go/src/git.mokoconsulting.tech/MokoConsulting/MokoGitea/gitea /app/gitea/gitea
|
||||||
|
|
||||||
ENV USER=git
|
ENV USER=git
|
||||||
ENV GITEA_CUSTOM=/data/gitea
|
ENV GITEA_CUSTOM=/data/gitea
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ RUN make frontend
|
|||||||
FROM docker.io/library/golang:1.26-alpine3.23 AS build-env
|
FROM docker.io/library/golang:1.26-alpine3.23 AS build-env
|
||||||
|
|
||||||
ARG GITEA_VERSION
|
ARG GITEA_VERSION
|
||||||
ARG TAGS="sqlite sqlite_unlock_notify"
|
ARG TAGS=""
|
||||||
ENV TAGS="bindata timetzdata $TAGS"
|
ENV TAGS="bindata timetzdata $TAGS"
|
||||||
ARG CGO_EXTRA_CFLAGS
|
ARG CGO_EXTRA_CFLAGS
|
||||||
|
|
||||||
|
|||||||
@@ -7,33 +7,41 @@ export GOEXPERIMENT ?= jsonv2
|
|||||||
|
|
||||||
GO ?= go
|
GO ?= go
|
||||||
SHASUM ?= shasum -a 256
|
SHASUM ?= shasum -a 256
|
||||||
HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes)
|
|
||||||
COMMA := ,
|
COMMA := ,
|
||||||
|
|
||||||
XGO_VERSION := go-1.25.x
|
XGO_VERSION := go-1.26.x
|
||||||
|
|
||||||
AIR_PACKAGE ?= github.com/air-verse/air@v1
|
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
|
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3 # renovate: datasource=go
|
||||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.9.2
|
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4 # renovate: datasource=go
|
||||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4
|
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15 # renovate: datasource=go
|
||||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15
|
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.8.0 # renovate: datasource=go
|
||||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.8.0
|
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.1 # renovate: datasource=go
|
||||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.1
|
|
||||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
||||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
|
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go
|
||||||
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.11
|
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.11 # renovate: datasource=go
|
||||||
|
|
||||||
DOCKER_IMAGE ?= gitea/gitea
|
|
||||||
DOCKER_TAG ?= latest
|
|
||||||
DOCKER_REF := $(DOCKER_IMAGE):$(DOCKER_TAG)
|
|
||||||
|
|
||||||
|
HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes)
|
||||||
ifeq ($(HAS_GO), yes)
|
ifeq ($(HAS_GO), yes)
|
||||||
CGO_EXTRA_CFLAGS := -DSQLITE_MAX_VARIABLE_NUMBER=32766
|
CGO_EXTRA_CFLAGS := -DSQLITE_MAX_VARIABLE_NUMBER=32766
|
||||||
CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS)
|
CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
MAKE_EVIDENCE_DIR := .make_evidence
|
||||||
|
|
||||||
|
# Use sqlite as default database if running tests, only do so for local tests, not in CI.
|
||||||
|
# CI should explicitly set the database to avoid unexpected results.
|
||||||
|
ifneq ($(findstring test-,$(MAKECMDGOALS)),)
|
||||||
|
ifeq ($(CI),)
|
||||||
|
GITEA_TEST_DATABASE ?= sqlite
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
TAGS ?=
|
||||||
|
TAGS_EVIDENCE := $(MAKE_EVIDENCE_DIR)/tags
|
||||||
|
|
||||||
CGO_ENABLED ?= 0
|
CGO_ENABLED ?= 0
|
||||||
ifneq (,$(findstring sqlite,$(TAGS))$(findstring pam,$(TAGS)))
|
ifneq (,$(findstring sqlite_mattn,$(TAGS))$(findstring pam,$(TAGS)))
|
||||||
CGO_ENABLED = 1
|
CGO_ENABLED = 1
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -50,15 +58,16 @@ else ifeq ($(patsubst Windows%,Windows,$(OS)),Windows)
|
|||||||
IS_WINDOWS := yes
|
IS_WINDOWS := yes
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# GOFLAGS and EXTRA_GOFLAGS are for the 'go build' command only
|
||||||
ifeq ($(IS_WINDOWS),yes)
|
ifeq ($(IS_WINDOWS),yes)
|
||||||
GOFLAGS := -v -buildmode=exe
|
GOFLAGS := -v -buildmode=exe
|
||||||
EXECUTABLE ?= gitea.exe
|
EXECUTABLE ?= gitea.exe
|
||||||
EXECUTABLE_E2E ?= gitea-e2e.exe
|
|
||||||
else
|
else
|
||||||
GOFLAGS := -v
|
GOFLAGS := -v
|
||||||
EXECUTABLE ?= gitea
|
EXECUTABLE ?= gitea
|
||||||
EXECUTABLE_E2E ?= gitea-e2e
|
|
||||||
endif
|
endif
|
||||||
|
EXTRA_GOFLAGS ?=
|
||||||
|
|
||||||
ifeq ($(shell sed --version 2>/dev/null | grep -q GNU && echo gnu),gnu)
|
ifeq ($(shell sed --version 2>/dev/null | grep -q GNU && echo gnu),gnu)
|
||||||
SED_INPLACE := sed -i
|
SED_INPLACE := sed -i
|
||||||
@@ -66,15 +75,8 @@ else
|
|||||||
SED_INPLACE := sed -i ''
|
SED_INPLACE := sed -i ''
|
||||||
endif
|
endif
|
||||||
|
|
||||||
EXTRA_GOFLAGS ?=
|
# GOTEST_FLAGS is for unit test and integration test
|
||||||
|
GOTEST_FLAGS ?= -timeout 40m
|
||||||
MAKE_EVIDENCE_DIR := .make_evidence
|
|
||||||
|
|
||||||
GOTESTFLAGS ?=
|
|
||||||
ifeq ($(RACE_ENABLED),true)
|
|
||||||
GOFLAGS += -race
|
|
||||||
GOTESTFLAGS += -race
|
|
||||||
endif
|
|
||||||
|
|
||||||
STORED_VERSION_FILE := VERSION
|
STORED_VERSION_FILE := VERSION
|
||||||
|
|
||||||
@@ -127,12 +129,6 @@ AIR_TMP_DIR := .air
|
|||||||
|
|
||||||
GO_LICENSE_FILE := assets/go-licenses.json
|
GO_LICENSE_FILE := assets/go-licenses.json
|
||||||
|
|
||||||
TAGS ?=
|
|
||||||
TAGS_SPLIT := $(subst $(COMMA), ,$(TAGS))
|
|
||||||
TAGS_EVIDENCE := $(MAKE_EVIDENCE_DIR)/tags
|
|
||||||
|
|
||||||
TEST_TAGS ?= $(TAGS_SPLIT) sqlite sqlite_unlock_notify
|
|
||||||
|
|
||||||
TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(DIST) $(MAKE_EVIDENCE_DIR) $(AIR_TMP_DIR)
|
TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(DIST) $(MAKE_EVIDENCE_DIR) $(AIR_TMP_DIR)
|
||||||
|
|
||||||
GO_DIRS := build cmd models modules routers services tests tools
|
GO_DIRS := build cmd models modules routers services tests tools
|
||||||
@@ -152,6 +148,7 @@ ESLINT_CONCURRENCY ?= 2
|
|||||||
SWAGGER_SPEC := templates/swagger/v1_json.tmpl
|
SWAGGER_SPEC := templates/swagger/v1_json.tmpl
|
||||||
SWAGGER_SPEC_INPUT := templates/swagger/v1_input.json
|
SWAGGER_SPEC_INPUT := templates/swagger/v1_input.json
|
||||||
SWAGGER_EXCLUDE := code.gitea.io/sdk
|
SWAGGER_EXCLUDE := code.gitea.io/sdk
|
||||||
|
OPENAPI3_SPEC := templates/swagger/v1_openapi3_json.tmpl
|
||||||
|
|
||||||
TEST_MYSQL_HOST ?= mysql:3306
|
TEST_MYSQL_HOST ?= mysql:3306
|
||||||
TEST_MYSQL_DBNAME ?= testgitea
|
TEST_MYSQL_DBNAME ?= testgitea
|
||||||
@@ -164,13 +161,19 @@ TEST_PGSQL_PASSWORD ?= postgres
|
|||||||
TEST_PGSQL_SCHEMA ?= gtestschema
|
TEST_PGSQL_SCHEMA ?= gtestschema
|
||||||
TEST_MINIO_ENDPOINT ?= minio:9000
|
TEST_MINIO_ENDPOINT ?= minio:9000
|
||||||
TEST_MSSQL_HOST ?= mssql:1433
|
TEST_MSSQL_HOST ?= mssql:1433
|
||||||
TEST_MSSQL_DBNAME ?= gitea
|
TEST_MSSQL_DBNAME ?= testgitea
|
||||||
TEST_MSSQL_USERNAME ?= sa
|
TEST_MSSQL_USERNAME ?= sa
|
||||||
TEST_MSSQL_PASSWORD ?= MwantsaSecurePassword1
|
TEST_MSSQL_PASSWORD ?= MwantsaSecurePassword1
|
||||||
|
|
||||||
# Include local Makefile
|
# Include local Makefile
|
||||||
# Makefile.local is listed in .gitignore
|
# Makefile.local is listed in .gitignore
|
||||||
sinclude Makefile.local
|
ifneq ("$(wildcard Makefile.local)","")
|
||||||
|
include Makefile.local
|
||||||
|
endif
|
||||||
|
|
||||||
|
$(foreach v, $(filter TEST_%, $(.VARIABLES)), $(eval MAKEFILE_VARS+=$v=$($v)))
|
||||||
|
$(foreach v, $(filter GITEA_TEST_%, $(.VARIABLES)), $(eval MAKEFILE_VARS+=$v=$($v)))
|
||||||
|
export MAKEFILE_VARS
|
||||||
|
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: build
|
all: build
|
||||||
@@ -179,15 +182,8 @@ all: build
|
|||||||
help: Makefile ## print Makefile help information.
|
help: Makefile ## print Makefile help information.
|
||||||
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m[TARGETS] default target: build\033[0m\n\n\033[35mTargets:\033[0m\n"} /^[0-9A-Za-z._-]+:.*?##/ { printf " \033[36m%-45s\033[0m %s\n", $$1, $$2 }' Makefile #$(MAKEFILE_LIST)
|
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m[TARGETS] default target: build\033[0m\n\n\033[35mTargets:\033[0m\n"} /^[0-9A-Za-z._-]+:.*?##/ { printf " \033[36m%-45s\033[0m %s\n", $$1, $$2 }' Makefile #$(MAKEFILE_LIST)
|
||||||
@printf " \033[36m%-46s\033[0m %s\n" "test-e2e" "test end to end using playwright"
|
@printf " \033[36m%-46s\033[0m %s\n" "test-e2e" "test end to end using playwright"
|
||||||
@printf " \033[36m%-46s\033[0m %s\n" "test[#TestSpecificName]" "run unit test"
|
@printf " \033[36m%-46s\033[0m %s\n" "test-backend[#TestSpecificName]" "run unit test (sqlite only)"
|
||||||
@printf " \033[36m%-46s\033[0m %s\n" "test-sqlite[#TestSpecificName]" "run integration test for sqlite"
|
@printf " \033[36m%-46s\033[0m %s\n" "test-integration[#TestSpecificName]" "run integration test for GITEA_TEST_DATABASE (sqlite, mysql, pgsql, mssql)"
|
||||||
|
|
||||||
.PHONY: git-check
|
|
||||||
git-check:
|
|
||||||
@if git lfs >/dev/null 2>&1 ; then : ; else \
|
|
||||||
echo "Gitea requires git with lfs support to run tests." ; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
.PHONY: clean-all
|
.PHONY: clean-all
|
||||||
clean-all: clean ## delete backend, frontend and integration files
|
clean-all: clean ## delete backend, frontend and integration files
|
||||||
@@ -195,18 +191,12 @@ clean-all: clean ## delete backend, frontend and integration files
|
|||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean: ## delete backend and integration files
|
clean: ## delete backend and integration files
|
||||||
rm -rf $(EXECUTABLE) $(EXECUTABLE_E2E) $(DIST) $(BINDATA_DEST_WILDCARD) \
|
rm -f $(EXECUTABLE) test-*.test tests/*.ini
|
||||||
integrations*.test \
|
rm -rf $(DIST) $(BINDATA_DEST_WILDCARD) man tests/integration/gitea-integration-*
|
||||||
tests/integration/gitea-integration-* \
|
|
||||||
tests/integration/indexers-* \
|
|
||||||
tests/sqlite.ini tests/mysql.ini tests/pgsql.ini tests/mssql.ini man/ \
|
|
||||||
tests/e2e/gitea-e2e-*/ \
|
|
||||||
tests/e2e/indexers-*/ \
|
|
||||||
tests/e2e/reports/ tests/e2e/test-artifacts/ tests/e2e/test-snapshots/
|
|
||||||
|
|
||||||
.PHONY: fmt
|
.PHONY: fmt
|
||||||
fmt: ## format the Go and template code
|
fmt: ## format the Go and template code
|
||||||
@GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run tools/code-batch-process.go gitea-fmt -w '{file-list}'
|
$(GO) run $(GOLANGCI_LINT_PACKAGE) fmt
|
||||||
$(eval TEMPLATES := $(shell find templates -type f -name '*.tmpl'))
|
$(eval TEMPLATES := $(shell find templates -type f -name '*.tmpl'))
|
||||||
@# strip whitespace after '{{' or '(' and before '}}' or ')' unless there is only
|
@# strip whitespace after '{{' or '(' and before '}}' or ')' unless there is only
|
||||||
@# whitespace before it
|
@# whitespace before it
|
||||||
@@ -234,7 +224,7 @@ TAGS_PREREQ := $(TAGS_EVIDENCE)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: generate-swagger
|
.PHONY: generate-swagger
|
||||||
generate-swagger: $(SWAGGER_SPEC) ## generate the swagger spec from code comments
|
generate-swagger: $(SWAGGER_SPEC) $(OPENAPI3_SPEC) ## generate the swagger spec from code comments
|
||||||
|
|
||||||
$(SWAGGER_SPEC): $(GO_SOURCES) $(SWAGGER_SPEC_INPUT)
|
$(SWAGGER_SPEC): $(GO_SOURCES) $(SWAGGER_SPEC_INPUT)
|
||||||
$(GO) run $(SWAGGER_PACKAGE) generate spec --exclude "$(SWAGGER_EXCLUDE)" --input "$(SWAGGER_SPEC_INPUT)" --output './$(SWAGGER_SPEC)'
|
$(GO) run $(SWAGGER_PACKAGE) generate spec --exclude "$(SWAGGER_EXCLUDE)" --input "$(SWAGGER_SPEC_INPUT)" --output './$(SWAGGER_SPEC)'
|
||||||
@@ -256,6 +246,21 @@ swagger-validate: ## check if the swagger spec is valid
|
|||||||
$(GO) run $(SWAGGER_PACKAGE) validate './$(SWAGGER_SPEC)'
|
$(GO) run $(SWAGGER_PACKAGE) validate './$(SWAGGER_SPEC)'
|
||||||
@$(SED_INPLACE) -E -e 's|"basePath":( *)"/(.*)"|"basePath":\1"\2"|g' './$(SWAGGER_SPEC)' # remove the prefix slash from basePath
|
@$(SED_INPLACE) -E -e 's|"basePath":( *)"/(.*)"|"basePath":\1"\2"|g' './$(SWAGGER_SPEC)' # remove the prefix slash from basePath
|
||||||
|
|
||||||
|
.PHONY: generate-openapi3
|
||||||
|
generate-openapi3: $(OPENAPI3_SPEC) ## generate the OpenAPI 3.0 spec from the Swagger 2.0 spec
|
||||||
|
|
||||||
|
$(OPENAPI3_SPEC): $(SWAGGER_SPEC) build/generate-openapi.go $(wildcard build/openapi3gen/*.go)
|
||||||
|
$(GO) run build/generate-openapi.go
|
||||||
|
|
||||||
|
.PHONY: openapi3-check
|
||||||
|
openapi3-check: generate-openapi3
|
||||||
|
@diff=$$(git diff --color=always '$(OPENAPI3_SPEC)'); \
|
||||||
|
if [ -n "$$diff" ]; then \
|
||||||
|
echo "Please run 'make generate-openapi3' and commit the result:"; \
|
||||||
|
printf "%s" "$${diff}"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
.PHONY: checks
|
.PHONY: checks
|
||||||
checks: checks-frontend checks-backend ## run various consistency checks
|
checks: checks-frontend checks-backend ## run various consistency checks
|
||||||
|
|
||||||
@@ -263,10 +268,10 @@ checks: checks-frontend checks-backend ## run various consistency checks
|
|||||||
checks-frontend: lockfile-check svg-check ## check frontend files
|
checks-frontend: lockfile-check svg-check ## check frontend files
|
||||||
|
|
||||||
.PHONY: checks-backend
|
.PHONY: checks-backend
|
||||||
checks-backend: tidy-check swagger-check fmt-check swagger-validate security-check ## check backend files
|
checks-backend: tidy-check swagger-check openapi3-check fmt-check swagger-validate security-check ## check backend files
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint: lint-frontend lint-backend lint-spell ## lint everything
|
lint: lint-frontend lint-backend lint-templates lint-swagger lint-spell lint-md lint-actions lint-json lint-yaml ## lint everything
|
||||||
|
|
||||||
.PHONY: lint-fix
|
.PHONY: lint-fix
|
||||||
lint-fix: lint-frontend-fix lint-backend-fix lint-spell-fix ## lint everything and fix issues
|
lint-fix: lint-frontend-fix lint-backend-fix lint-spell-fix ## lint everything and fix issues
|
||||||
@@ -278,10 +283,10 @@ lint-frontend: lint-js lint-css ## lint frontend files
|
|||||||
lint-frontend-fix: lint-js-fix lint-css-fix ## lint frontend files and fix issues
|
lint-frontend-fix: lint-js-fix lint-css-fix ## lint frontend files and fix issues
|
||||||
|
|
||||||
.PHONY: lint-backend
|
.PHONY: lint-backend
|
||||||
lint-backend: lint-go lint-go-gitea-vet lint-editorconfig ## lint backend files
|
lint-backend: lint-go lint-editorconfig ## lint backend files
|
||||||
|
|
||||||
.PHONY: lint-backend-fix
|
.PHONY: lint-backend-fix
|
||||||
lint-backend-fix: lint-go-fix lint-go-gitea-vet lint-editorconfig ## lint backend files and fix issues
|
lint-backend-fix: lint-go-fix lint-editorconfig ## lint backend files and fix issues
|
||||||
|
|
||||||
.PHONY: lint-js
|
.PHONY: lint-js
|
||||||
lint-js: node_modules ## lint js and ts files
|
lint-js: node_modules ## lint js and ts files
|
||||||
@@ -313,6 +318,10 @@ lint-md: node_modules ## lint markdown files
|
|||||||
lint-md-fix: node_modules ## lint markdown files and fix issues
|
lint-md-fix: node_modules ## lint markdown files and fix issues
|
||||||
pnpm exec markdownlint --fix *.md
|
pnpm exec markdownlint --fix *.md
|
||||||
|
|
||||||
|
.PHONY: lint-pr-title
|
||||||
|
lint-pr-title: ## lint PR title against Conventional Commits (set PR_TITLE=...)
|
||||||
|
@node ./tools/lint-pr-title.js
|
||||||
|
|
||||||
.PHONY: lint-spell
|
.PHONY: lint-spell
|
||||||
lint-spell: ## lint spelling
|
lint-spell: ## lint spelling
|
||||||
@git ls-files $(SPELLCHECK_FILES) | xargs go run $(MISSPELL_PACKAGE) -dict assets/misspellings.csv -error
|
@git ls-files $(SPELLCHECK_FILES) | xargs go run $(MISSPELL_PACKAGE) -dict assets/misspellings.csv -error
|
||||||
@@ -336,11 +345,6 @@ lint-go-windows:
|
|||||||
@GOOS= GOARCH= $(GO) install $(GOLANGCI_LINT_PACKAGE)
|
@GOOS= GOARCH= $(GO) install $(GOLANGCI_LINT_PACKAGE)
|
||||||
golangci-lint run
|
golangci-lint run
|
||||||
|
|
||||||
.PHONY: lint-go-gitea-vet
|
|
||||||
lint-go-gitea-vet: ## lint go files with gitea-vet
|
|
||||||
@echo "Running gitea-vet..."
|
|
||||||
@$(GO) vet -vettool="$(shell GOOS= GOARCH= go tool -n gitea-vet)" ./...
|
|
||||||
|
|
||||||
.PHONY: lint-editorconfig
|
.PHONY: lint-editorconfig
|
||||||
lint-editorconfig:
|
lint-editorconfig:
|
||||||
@echo "Running editorconfig check..."
|
@echo "Running editorconfig check..."
|
||||||
@@ -379,13 +383,10 @@ watch-frontend: node_modules ## start vite dev server for frontend
|
|||||||
watch-backend: ## watch backend files and continuously rebuild
|
watch-backend: ## watch backend files and continuously rebuild
|
||||||
GITEA_RUN_MODE=dev $(GO) run $(AIR_PACKAGE) -c .air.toml
|
GITEA_RUN_MODE=dev $(GO) run $(AIR_PACKAGE) -c .air.toml
|
||||||
|
|
||||||
.PHONY: test
|
|
||||||
test: test-frontend test-backend ## test everything
|
|
||||||
|
|
||||||
.PHONY: test-backend
|
.PHONY: test-backend
|
||||||
test-backend: ## test backend files
|
test-backend: ## test backend files
|
||||||
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
|
@echo "Running go test with $(GOTEST_FLAGS) -tags '$(TAGS)'..."
|
||||||
@$(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_PACKAGES)
|
@$(GO) test $(GOTEST_FLAGS) -tags='$(TAGS)' $(GO_TEST_PACKAGES)
|
||||||
|
|
||||||
.PHONY: test-frontend
|
.PHONY: test-frontend
|
||||||
test-frontend: node_modules ## test frontend files
|
test-frontend: node_modules ## test frontend files
|
||||||
@@ -403,10 +404,10 @@ test-check:
|
|||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
.PHONY: test\#%
|
.PHONY: test-backend\#%
|
||||||
test\#%:
|
test-backend\#%:
|
||||||
@echo "Running go test with -tags '$(TEST_TAGS)'..."
|
@echo "Running go test with -tags '$(TAGS)'..."
|
||||||
@$(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES)
|
@$(GO) test $(GOTEST_FLAGS) -tags='$(TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES)
|
||||||
|
|
||||||
.PHONY: coverage
|
.PHONY: coverage
|
||||||
coverage:
|
coverage:
|
||||||
@@ -416,8 +417,8 @@ coverage:
|
|||||||
|
|
||||||
.PHONY: unit-test-coverage
|
.PHONY: unit-test-coverage
|
||||||
unit-test-coverage:
|
unit-test-coverage:
|
||||||
@echo "Running unit-test-coverage $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
|
@echo "Running unit-test-coverage $(GOTEST_FLAGS) -tags '$(TAGS)'..."
|
||||||
@$(GO) test $(GOTESTFLAGS) -timeout=20m -tags='$(TEST_TAGS)' -cover -coverprofile coverage.out $(GO_TEST_PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1
|
@$(GO) test $(GOTEST_FLAGS) -tags='$(TAGS)' -cover -coverprofile coverage.out $(GO_TEST_PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1
|
||||||
|
|
||||||
.PHONY: tidy
|
.PHONY: tidy
|
||||||
tidy: ## run go mod tidy
|
tidy: ## run go mod tidy
|
||||||
@@ -444,83 +445,33 @@ go-licenses: $(GO_LICENSE_FILE) ## regenerate go licenses
|
|||||||
$(GO_LICENSE_FILE): go.mod go.sum
|
$(GO_LICENSE_FILE): go.mod go.sum
|
||||||
GO=$(GO) $(GO) run build/generate-go-licenses.go $(GO_LICENSE_FILE)
|
GO=$(GO) $(GO) run build/generate-go-licenses.go $(GO_LICENSE_FILE)
|
||||||
|
|
||||||
generate-ini-sqlite:
|
.PHONY: test-integration
|
||||||
sed -e 's|{{WORK_PATH}}|$(CURDIR)/tests/$(or $(TEST_TYPE),integration)/gitea-$(or $(TEST_TYPE),integration)-sqlite|g' \
|
test-integration:
|
||||||
-e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \
|
@# Use a compiled binary: testlogger forwards gitea logs to t.Log, so `go test -v`
|
||||||
tests/sqlite.ini.tmpl > tests/sqlite.ini
|
@# would flood output per passing test. testcache can't help these tests anyway —
|
||||||
|
@# they mutate the work directory, so cache inputs change between runs.
|
||||||
|
$(GO) test $(GOTEST_FLAGS) -tags '$(TAGS)' -c code.gitea.io/gitea/tests/integration -o ./test-integration-$(GITEA_TEST_DATABASE).test
|
||||||
|
./test-integration-$(GITEA_TEST_DATABASE).test
|
||||||
|
|
||||||
.PHONY: test-sqlite
|
.PHONY: test-integration\#%
|
||||||
test-sqlite: integrations.sqlite.test generate-ini-sqlite
|
test-integration\#%:
|
||||||
GITEA_TEST_CONF=tests/sqlite.ini ./integrations.sqlite.test
|
$(GO) test $(GOTEST_FLAGS) -tags '$(TAGS)' -run $(subst .,/,$*) code.gitea.io/gitea/tests/integration
|
||||||
|
|
||||||
.PHONY: test-sqlite\#%
|
.PHONY: test-migration
|
||||||
test-sqlite\#%: integrations.sqlite.test generate-ini-sqlite
|
test-migration: migrations.integration.test migrations.individual.test
|
||||||
GITEA_TEST_CONF=tests/sqlite.ini ./integrations.sqlite.test -test.run $(subst .,/,$*)
|
|
||||||
|
|
||||||
.PHONY: test-sqlite-migration
|
.PHONY: migrations.integration.test
|
||||||
test-sqlite-migration: migrations.sqlite.test migrations.individual.sqlite.test
|
migrations.integration.test:
|
||||||
|
$(GO) test $(GOTEST_FLAGS) -tags '$(TAGS)' code.gitea.io/gitea/tests/integration/migration-test
|
||||||
|
|
||||||
generate-ini-mysql:
|
.PHONY: migrations.individual.test
|
||||||
sed -e 's|{{TEST_MYSQL_HOST}}|${TEST_MYSQL_HOST}|g' \
|
migrations.individual.test:
|
||||||
-e 's|{{TEST_MYSQL_DBNAME}}|${TEST_MYSQL_DBNAME}|g' \
|
@# tests of multiple packages use the same database, don't run in parallel
|
||||||
-e 's|{{TEST_MYSQL_USERNAME}}|${TEST_MYSQL_USERNAME}|g' \
|
$(GO) test $(GOTEST_FLAGS) -tags '$(TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
||||||
-e 's|{{TEST_MYSQL_PASSWORD}}|${TEST_MYSQL_PASSWORD}|g' \
|
|
||||||
-e 's|{{WORK_PATH}}|$(CURDIR)/tests/$(or $(TEST_TYPE),integration)/gitea-$(or $(TEST_TYPE),integration)-mysql|g' \
|
|
||||||
-e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \
|
|
||||||
tests/mysql.ini.tmpl > tests/mysql.ini
|
|
||||||
|
|
||||||
.PHONY: test-mysql
|
.PHONY: migrations.individual.test\#%
|
||||||
test-mysql: integrations.mysql.test generate-ini-mysql
|
migrations.individual.test\#%:
|
||||||
GITEA_TEST_CONF=tests/mysql.ini ./integrations.mysql.test
|
$(GO) test $(GOTEST_FLAGS) -tags '$(TAGS)' code.gitea.io/gitea/models/migrations/$*
|
||||||
|
|
||||||
.PHONY: test-mysql\#%
|
|
||||||
test-mysql\#%: integrations.mysql.test generate-ini-mysql
|
|
||||||
GITEA_TEST_CONF=tests/mysql.ini ./integrations.mysql.test -test.run $(subst .,/,$*)
|
|
||||||
|
|
||||||
.PHONY: test-mysql-migration
|
|
||||||
test-mysql-migration: migrations.mysql.test migrations.individual.mysql.test
|
|
||||||
|
|
||||||
generate-ini-pgsql:
|
|
||||||
sed -e 's|{{TEST_PGSQL_HOST}}|${TEST_PGSQL_HOST}|g' \
|
|
||||||
-e 's|{{TEST_PGSQL_DBNAME}}|${TEST_PGSQL_DBNAME}|g' \
|
|
||||||
-e 's|{{TEST_PGSQL_USERNAME}}|${TEST_PGSQL_USERNAME}|g' \
|
|
||||||
-e 's|{{TEST_PGSQL_PASSWORD}}|${TEST_PGSQL_PASSWORD}|g' \
|
|
||||||
-e 's|{{TEST_PGSQL_SCHEMA}}|${TEST_PGSQL_SCHEMA}|g' \
|
|
||||||
-e 's|{{TEST_MINIO_ENDPOINT}}|${TEST_MINIO_ENDPOINT}|g' \
|
|
||||||
-e 's|{{WORK_PATH}}|$(CURDIR)/tests/$(or $(TEST_TYPE),integration)/gitea-$(or $(TEST_TYPE),integration)-pgsql|g' \
|
|
||||||
-e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \
|
|
||||||
tests/pgsql.ini.tmpl > tests/pgsql.ini
|
|
||||||
|
|
||||||
.PHONY: test-pgsql
|
|
||||||
test-pgsql: integrations.pgsql.test generate-ini-pgsql
|
|
||||||
GITEA_TEST_CONF=tests/pgsql.ini ./integrations.pgsql.test
|
|
||||||
|
|
||||||
.PHONY: test-pgsql\#%
|
|
||||||
test-pgsql\#%: integrations.pgsql.test generate-ini-pgsql
|
|
||||||
GITEA_TEST_CONF=tests/pgsql.ini ./integrations.pgsql.test -test.run $(subst .,/,$*)
|
|
||||||
|
|
||||||
.PHONY: test-pgsql-migration
|
|
||||||
test-pgsql-migration: migrations.pgsql.test migrations.individual.pgsql.test
|
|
||||||
|
|
||||||
generate-ini-mssql:
|
|
||||||
sed -e 's|{{TEST_MSSQL_HOST}}|${TEST_MSSQL_HOST}|g' \
|
|
||||||
-e 's|{{TEST_MSSQL_DBNAME}}|${TEST_MSSQL_DBNAME}|g' \
|
|
||||||
-e 's|{{TEST_MSSQL_USERNAME}}|${TEST_MSSQL_USERNAME}|g' \
|
|
||||||
-e 's|{{TEST_MSSQL_PASSWORD}}|${TEST_MSSQL_PASSWORD}|g' \
|
|
||||||
-e 's|{{WORK_PATH}}|$(CURDIR)/tests/$(or $(TEST_TYPE),integration)/gitea-$(or $(TEST_TYPE),integration)-mssql|g' \
|
|
||||||
-e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \
|
|
||||||
tests/mssql.ini.tmpl > tests/mssql.ini
|
|
||||||
|
|
||||||
.PHONY: test-mssql
|
|
||||||
test-mssql: integrations.mssql.test generate-ini-mssql
|
|
||||||
GITEA_TEST_CONF=tests/mssql.ini ./integrations.mssql.test
|
|
||||||
|
|
||||||
.PHONY: test-mssql\#%
|
|
||||||
test-mssql\#%: integrations.mssql.test generate-ini-mssql
|
|
||||||
GITEA_TEST_CONF=tests/mssql.ini ./integrations.mssql.test -test.run $(subst .,/,$*)
|
|
||||||
|
|
||||||
.PHONY: test-mssql-migration
|
|
||||||
test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test
|
|
||||||
|
|
||||||
.PHONY: playwright
|
.PHONY: playwright
|
||||||
playwright: deps-frontend
|
playwright: deps-frontend
|
||||||
@@ -528,109 +479,8 @@ playwright: deps-frontend
|
|||||||
@pnpm exec playwright install $(if $(GITHUB_ACTIONS),,--with-deps) chromium firefox $(PLAYWRIGHT_FLAGS)
|
@pnpm exec playwright install $(if $(GITHUB_ACTIONS),,--with-deps) chromium firefox $(PLAYWRIGHT_FLAGS)
|
||||||
|
|
||||||
.PHONY: test-e2e
|
.PHONY: test-e2e
|
||||||
test-e2e: playwright $(EXECUTABLE_E2E)
|
test-e2e: playwright frontend backend
|
||||||
@EXECUTABLE=$(EXECUTABLE_E2E) ./tools/test-e2e.sh $(GITEA_TEST_E2E_FLAGS)
|
@EXECUTABLE=$(EXECUTABLE) ./tools/test-e2e.sh $(GITEA_TEST_E2E_FLAGS)
|
||||||
|
|
||||||
.PHONY: bench-sqlite
|
|
||||||
bench-sqlite: integrations.sqlite.test generate-ini-sqlite
|
|
||||||
GITEA_TEST_CONF=tests/sqlite.ini ./integrations.sqlite.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
|
|
||||||
|
|
||||||
.PHONY: bench-mysql
|
|
||||||
bench-mysql: integrations.mysql.test generate-ini-mysql
|
|
||||||
GITEA_TEST_CONF=tests/mysql.ini ./integrations.mysql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
|
|
||||||
|
|
||||||
.PHONY: bench-mssql
|
|
||||||
bench-mssql: integrations.mssql.test generate-ini-mssql
|
|
||||||
GITEA_TEST_CONF=tests/mssql.ini ./integrations.mssql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
|
|
||||||
|
|
||||||
.PHONY: bench-pgsql
|
|
||||||
bench-pgsql: integrations.pgsql.test generate-ini-pgsql
|
|
||||||
GITEA_TEST_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_TEST_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_TEST_CONF=tests/sqlite.ini ./integrations.cover.sqlite.test -test.coverprofile=integration.coverage.out
|
|
||||||
|
|
||||||
integrations.mysql.test: git-check $(GO_SOURCES)
|
|
||||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.mysql.test
|
|
||||||
|
|
||||||
integrations.pgsql.test: git-check $(GO_SOURCES)
|
|
||||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.pgsql.test
|
|
||||||
|
|
||||||
integrations.mssql.test: git-check $(GO_SOURCES)
|
|
||||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.mssql.test
|
|
||||||
|
|
||||||
integrations.sqlite.test: git-check $(GO_SOURCES)
|
|
||||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.sqlite.test -tags '$(TEST_TAGS)'
|
|
||||||
|
|
||||||
integrations.cover.test: git-check $(GO_SOURCES)
|
|
||||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -coverpkg $(shell echo $(GO_TEST_PACKAGES) | tr ' ' ',') -o integrations.cover.test
|
|
||||||
|
|
||||||
integrations.cover.sqlite.test: git-check $(GO_SOURCES)
|
|
||||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/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
|
|
||||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.mysql.test
|
|
||||||
GITEA_TEST_CONF=tests/mysql.ini ./migrations.mysql.test
|
|
||||||
|
|
||||||
.PHONY: migrations.pgsql.test
|
|
||||||
migrations.pgsql.test: $(GO_SOURCES) generate-ini-pgsql
|
|
||||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.pgsql.test
|
|
||||||
GITEA_TEST_CONF=tests/pgsql.ini ./migrations.pgsql.test
|
|
||||||
|
|
||||||
.PHONY: migrations.mssql.test
|
|
||||||
migrations.mssql.test: $(GO_SOURCES) generate-ini-mssql
|
|
||||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.mssql.test
|
|
||||||
GITEA_TEST_CONF=tests/mssql.ini ./migrations.mssql.test
|
|
||||||
|
|
||||||
.PHONY: migrations.sqlite.test
|
|
||||||
migrations.sqlite.test: $(GO_SOURCES) generate-ini-sqlite
|
|
||||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.sqlite.test -tags '$(TEST_TAGS)'
|
|
||||||
GITEA_TEST_CONF=tests/sqlite.ini ./migrations.sqlite.test
|
|
||||||
|
|
||||||
.PHONY: migrations.individual.mysql.test
|
|
||||||
migrations.individual.mysql.test: $(GO_SOURCES) generate-ini-mysql
|
|
||||||
GITEA_TEST_CONF=tests/mysql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
|
||||||
|
|
||||||
.PHONY: migrations.individual.sqlite.test\#%
|
|
||||||
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
|
|
||||||
GITEA_TEST_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
|
|
||||||
|
|
||||||
.PHONY: migrations.individual.pgsql.test
|
|
||||||
migrations.individual.pgsql.test: $(GO_SOURCES) generate-ini-pgsql
|
|
||||||
GITEA_TEST_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
|
||||||
|
|
||||||
.PHONY: migrations.individual.pgsql.test\#%
|
|
||||||
migrations.individual.pgsql.test\#%: $(GO_SOURCES) generate-ini-pgsql
|
|
||||||
GITEA_TEST_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
|
|
||||||
|
|
||||||
.PHONY: migrations.individual.mssql.test
|
|
||||||
migrations.individual.mssql.test: $(GO_SOURCES) generate-ini-mssql
|
|
||||||
GITEA_TEST_CONF=tests/mssql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
|
||||||
|
|
||||||
.PHONY: migrations.individual.mssql.test\#%
|
|
||||||
migrations.individual.mssql.test\#%: $(GO_SOURCES) generate-ini-mssql
|
|
||||||
GITEA_TEST_CONF=tests/mssql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
|
|
||||||
|
|
||||||
.PHONY: migrations.individual.sqlite.test
|
|
||||||
migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite
|
|
||||||
GITEA_TEST_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
|
||||||
|
|
||||||
.PHONY: migrations.individual.sqlite.test\#%
|
|
||||||
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
|
|
||||||
GITEA_TEST_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
|
|
||||||
|
|
||||||
.PHONY: check
|
|
||||||
check: test
|
|
||||||
|
|
||||||
.PHONY: install $(TAGS_PREREQ)
|
|
||||||
install: $(wildcard *.go)
|
|
||||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) install -v -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)'
|
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build: frontend backend ## build everything
|
build: frontend backend ## build everything
|
||||||
@@ -663,9 +513,6 @@ ifneq ($(and $(STATIC),$(findstring pam,$(TAGS))),)
|
|||||||
endif
|
endif
|
||||||
CGO_ENABLED="$(CGO_ENABLED)" CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(EXTLDFLAGS) $(LDFLAGS)' -o $@
|
CGO_ENABLED="$(CGO_ENABLED)" CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(EXTLDFLAGS) $(LDFLAGS)' -o $@
|
||||||
|
|
||||||
$(EXECUTABLE_E2E): $(GO_SOURCES) $(FRONTEND_DEST)
|
|
||||||
CGO_ENABLED=1 $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TEST_TAGS)' -ldflags '-s -w $(EXTLDFLAGS) $(LDFLAGS)' -o $@
|
|
||||||
|
|
||||||
.PHONY: release
|
.PHONY: release
|
||||||
release: frontend generate release-windows release-linux release-darwin release-freebsd release-copy release-compress vendor release-sources release-check
|
release: frontend generate release-windows release-linux release-darwin release-freebsd release-copy release-compress vendor release-sources release-check
|
||||||
|
|
||||||
@@ -730,7 +577,6 @@ deps-backend: ## install backend dependencies
|
|||||||
deps-tools: ## install tool dependencies
|
deps-tools: ## install tool dependencies
|
||||||
$(GO) install $(AIR_PACKAGE) & \
|
$(GO) install $(AIR_PACKAGE) & \
|
||||||
$(GO) install $(EDITORCONFIG_CHECKER_PACKAGE) & \
|
$(GO) install $(EDITORCONFIG_CHECKER_PACKAGE) & \
|
||||||
$(GO) install $(GOFUMPT_PACKAGE) & \
|
|
||||||
$(GO) install $(GOLANGCI_LINT_PACKAGE) & \
|
$(GO) install $(GOLANGCI_LINT_PACKAGE) & \
|
||||||
$(GO) install $(GXZ_PACKAGE) & \
|
$(GO) install $(GXZ_PACKAGE) & \
|
||||||
$(GO) install $(MISSPELL_PACKAGE) & \
|
$(GO) install $(MISSPELL_PACKAGE) & \
|
||||||
@@ -761,6 +607,11 @@ update-js: node_modules ## update js dependencies
|
|||||||
pnpm exec updates -u -f package.json
|
pnpm exec updates -u -f package.json
|
||||||
rm -rf node_modules pnpm-lock.yaml
|
rm -rf node_modules pnpm-lock.yaml
|
||||||
pnpm install
|
pnpm install
|
||||||
|
@touch node_modules
|
||||||
|
$(MAKE) --no-print-directory nolyfill
|
||||||
|
|
||||||
|
.PHONY: nolyfill
|
||||||
|
nolyfill: node_modules ## apply nolyfill overrides to package.json and relock
|
||||||
pnpm exec nolyfill install
|
pnpm exec nolyfill install
|
||||||
pnpm install
|
pnpm install
|
||||||
@touch node_modules
|
@touch node_modules
|
||||||
@@ -824,11 +675,6 @@ generate-manpage: ## generate manpage
|
|||||||
@gzip -9 man/man1/gitea.1 && echo man/man1/gitea.1.gz created
|
@gzip -9 man/man1/gitea.1 && echo man/man1/gitea.1.gz created
|
||||||
@#TODO A small script that formats config-cheat-sheet.en-us.md nicely for use as a config man page
|
@#TODO A small script that formats config-cheat-sheet.en-us.md nicely for use as a config man page
|
||||||
|
|
||||||
.PHONY: docker
|
|
||||||
docker:
|
|
||||||
docker build --disable-content-trust=false -t $(DOCKER_REF) .
|
|
||||||
# support also build args docker build --build-arg GITEA_VERSION=v1.2.3 --build-arg TAGS="bindata sqlite sqlite_unlock_notify" .
|
|
||||||
|
|
||||||
# Disable parallel execution because it would break some targets that don't
|
# Disable parallel execution because it would break some targets that don't
|
||||||
# specify exact dependencies like 'backend' which does currently not depend
|
# specify exact dependencies like 'backend' which does currently not depend
|
||||||
# on 'frontend' to enable Node.js-less builds from source tarballs.
|
# on 'frontend' to enable Node.js-less builds from source tarballs.
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"folders":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name":"MokoGitea",
|
||||||
|
"path": ".",
|
||||||
|
"folder_exclude_patterns":
|
||||||
|
[
|
||||||
|
".git",
|
||||||
|
".claude/worktree"
|
||||||
|
],
|
||||||
|
"file_exclude_patterns":
|
||||||
|
[
|
||||||
|
"*.sublime-workspace"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"Workspace",
|
||||||
|
"path": "E:\Documents\Workspace",
|
||||||
|
"folder_exclude_patterns":
|
||||||
|
[
|
||||||
|
".git",
|
||||||
|
".claude/worktree"
|
||||||
|
],
|
||||||
|
"file_exclude_patterns":
|
||||||
|
[
|
||||||
|
"*.sublime-workspace"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"Scripts",
|
||||||
|
"path": "J:\Shared drives\Knowledgebase\Scripts",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
@@ -1,212 +1,43 @@
|
|||||||
# Gitea
|
# MokoGitea
|
||||||
|
|
||||||
[](https://github.com/go-gitea/gitea/actions/workflows/release-nightly.yml?query=branch%3Amain "Release Nightly")
|
Moko fork of Gitea — adding project board REST API endpoints and custom enhancements
|
||||||
[](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea")
|
|
||||||
[](https://goreportcard.com/report/code.gitea.io/gitea "Go Report Card")
|
|
||||||
[](https://pkg.go.dev/code.gitea.io/gitea "GoDoc")
|
|
||||||
[](https://github.com/go-gitea/gitea/releases/latest "GitHub release")
|
|
||||||
[](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source")
|
|
||||||
[](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
|
|
||||||
[](https://opensource.org/licenses/MIT "License: MIT")
|
|
||||||
[](https://translate.gitea.com "Crowdin")
|
|
||||||
|
|
||||||
[繁體中文](./README.zh-tw.md) | [简体中文](./README.zh-cn.md)
|
  
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
The goal of this project is to make the easiest, fastest, and most
|
Custom Gitea fork with Project Board API
|
||||||
painless way of setting up a self-hosted Git service.
|
|
||||||
|
|
||||||
As Gitea is written in Go, it works across **all** the platforms and
|
---
|
||||||
architectures that are supported by Go, including Linux, macOS, and
|
|
||||||
Windows on x86, amd64, ARM and PowerPC architectures.
|
|
||||||
This project has been
|
|
||||||
[forked](https://blog.gitea.com/welcome-to-gitea/) from
|
|
||||||
[Gogs](https://gogs.io) since November of 2016, but a lot has changed.
|
|
||||||
|
|
||||||
For online demonstrations, you can visit [demo.gitea.com](https://demo.gitea.com).
|
## Pages
|
||||||
|
|
||||||
For accessing free Gitea service (with a limited number of repositories), you can visit [gitea.com](https://gitea.com/user/login).
|
- [Branding](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/wiki/Branding)
|
||||||
|
- [Deployment](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/wiki/Deployment)
|
||||||
|
- [Project API](Project API)
|
||||||
|
- [roadmap](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/wiki/roadmap)
|
||||||
|
|
||||||
To quickly deploy your own dedicated Gitea instance on Gitea Cloud, you can start a free trial at [cloud.gitea.com](https://cloud.gitea.com).
|
---
|
||||||
|
|
||||||
|
**Category:** Infrastructure | **Platform:** [moko-platform wiki](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
You can find comprehensive documentation on our official [documentation website](https://docs.gitea.com/).
|
Full documentation is available on the [Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/wiki).
|
||||||
|
|
||||||
It includes installation, administration, usage, development, contributing guides, and more to help you get started and explore all features effectively.
|
|
||||||
|
|
||||||
If you have any suggestions or would like to contribute to it, you can visit the [documentation repository](https://gitea.com/gitea/docs)
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
From the root of the source tree, run:
|
|
||||||
|
|
||||||
TAGS="bindata" make build
|
|
||||||
|
|
||||||
or if SQLite support is required:
|
|
||||||
|
|
||||||
TAGS="bindata sqlite sqlite_unlock_notify" make build
|
|
||||||
|
|
||||||
The `build` target is split into two sub-targets:
|
|
||||||
|
|
||||||
- `make backend` which requires [Go Stable](https://go.dev/dl/), the required version is defined in [go.mod](/go.mod).
|
|
||||||
- `make frontend` which requires [Node.js LTS](https://nodejs.org/en/download/) or greater and [pnpm](https://pnpm.io/installation).
|
|
||||||
|
|
||||||
Internet connectivity is required to download the go and npm modules. When building from the official source tarballs which include pre-built frontend files, the `frontend` target will not be triggered, making it possible to build without Node.js.
|
|
||||||
|
|
||||||
More info: https://docs.gitea.com/installation/install-from-source
|
|
||||||
|
|
||||||
## Using
|
|
||||||
|
|
||||||
After building, a binary file named `gitea` will be generated in the root of the source tree by default. To run it, use:
|
|
||||||
|
|
||||||
./gitea web
|
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> If you're interested in using our APIs, we have experimental support with [documentation](https://docs.gitea.com/api).
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Expected workflow is: Fork -> Patch -> Push -> Pull Request
|
See the wiki for development guidelines and contribution instructions.
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
>
|
|
||||||
> 1. **YOU MUST READ THE [CONTRIBUTORS GUIDE](CONTRIBUTING.md) BEFORE STARTING TO WORK ON A PULL REQUEST.**
|
|
||||||
> 2. If you have found a vulnerability in the project, please write privately to **security@gitea.io**. Thanks!
|
|
||||||
|
|
||||||
## Translating
|
|
||||||
|
|
||||||
[](https://translate.gitea.com)
|
|
||||||
|
|
||||||
Translations are done through [Crowdin](https://translate.gitea.com). If you want to translate to a new language, ask one of the managers in the Crowdin project to add a new language there.
|
|
||||||
|
|
||||||
You can also just create an issue for adding a language or ask on Discord on the #translation channel. If you need context or find some translation issues, you can leave a comment on the string or ask on Discord. For general translation questions there is a section in the docs. Currently a bit empty, but we hope to fill it as questions pop up.
|
|
||||||
|
|
||||||
Get more information from [documentation](https://docs.gitea.com/contributing/localization).
|
|
||||||
|
|
||||||
## Official and Third-Party Projects
|
|
||||||
|
|
||||||
We provide an official [go-sdk](https://gitea.com/gitea/go-sdk), a CLI tool called [tea](https://gitea.com/gitea/tea) and an [action runner](https://gitea.com/gitea/act_runner) for Gitea Action.
|
|
||||||
|
|
||||||
We maintain a list of Gitea-related projects at [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea), where you can discover more third-party projects, including SDKs, plugins, themes, and more.
|
|
||||||
|
|
||||||
## Communication
|
|
||||||
|
|
||||||
[](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea")
|
|
||||||
|
|
||||||
If you have questions that are not covered by the [documentation](https://docs.gitea.com/), you can get in contact with us on our [Discord server](https://discord.gg/Gitea) or create a post in the [discourse forum](https://forum.gitea.com/).
|
|
||||||
|
|
||||||
## Authors
|
|
||||||
|
|
||||||
- [Maintainers](https://github.com/orgs/go-gitea/people)
|
|
||||||
- [Contributors](https://github.com/go-gitea/gitea/graphs/contributors)
|
|
||||||
- [Translators](options/locale/TRANSLATORS)
|
|
||||||
|
|
||||||
## Backers
|
|
||||||
|
|
||||||
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/gitea#backer)]
|
|
||||||
|
|
||||||
<a href="https://opencollective.com/gitea#backers" target="_blank"><img src="https://opencollective.com/gitea/backers.svg?width=890"></a>
|
|
||||||
|
|
||||||
## Sponsors
|
|
||||||
|
|
||||||
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/gitea#sponsor)]
|
|
||||||
|
|
||||||
<a href="https://opencollective.com/gitea/sponsor/0/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/0/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/gitea/sponsor/1/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/1/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/gitea/sponsor/2/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/2/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/gitea/sponsor/3/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/3/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/gitea/sponsor/4/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/4/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/gitea/sponsor/5/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/5/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/gitea/sponsor/6/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/6/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/gitea/sponsor/7/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/7/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/gitea/sponsor/8/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/8/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/gitea/sponsor/9/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/9/avatar.svg"></a>
|
|
||||||
|
|
||||||
## FAQ
|
|
||||||
|
|
||||||
**How do you pronounce Gitea?**
|
|
||||||
|
|
||||||
Gitea is pronounced [/ɡɪ’ti:/](https://youtu.be/EM71-2uDAoY) as in "gi-tea" with a hard g.
|
|
||||||
|
|
||||||
**Why is this not hosted on a Gitea instance?**
|
|
||||||
|
|
||||||
We're [working on it](https://github.com/go-gitea/gitea/issues/1029).
|
|
||||||
|
|
||||||
**Where can I find the security patches?**
|
|
||||||
|
|
||||||
In the [release log](https://github.com/go-gitea/gitea/releases) or the [change log](https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md), search for the keyword `SECURITY` to find the security patches.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the MIT License.
|
This project is licensed under the GNU General Public License v3.0 or later -- see the [LICENSE](LICENSE) file.
|
||||||
See the [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) file
|
|
||||||
for the full license text.
|
|
||||||
|
|
||||||
## Further information
|
---
|
||||||
|
|
||||||
<details>
|
*[Moko Consulting](https://mokoconsulting.tech) -- [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)*
|
||||||
<summary>Looking for an overview of the interface? Check it out!</summary>
|
|
||||||
|
|
||||||
### Login/Register Page
|
|
||||||
|
|
||||||

|
|
||||||

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

|
|
||||||

|
|
||||||

|
|
||||||

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

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

|
|
||||||

|
|
||||||

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

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

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

|
|
||||||

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

|
|
||||||

|
|
||||||

|
|
||||||

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

|
|
||||||

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

|
|
||||||

|
|
||||||

|
|
||||||

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

|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|||||||
@@ -38,10 +38,6 @@
|
|||||||
|
|
||||||
TAGS="bindata" make build
|
TAGS="bindata" make build
|
||||||
|
|
||||||
如果需要 SQLite 支持:
|
|
||||||
|
|
||||||
TAGS="bindata sqlite sqlite_unlock_notify" make build
|
|
||||||
|
|
||||||
`build` 目标分为两个子目标:
|
`build` 目标分为两个子目标:
|
||||||
|
|
||||||
- `make backend` 需要 [Go Stable](https://go.dev/dl/),所需版本在 [go.mod](/go.mod) 中定义。
|
- `make backend` 需要 [Go Stable](https://go.dev/dl/),所需版本在 [go.mod](/go.mod) 中定义。
|
||||||
|
|||||||
@@ -38,10 +38,6 @@
|
|||||||
|
|
||||||
TAGS="bindata" make build
|
TAGS="bindata" make build
|
||||||
|
|
||||||
如果需要 SQLite 支援:
|
|
||||||
|
|
||||||
TAGS="bindata sqlite sqlite_unlock_notify" make build
|
|
||||||
|
|
||||||
`build` 目標分為兩個子目標:
|
`build` 目標分為兩個子目標:
|
||||||
|
|
||||||
- `make backend` 需要 [Go Stable](https://go.dev/dl/),所需版本在 [go.mod](/go.mod) 中定義。
|
- `make backend` 需要 [Go Stable](https://go.dev/dl/),所需版本在 [go.mod](/go.mod) 中定義。
|
||||||
|
|||||||
Generated
+86
-21
File diff suppressed because one or more lines are too long
@@ -9,7 +9,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/assetfs"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/assetfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/json"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/util"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ var primaryLicenseRe = regexp.MustCompile(`^(?i)(LICEN[SC]E|COPYING)$`)
|
|||||||
// ignoredNames are LicenseEntry.Name values to exclude from the output.
|
// ignoredNames are LicenseEntry.Name values to exclude from the output.
|
||||||
var ignoredNames = map[string]bool{
|
var ignoredNames = map[string]bool{
|
||||||
"code.gitea.io/gitea": true,
|
"code.gitea.io/gitea": true,
|
||||||
"code.gitea.io/gitea/options/license": true,
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/options/license": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
var excludedExt = map[string]bool{
|
var excludedExt = map[string]bool{
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// generate-openapi converts Gitea's Swagger 2.0 spec into an OpenAPI 3.0 spec.
|
||||||
|
//
|
||||||
|
// Gitea generates a Swagger 2.0 spec from code annotations (make generate-swagger).
|
||||||
|
// This tool converts it to OAS3 so that SDK generators and tools that require
|
||||||
|
// OAS3 (e.g. progenitor for Rust) can consume it directly. The conversion also
|
||||||
|
// deduplicates inline enum definitions into named schema components, producing
|
||||||
|
// cleaner SDK output with proper enum types instead of anonymous strings.
|
||||||
|
//
|
||||||
|
// Run: go run build/generate-openapi.go
|
||||||
|
// Output: templates/swagger/v1_openapi3_json.tmpl
|
||||||
|
|
||||||
|
//go:build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/build/openapi3gen"
|
||||||
|
|
||||||
|
"github.com/getkin/kin-openapi/openapi3"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
swaggerSpecPath = "templates/swagger/v1_json.tmpl"
|
||||||
|
openapi3OutPath = "templates/swagger/v1_openapi3_json.tmpl"
|
||||||
|
|
||||||
|
appSubUrlVar = "{{.SwaggerAppSubUrl}}"
|
||||||
|
appVerVar = "{{.SwaggerAppVer}}"
|
||||||
|
appNameVar = "{{.SwaggerAppName}}"
|
||||||
|
|
||||||
|
appSubUrlPlaceholder = "GITEA_APP_SUB_URL_PLACEHOLDER"
|
||||||
|
appVerPlaceholder = "0.0.0-gitea-placeholder"
|
||||||
|
appNamePlaceholder = "GiteaAppNamePlaceholder"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
appSubUrlRe = regexp.MustCompile(regexp.QuoteMeta(appSubUrlVar))
|
||||||
|
appVerRe = regexp.MustCompile(regexp.QuoteMeta(appVerVar))
|
||||||
|
appNameRe = regexp.MustCompile(regexp.QuoteMeta(appNameVar))
|
||||||
|
|
||||||
|
enumScanDirs = []string{
|
||||||
|
"modules/structs",
|
||||||
|
"modules/commitstatus",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
astEnumMap, err := openapi3gen.ScanSwaggerEnumTypes(enumScanDirs)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("scanning swagger:enum annotations: %v", err)
|
||||||
|
}
|
||||||
|
names := make([]string, 0, len(astEnumMap))
|
||||||
|
for _, n := range astEnumMap {
|
||||||
|
names = append(names, n)
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
fmt.Fprintf(os.Stderr, "discovered %d swagger:enum types: %s\n", len(names), strings.Join(names, ", "))
|
||||||
|
|
||||||
|
data, err := os.ReadFile(swaggerSpecPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("reading swagger spec: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cleaned := appSubUrlRe.ReplaceAll(data, []byte(appSubUrlPlaceholder))
|
||||||
|
cleaned = appVerRe.ReplaceAll(cleaned, []byte(appVerPlaceholder))
|
||||||
|
cleaned = appNameRe.ReplaceAll(cleaned, []byte(appNamePlaceholder))
|
||||||
|
|
||||||
|
oas3, err := openapi3gen.Convert(cleaned, astEnumMap)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("converting to openapi 3.0: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
oas3.Servers = openapi3.Servers{
|
||||||
|
{URL: appSubUrlPlaceholder + "/api/v1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := json.MarshalIndent(oas3, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("marshaling openapi 3.0: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := strings.ReplaceAll(string(out), appSubUrlPlaceholder, appSubUrlVar)
|
||||||
|
result = strings.ReplaceAll(result, appVerPlaceholder, appVerVar)
|
||||||
|
result = strings.ReplaceAll(result, appNamePlaceholder, appNameVar)
|
||||||
|
result = strings.TrimSpace(result)
|
||||||
|
|
||||||
|
if err := os.WriteFile(openapi3OutPath, []byte(result), 0o644); err != nil {
|
||||||
|
log.Fatalf("writing openapi 3.0 spec: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Generated %s\n", openapi3OutPath)
|
||||||
|
}
|
||||||
@@ -0,0 +1,281 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package openapi3gen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/json"
|
||||||
|
|
||||||
|
"github.com/getkin/kin-openapi/openapi2"
|
||||||
|
"github.com/getkin/kin-openapi/openapi2conv"
|
||||||
|
"github.com/getkin/kin-openapi/openapi3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// rxDeprecated matches "deprecated" as a word at the start of a description
|
||||||
|
// or preceded by whitespace/punctuation that indicates a leading marker (e.g.
|
||||||
|
// "Deprecated: true", "deprecated (use X instead)"). Rejects negated phrases
|
||||||
|
// like "not deprecated" or "previously deprecated, now supported".
|
||||||
|
var rxDeprecated = regexp.MustCompile(`(?i)(?:^|[\n.;])\s*deprecated\b`)
|
||||||
|
|
||||||
|
// Convert parses a Swagger 2.0 spec and returns an OAS3 spec, applying
|
||||||
|
// Gitea-specific post-processing: file-schema fixups, URI formats,
|
||||||
|
// deprecated flags, and shared-enum extraction.
|
||||||
|
//
|
||||||
|
// astEnumMap is a value-set-key → Go-type-name map (built by
|
||||||
|
// ScanSwaggerEnumTypes). If a shared enum in the spec has no entry in the
|
||||||
|
// map, Convert returns an error — no fallback naming.
|
||||||
|
func Convert(swaggerJSON []byte, astEnumMap map[string]string) (*openapi3.T, error) {
|
||||||
|
var swagger2 openapi2.T
|
||||||
|
if err := json.Unmarshal(swaggerJSON, &swagger2); err != nil {
|
||||||
|
return nil, fmt.Errorf("parsing swagger 2.0: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
oas3, err := openapi2conv.ToV3(&swagger2)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting to openapi 3.0: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fixFileSchemas(oas3)
|
||||||
|
addURIFormats(oas3)
|
||||||
|
addDeprecatedFlags(oas3)
|
||||||
|
if err := extractSharedEnums(oas3, astEnumMap); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return oas3, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fixFileSchemas(doc *openapi3.T) {
|
||||||
|
for _, pathItem := range doc.Paths.Map() {
|
||||||
|
for _, op := range []*openapi3.Operation{
|
||||||
|
pathItem.Get, pathItem.Post, pathItem.Put, pathItem.Patch,
|
||||||
|
pathItem.Delete, pathItem.Head, pathItem.Options, pathItem.Trace,
|
||||||
|
} {
|
||||||
|
if op == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, resp := range op.Responses.Map() {
|
||||||
|
if resp.Value == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, mediaType := range resp.Value.Content {
|
||||||
|
fixSchema(mediaType.Schema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if op.RequestBody != nil && op.RequestBody.Value != nil {
|
||||||
|
for _, mediaType := range op.RequestBody.Value.Content {
|
||||||
|
fixSchema(mediaType.Schema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fixSchema rewrites any "type: file" schemas to the OAS3 equivalent
|
||||||
|
// (type: string, format: binary), recursing into Properties, Items, and
|
||||||
|
// AllOf/OneOf/AnyOf/Not branches. $ref nodes are skipped so shared schemas
|
||||||
|
// are rewritten exactly once when visited through their declaration.
|
||||||
|
func fixSchema(ref *openapi3.SchemaRef) {
|
||||||
|
if ref == nil || ref.Value == nil || ref.Ref != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s := ref.Value
|
||||||
|
if s.Type.Is("file") {
|
||||||
|
s.Type = &openapi3.Types{"string"}
|
||||||
|
s.Format = "binary"
|
||||||
|
}
|
||||||
|
for _, p := range s.Properties {
|
||||||
|
fixSchema(p)
|
||||||
|
}
|
||||||
|
fixSchema(s.Items)
|
||||||
|
for _, sub := range s.AllOf {
|
||||||
|
fixSchema(sub)
|
||||||
|
}
|
||||||
|
for _, sub := range s.OneOf {
|
||||||
|
fixSchema(sub)
|
||||||
|
}
|
||||||
|
for _, sub := range s.AnyOf {
|
||||||
|
fixSchema(sub)
|
||||||
|
}
|
||||||
|
fixSchema(s.Not)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addURIFormats sets format: uri on string properties whose names indicate
|
||||||
|
// they hold URLs. This information is lost in Swagger 2.0 but is valuable
|
||||||
|
// for code generators.
|
||||||
|
func addURIFormats(doc *openapi3.T) {
|
||||||
|
if doc.Components == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, schemaRef := range doc.Components.Schemas {
|
||||||
|
if schemaRef.Value == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for propName, propRef := range schemaRef.Value.Properties {
|
||||||
|
if propRef == nil || propRef.Value == nil || propRef.Ref != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
prop := propRef.Value
|
||||||
|
if !prop.Type.Is("string") || prop.Format != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if isURLProperty(propName) {
|
||||||
|
prop.Format = "uri"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isURLProperty(name string) bool {
|
||||||
|
if strings.HasSuffix(name, "_url") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
switch name {
|
||||||
|
case "url", "html_url", "clone_url":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// addDeprecatedFlags sets deprecated: true on schema properties whose
|
||||||
|
// description starts with a "deprecated" marker (e.g. "Deprecated: true"
|
||||||
|
// or "deprecated (use X instead)"). Does not match negated phrases.
|
||||||
|
func addDeprecatedFlags(doc *openapi3.T) {
|
||||||
|
if doc.Components == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, schemaRef := range doc.Components.Schemas {
|
||||||
|
if schemaRef.Value == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, propRef := range schemaRef.Value.Properties {
|
||||||
|
if propRef == nil || propRef.Value == nil || propRef.Ref != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if rxDeprecated.MatchString(propRef.Value.Description) {
|
||||||
|
propRef.Value.Deprecated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type enumUsage struct {
|
||||||
|
schemaName string
|
||||||
|
propName string
|
||||||
|
propRef *openapi3.SchemaRef
|
||||||
|
inItems bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractSharedEnums finds identical enum arrays used by multiple schema
|
||||||
|
// properties, creates a standalone named schema for each, and replaces
|
||||||
|
// the inline enums with $ref pointers.
|
||||||
|
//
|
||||||
|
// If the derived enum name collides with an existing component schema, or
|
||||||
|
// no // swagger:enum annotation matches the value set, generation aborts
|
||||||
|
// with an actionable error — there are no silent fallbacks.
|
||||||
|
func extractSharedEnums(doc *openapi3.T, astEnumMap map[string]string) error {
|
||||||
|
if doc.Components == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
enumGroups := map[string][]enumUsage{}
|
||||||
|
|
||||||
|
for schemaName, schemaRef := range doc.Components.Schemas {
|
||||||
|
if schemaRef.Value == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for propName, propRef := range schemaRef.Value.Properties {
|
||||||
|
if propRef == nil || propRef.Value == nil || propRef.Ref != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(propRef.Value.Enum) > 1 && propRef.Value.Type.Is("string") {
|
||||||
|
key := EnumKey(propRef.Value.Enum)
|
||||||
|
enumGroups[key] = append(enumGroups[key], enumUsage{schemaName, propName, propRef, false})
|
||||||
|
}
|
||||||
|
if propRef.Value.Type.Is("array") && propRef.Value.Items != nil &&
|
||||||
|
propRef.Value.Items.Value != nil && propRef.Value.Items.Ref == "" &&
|
||||||
|
len(propRef.Value.Items.Value.Enum) > 1 && propRef.Value.Items.Value.Type.Is("string") {
|
||||||
|
key := EnumKey(propRef.Value.Items.Value.Enum)
|
||||||
|
enumGroups[key] = append(enumGroups[key], enumUsage{schemaName, propName, propRef, true})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, usages := range enumGroups {
|
||||||
|
if len(usages) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
enumName, err := deriveEnumName(key, usages, astEnumMap)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, exists := doc.Components.Schemas[enumName]; exists {
|
||||||
|
return fmt.Errorf("enum name collision: %s already exists as a component schema", enumName)
|
||||||
|
}
|
||||||
|
|
||||||
|
var enumValues []any
|
||||||
|
if usages[0].inItems {
|
||||||
|
enumValues = usages[0].propRef.Value.Items.Value.Enum
|
||||||
|
} else {
|
||||||
|
enumValues = usages[0].propRef.Value.Enum
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.Components.Schemas[enumName] = &openapi3.SchemaRef{
|
||||||
|
Value: &openapi3.Schema{
|
||||||
|
Type: &openapi3.Types{"string"},
|
||||||
|
Enum: enumValues,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ref := "#/components/schemas/" + enumName
|
||||||
|
|
||||||
|
for _, usage := range usages {
|
||||||
|
if usage.inItems {
|
||||||
|
usage.propRef.Value.Items = &openapi3.SchemaRef{Ref: ref}
|
||||||
|
} else {
|
||||||
|
old := usage.propRef.Value
|
||||||
|
if old.Description == "" && !old.Deprecated && old.Format == "" {
|
||||||
|
usage.propRef.Ref = ref
|
||||||
|
usage.propRef.Value = nil
|
||||||
|
} else {
|
||||||
|
usage.propRef.Value = &openapi3.Schema{
|
||||||
|
AllOf: openapi3.SchemaRefs{
|
||||||
|
{Ref: ref},
|
||||||
|
},
|
||||||
|
Description: old.Description,
|
||||||
|
Deprecated: old.Deprecated,
|
||||||
|
Format: old.Format,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// deriveEnumName looks up a shared enum's Go type name from astEnumMap by
|
||||||
|
// value-set key. If no annotation matches, returns an error identifying the
|
||||||
|
// offending properties and the fix.
|
||||||
|
func deriveEnumName(key string, usages []enumUsage, astEnumMap map[string]string) (string, error) {
|
||||||
|
if name, ok := astEnumMap[key]; ok {
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
props := map[string]bool{}
|
||||||
|
for _, u := range usages {
|
||||||
|
props[fmt.Sprintf("%s.%s", u.schemaName, u.propName)] = true
|
||||||
|
}
|
||||||
|
propList := make([]string, 0, len(props))
|
||||||
|
for p := range props {
|
||||||
|
propList = append(propList, p)
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf(
|
||||||
|
"no swagger:enum annotation matches value-set %q used by %d properties: %v; "+
|
||||||
|
"fix by adding a named string type with // swagger:enum to modules/structs or modules/commitstatus",
|
||||||
|
key, len(usages), propList,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package openapi3gen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/getkin/kin-openapi/openapi3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDeriveEnumName_hit(t *testing.T) {
|
||||||
|
key := EnumKey([]any{"red", "green", "blue"})
|
||||||
|
astMap := map[string]string{key: "Color"}
|
||||||
|
usages := []enumUsage{{schemaName: "Paint", propName: "color"}}
|
||||||
|
got, err := deriveEnumName(key, usages, astMap)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if got != "Color" {
|
||||||
|
t.Fatalf("got %q, want %q", got, "Color")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeriveEnumName_miss(t *testing.T) {
|
||||||
|
key := EnumKey([]any{"x", "y"})
|
||||||
|
usages := []enumUsage{{schemaName: "Thing", propName: "kind"}}
|
||||||
|
_, err := deriveEnumName(key, usages, map[string]string{})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected miss error, got nil")
|
||||||
|
}
|
||||||
|
msg := err.Error()
|
||||||
|
if !strings.Contains(msg, "Thing.kind") {
|
||||||
|
t.Fatalf("error %q should list the missing usage", msg)
|
||||||
|
}
|
||||||
|
if !strings.Contains(msg, "swagger:enum") {
|
||||||
|
t.Fatalf("error %q should hint at the fix", msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtractSharedEnums_usesASTMap(t *testing.T) {
|
||||||
|
doc := &openapi3.T{
|
||||||
|
Components: &openapi3.Components{
|
||||||
|
Schemas: openapi3.Schemas{
|
||||||
|
"A": {Value: &openapi3.Schema{
|
||||||
|
Type: &openapi3.Types{"object"},
|
||||||
|
Properties: openapi3.Schemas{
|
||||||
|
"color": {Value: &openapi3.Schema{
|
||||||
|
Type: &openapi3.Types{"string"},
|
||||||
|
Enum: []any{"red", "green", "blue"},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
"B": {Value: &openapi3.Schema{
|
||||||
|
Type: &openapi3.Types{"object"},
|
||||||
|
Properties: openapi3.Schemas{
|
||||||
|
"color": {Value: &openapi3.Schema{
|
||||||
|
Type: &openapi3.Types{"string"},
|
||||||
|
Enum: []any{"red", "green", "blue"},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
astMap := map[string]string{EnumKey([]any{"red", "green", "blue"}): "Color"}
|
||||||
|
if err := extractSharedEnums(doc, astMap); err != nil {
|
||||||
|
t.Fatalf("extractSharedEnums: %v", err)
|
||||||
|
}
|
||||||
|
if _, ok := doc.Components.Schemas["Color"]; !ok {
|
||||||
|
t.Fatalf("expected Color schema to be extracted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFixFileSchemas_recursesIntoNested(t *testing.T) {
|
||||||
|
fileType := func() *openapi3.SchemaRef {
|
||||||
|
return &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"file"}}}
|
||||||
|
}
|
||||||
|
doc := &openapi3.T{
|
||||||
|
Paths: openapi3.NewPaths(),
|
||||||
|
}
|
||||||
|
doc.Paths.Set("/upload", &openapi3.PathItem{
|
||||||
|
Post: &openapi3.Operation{
|
||||||
|
RequestBody: &openapi3.RequestBodyRef{
|
||||||
|
Value: &openapi3.RequestBody{
|
||||||
|
Content: openapi3.Content{
|
||||||
|
"multipart/form-data": {
|
||||||
|
Schema: &openapi3.SchemaRef{Value: &openapi3.Schema{
|
||||||
|
Type: &openapi3.Types{"object"},
|
||||||
|
Properties: openapi3.Schemas{
|
||||||
|
"attachment": fileType(),
|
||||||
|
"items": {Value: &openapi3.Schema{
|
||||||
|
Type: &openapi3.Types{"array"},
|
||||||
|
Items: fileType(),
|
||||||
|
}},
|
||||||
|
"alt": {Value: &openapi3.Schema{
|
||||||
|
AllOf: openapi3.SchemaRefs{fileType()},
|
||||||
|
}},
|
||||||
|
"one": {Value: &openapi3.Schema{
|
||||||
|
OneOf: openapi3.SchemaRefs{fileType()},
|
||||||
|
}},
|
||||||
|
"any": {Value: &openapi3.Schema{
|
||||||
|
AnyOf: openapi3.SchemaRefs{fileType()},
|
||||||
|
}},
|
||||||
|
"not": {Value: &openapi3.Schema{
|
||||||
|
Not: fileType(),
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Responses: openapi3.NewResponses(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
fixFileSchemas(doc)
|
||||||
|
|
||||||
|
props := doc.Paths.Value("/upload").Post.RequestBody.Value.Content["multipart/form-data"].Schema.Value.Properties
|
||||||
|
if !props["attachment"].Value.Type.Is("string") || props["attachment"].Value.Format != "binary" {
|
||||||
|
t.Errorf("nested property not fixed: %+v", props["attachment"].Value)
|
||||||
|
}
|
||||||
|
if !props["items"].Value.Items.Value.Type.Is("string") || props["items"].Value.Items.Value.Format != "binary" {
|
||||||
|
t.Errorf("array items not fixed: %+v", props["items"].Value.Items.Value)
|
||||||
|
}
|
||||||
|
if !props["alt"].Value.AllOf[0].Value.Type.Is("string") || props["alt"].Value.AllOf[0].Value.Format != "binary" {
|
||||||
|
t.Errorf("allOf branch not fixed: %+v", props["alt"].Value.AllOf[0].Value)
|
||||||
|
}
|
||||||
|
if !props["one"].Value.OneOf[0].Value.Type.Is("string") || props["one"].Value.OneOf[0].Value.Format != "binary" {
|
||||||
|
t.Errorf("oneOf branch not fixed: %+v", props["one"].Value.OneOf[0].Value)
|
||||||
|
}
|
||||||
|
if !props["any"].Value.AnyOf[0].Value.Type.Is("string") || props["any"].Value.AnyOf[0].Value.Format != "binary" {
|
||||||
|
t.Errorf("anyOf branch not fixed: %+v", props["any"].Value.AnyOf[0].Value)
|
||||||
|
}
|
||||||
|
if !props["not"].Value.Not.Value.Type.Is("string") || props["not"].Value.Not.Value.Format != "binary" {
|
||||||
|
t.Errorf("not branch not fixed: %+v", props["not"].Value.Not.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtractSharedEnums_missReturnsError(t *testing.T) {
|
||||||
|
doc := &openapi3.T{
|
||||||
|
Components: &openapi3.Components{
|
||||||
|
Schemas: openapi3.Schemas{
|
||||||
|
"A": {Value: &openapi3.Schema{
|
||||||
|
Type: &openapi3.Types{"object"},
|
||||||
|
Properties: openapi3.Schemas{
|
||||||
|
"color": {Value: &openapi3.Schema{
|
||||||
|
Type: &openapi3.Types{"string"},
|
||||||
|
Enum: []any{"red", "green"},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
"B": {Value: &openapi3.Schema{
|
||||||
|
Type: &openapi3.Types{"object"},
|
||||||
|
Properties: openapi3.Schemas{
|
||||||
|
"color": {Value: &openapi3.Schema{
|
||||||
|
Type: &openapi3.Types{"string"},
|
||||||
|
Enum: []any{"red", "green"},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := extractSharedEnums(doc, map[string]string{}); err == nil {
|
||||||
|
t.Fatal("expected miss error")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// Package openapi3gen converts Gitea's Swagger 2.0 spec to an OpenAPI 3.0
|
||||||
|
// spec. It discovers Go enum type names by scanning swagger:enum annotations
|
||||||
|
// in the source tree, then names extracted shared-enum schemas accordingly.
|
||||||
|
package openapi3gen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EnumKey returns a canonical key for a set of enum values: values are
|
||||||
|
// stringified, sorted, and joined with "|". Used to match enum value sets
|
||||||
|
// across spec properties and scanned Go type declarations.
|
||||||
|
func EnumKey(values []any) string {
|
||||||
|
strs := make([]string, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
strs[i] = fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
sort.Strings(strs)
|
||||||
|
return strings.Join(strs, "|")
|
||||||
|
}
|
||||||
|
|
||||||
|
var rxSwaggerEnum = regexp.MustCompile(`swagger:enum\s+(\w+)`)
|
||||||
|
|
||||||
|
// ScanSwaggerEnumTypes walks .go files under each dir and returns a map from
|
||||||
|
// a canonical value-set key (see EnumKey) to the Go type name declared with
|
||||||
|
// // swagger:enum TypeName.
|
||||||
|
//
|
||||||
|
// Returns an error on parse failure, on an annotation for a type whose
|
||||||
|
// constants can't be extracted, or on value-set collisions between two
|
||||||
|
// different enum types.
|
||||||
|
func ScanSwaggerEnumTypes(dirs []string) (map[string]string, error) {
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
parsed := []*ast.File{}
|
||||||
|
|
||||||
|
for _, dir := range dirs {
|
||||||
|
entries, err := os.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("reading %s: %w", dir, err)
|
||||||
|
}
|
||||||
|
for _, entry := range entries {
|
||||||
|
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(entry.Name(), "_test.go") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
path := filepath.Join(dir, entry.Name())
|
||||||
|
file, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: %w", path, err)
|
||||||
|
}
|
||||||
|
parsed = append(parsed, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enumTypes := map[string]string{} // typeName → "" (presence marker)
|
||||||
|
enumValues := map[string][]any{} // typeName → values
|
||||||
|
|
||||||
|
// Pass 1: collect every // swagger:enum TypeName declaration.
|
||||||
|
for _, file := range parsed {
|
||||||
|
for _, decl := range file.Decls {
|
||||||
|
gd, ok := decl.(*ast.GenDecl)
|
||||||
|
if !ok || gd.Tok != token.TYPE {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := collectEnumType(gd, enumTypes); err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: %w", fset.Position(gd.Pos()).Filename, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass 2: collect const values; now every annotated type is visible.
|
||||||
|
for _, file := range parsed {
|
||||||
|
for _, decl := range file.Decls {
|
||||||
|
gd, ok := decl.(*ast.GenDecl)
|
||||||
|
if !ok || gd.Tok != token.CONST {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
collectEnumValues(gd, enumTypes, enumValues)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result := map[string]string{}
|
||||||
|
for typeName := range enumTypes {
|
||||||
|
values, ok := enumValues[typeName]
|
||||||
|
if !ok || len(values) == 0 {
|
||||||
|
return nil, fmt.Errorf("swagger:enum %s has no const block with typed string values", typeName)
|
||||||
|
}
|
||||||
|
key := EnumKey(values)
|
||||||
|
if existing, ok := result[key]; ok && existing != typeName {
|
||||||
|
return nil, fmt.Errorf("swagger:enum value-set collision: %s and %s both use %q", existing, typeName, key)
|
||||||
|
}
|
||||||
|
result[key] = typeName
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// collectEnumType scans a `type` GenDecl for // swagger:enum annotations,
|
||||||
|
// handling both the lone form (`// swagger:enum Foo\n type Foo string`)
|
||||||
|
// where the comment group is attached to the GenDecl, and the grouped form:
|
||||||
|
//
|
||||||
|
// type (
|
||||||
|
// // swagger:enum Foo
|
||||||
|
// Foo string
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// where the comment group is attached to each TypeSpec. Caveat: Go's parser
|
||||||
|
// only attaches a CommentGroup when it is immediately adjacent to the decl.
|
||||||
|
// A blank line (not a `//` continuation line) between the comment and the
|
||||||
|
// declaration drops the Doc, so annotations MUST sit directly above their
|
||||||
|
// type. All current annotated files obey this — the rule is noted here so
|
||||||
|
// a future edit that inserts a blank line fails fast rather than silently.
|
||||||
|
func collectEnumType(gd *ast.GenDecl, enumTypes map[string]string) error {
|
||||||
|
if err := registerEnumAnnotation(gd.Doc, gd.Specs, enumTypes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, spec := range gd.Specs {
|
||||||
|
ts, ok := spec.(*ast.TypeSpec)
|
||||||
|
if !ok || ts.Doc == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := registerEnumAnnotation(ts.Doc, []ast.Spec{ts}, enumTypes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerEnumAnnotation(doc *ast.CommentGroup, specs []ast.Spec, enumTypes map[string]string) error {
|
||||||
|
if doc == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
matches := rxSwaggerEnum.FindStringSubmatch(doc.Text())
|
||||||
|
if len(matches) < 2 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
annotated := matches[1]
|
||||||
|
for _, spec := range specs {
|
||||||
|
ts, ok := spec.(*ast.TypeSpec)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ts.Name.Name == annotated {
|
||||||
|
enumTypes[annotated] = ""
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("swagger:enum %s: no type declaration with that name in the same decl group; check for a typo", annotated)
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectEnumValues(gd *ast.GenDecl, enumTypes map[string]string, enumValues map[string][]any) {
|
||||||
|
for _, spec := range gd.Specs {
|
||||||
|
vs, ok := spec.(*ast.ValueSpec)
|
||||||
|
if !ok || vs.Type == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ident, ok := vs.Type.(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, isEnum := enumTypes[ident.Name]; !isEnum {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, val := range vs.Values {
|
||||||
|
lit, ok := val.(*ast.BasicLit)
|
||||||
|
if !ok || lit.Kind != token.STRING {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
unquoted, err := strconv.Unquote(lit.Value)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
enumValues[ident.Name] = append(enumValues[ident.Name], unquoted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package openapi3gen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEnumKey_sortsAndJoins(t *testing.T) {
|
||||||
|
key := EnumKey([]any{"b", "a", "c"})
|
||||||
|
if key != "a|b|c" {
|
||||||
|
t.Fatalf("EnumKey = %q, want %q", key, "a|b|c")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnumKey_handlesNonStringValues(t *testing.T) {
|
||||||
|
key := EnumKey([]any{2, 1, 3})
|
||||||
|
if key != "1|2|3" {
|
||||||
|
t.Fatalf("EnumKey = %q, want %q", key, "1|2|3")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScanSwaggerEnumTypes_basic(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
src := `package fixture
|
||||||
|
|
||||||
|
// Color is a primary color.
|
||||||
|
// swagger:enum Color
|
||||||
|
type Color string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ColorRed Color = "red"
|
||||||
|
ColorGreen Color = "green"
|
||||||
|
ColorBlue Color = "blue"
|
||||||
|
)
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "color.go"), []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := ScanSwaggerEnumTypes([]string{dir})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
|
||||||
|
}
|
||||||
|
wantKey := EnumKey([]any{"red", "green", "blue"})
|
||||||
|
if got[wantKey] != "Color" {
|
||||||
|
t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Color")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScanSwaggerEnumTypes_orphanAnnotation(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
src := `package fixture
|
||||||
|
|
||||||
|
// swagger:enum Sttype
|
||||||
|
type StateType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
StateOpen StateType = "open"
|
||||||
|
)
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "typo.go"), []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := ScanSwaggerEnumTypes([]string{dir})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for annotation referencing a non-matching type name")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "Sttype") {
|
||||||
|
t.Fatalf("error %q should mention the typo'd name Sttype", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScanSwaggerEnumTypes_collision(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
src := `package fixture
|
||||||
|
|
||||||
|
// swagger:enum Alpha
|
||||||
|
type Alpha string
|
||||||
|
const (
|
||||||
|
AlphaX Alpha = "x"
|
||||||
|
AlphaY Alpha = "y"
|
||||||
|
)
|
||||||
|
|
||||||
|
// swagger:enum Beta
|
||||||
|
type Beta string
|
||||||
|
const (
|
||||||
|
BetaX Beta = "x"
|
||||||
|
BetaY Beta = "y"
|
||||||
|
)
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "dup.go"), []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := ScanSwaggerEnumTypes([]string{dir})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected collision error, got nil")
|
||||||
|
}
|
||||||
|
msg := err.Error()
|
||||||
|
if !strings.Contains(msg, "Alpha") || !strings.Contains(msg, "Beta") {
|
||||||
|
t.Fatalf("error %q should mention both Alpha and Beta", msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScanSwaggerEnumTypes_parseFailure(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "bad.go"), []byte("package fixture\nfunc Foo() {"), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := ScanSwaggerEnumTypes([]string{dir})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected parse error, got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScanSwaggerEnumTypes_annotationWithoutConsts(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
src := `package fixture
|
||||||
|
|
||||||
|
// swagger:enum Lonely
|
||||||
|
type Lonely string
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "lonely.go"), []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := ScanSwaggerEnumTypes([]string{dir})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for annotation without consts")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "Lonely") {
|
||||||
|
t.Fatalf("error %q should mention Lonely", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScanSwaggerEnumTypes_constsAndTypeInDifferentFiles(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
// Name ordering: `a_consts.go` < `b_type.go`, so readdir returns consts first.
|
||||||
|
// Old single-pass scanner would miss the values; two-pass must not.
|
||||||
|
constsSrc := `package fixture
|
||||||
|
|
||||||
|
const (
|
||||||
|
HueA Hue = "a"
|
||||||
|
HueB Hue = "b"
|
||||||
|
)
|
||||||
|
`
|
||||||
|
typeSrc := `package fixture
|
||||||
|
|
||||||
|
// swagger:enum Hue
|
||||||
|
type Hue string
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "a_consts.go"), []byte(constsSrc), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "b_type.go"), []byte(typeSrc), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := ScanSwaggerEnumTypes([]string{dir})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
|
||||||
|
}
|
||||||
|
wantKey := EnumKey([]any{"a", "b"})
|
||||||
|
if got[wantKey] != "Hue" {
|
||||||
|
t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Hue")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScanSwaggerEnumTypes_constsBeforeType(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
src := `package fixture
|
||||||
|
|
||||||
|
const (
|
||||||
|
ShadeDark Shade = "dark"
|
||||||
|
ShadeLight Shade = "light"
|
||||||
|
)
|
||||||
|
|
||||||
|
// swagger:enum Shade
|
||||||
|
type Shade string
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "shade.go"), []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := ScanSwaggerEnumTypes([]string{dir})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
|
||||||
|
}
|
||||||
|
wantKey := EnumKey([]any{"dark", "light"})
|
||||||
|
if got[wantKey] != "Shade" {
|
||||||
|
t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Shade")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScanSwaggerEnumTypes_groupedTypeDecl(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
src := `package fixture
|
||||||
|
|
||||||
|
type (
|
||||||
|
// swagger:enum Color
|
||||||
|
Color string
|
||||||
|
// swagger:enum Shade
|
||||||
|
Shade string
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ColorRed Color = "red"
|
||||||
|
ColorBlue Color = "blue"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ShadeDark Shade = "dark"
|
||||||
|
ShadeLight Shade = "light"
|
||||||
|
)
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "grouped.go"), []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := ScanSwaggerEnumTypes([]string{dir})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
|
||||||
|
}
|
||||||
|
colorKey := EnumKey([]any{"red", "blue"})
|
||||||
|
shadeKey := EnumKey([]any{"dark", "light"})
|
||||||
|
if got[colorKey] != "Color" {
|
||||||
|
t.Fatalf("Color: map[%q] = %q, want %q", colorKey, got[colorKey], "Color")
|
||||||
|
}
|
||||||
|
if got[shadeKey] != "Shade" {
|
||||||
|
t.Fatalf("Shade: map[%q] = %q, want %q", shadeKey, got[shadeKey], "Shade")
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -7,8 +7,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/private"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/private"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
||||||
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|||||||
+6
-6
@@ -8,12 +8,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
repo_module "git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/repository"
|
||||||
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|||||||
+3
-3
@@ -10,9 +10,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||||
auth_service "code.gitea.io/gitea/services/auth"
|
auth_service "git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/auth"
|
||||||
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/auth"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/auth"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/util"
|
||||||
"code.gitea.io/gitea/services/auth/source/ldap"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/auth/source/ldap"
|
||||||
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/auth"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/auth"
|
||||||
"code.gitea.io/gitea/modules/test"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/test"
|
||||||
"code.gitea.io/gitea/services/auth/source/ldap"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/auth/source/ldap"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/auth"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/util"
|
||||||
"code.gitea.io/gitea/services/auth/source/oauth2"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/auth/source/oauth2"
|
||||||
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/auth"
|
||||||
"code.gitea.io/gitea/services/auth/source/oauth2"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/auth/source/oauth2"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/auth"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/util"
|
||||||
"code.gitea.io/gitea/services/auth/source/smtp"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/auth/source/smtp"
|
||||||
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/auth"
|
||||||
"code.gitea.io/gitea/services/auth/source/smtp"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/auth/source/smtp"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/graceful"
|
||||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
asymkey_service "git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/asymkey"
|
||||||
repo_service "code.gitea.io/gitea/services/repository"
|
repo_service "git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/repository"
|
||||||
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/auth/password"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/auth/password"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/optional"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
||||||
user_service "code.gitea.io/gitea/services/user"
|
user_service "git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/user"
|
||||||
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,11 +4,12 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -82,7 +83,9 @@ func TestChangePasswordCommand(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
err := microcmdUserChangePassword().Run(ctx, tc.args)
|
cmd := microcmdUserChangePassword()
|
||||||
|
cmd.Writer, cmd.ErrWriter = io.Discard, io.Discard
|
||||||
|
err := cmd.Run(ctx, tc.args)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Contains(t, err.Error(), tc.expectedErr)
|
require.Contains(t, err.Error(), tc.expectedErr)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
|
||||||
pwd "code.gitea.io/gitea/modules/auth/password"
|
pwd "git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/auth/password"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/optional"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
||||||
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/storage"
|
||||||
user_service "code.gitea.io/gitea/services/user"
|
user_service "git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/user"
|
||||||
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/auth"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
|
||||||
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
|
||||||
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
||||||
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -107,6 +108,7 @@ func TestCertCommandFailures(t *testing.T) {
|
|||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
app := cmdCert()
|
app := cmdCert()
|
||||||
|
app.Writer, app.ErrWriter = io.Discard, io.Discard
|
||||||
tempDir := t.TempDir()
|
tempDir := t.TempDir()
|
||||||
|
|
||||||
certFile := filepath.Join(tempDir, "cert.pem")
|
certFile := filepath.Join(tempDir, "cert.pem")
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/urfave/cli/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDefaultCommand(t *testing.T) {
|
|
||||||
test := func(t *testing.T, args []string, expectedRetName string, expectedRetValid bool) {
|
|
||||||
called := false
|
|
||||||
cmd := &cli.Command{
|
|
||||||
DefaultCommand: "test",
|
|
||||||
Commands: []*cli.Command{
|
|
||||||
{
|
|
||||||
Name: "test",
|
|
||||||
Action: func(ctx context.Context, command *cli.Command) error {
|
|
||||||
retName, retValid := isValidDefaultSubCommand(command)
|
|
||||||
assert.Equal(t, expectedRetName, retName)
|
|
||||||
assert.Equal(t, expectedRetValid, retValid)
|
|
||||||
called = true
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.NoError(t, cmd.Run(t.Context(), args))
|
|
||||||
assert.True(t, called)
|
|
||||||
}
|
|
||||||
test(t, []string{"./gitea"}, "", true)
|
|
||||||
test(t, []string{"./gitea", "test"}, "", true)
|
|
||||||
test(t, []string{"./gitea", "other"}, "other", false)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// Tests here reload the config system multiple times with uncontrollable details.
|
||||||
|
// So they must be in a separate package, to avoid affecting other tests
|
||||||
|
|
||||||
|
package cmdtest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/cmd"
|
||||||
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/unittest"
|
||||||
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
||||||
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/test"
|
||||||
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/util"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/urfave/cli/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
unittest.MainTest(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makePathOutput(workPath, customPath, customConf string) string {
|
||||||
|
return fmt.Sprintf("WorkPath=%s\nCustomPath=%s\nCustomConf=%s", workPath, customPath, customConf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestApp(testCmd cli.Command) *cli.Command {
|
||||||
|
app := cmd.NewMainApp(cmd.AppVersion{})
|
||||||
|
testCmd.Name = util.IfZero(testCmd.Name, "test-cmd")
|
||||||
|
cmd.PrepareSubcommandWithGlobalFlags(&testCmd)
|
||||||
|
app.Commands = append(app.Commands, &testCmd)
|
||||||
|
app.DefaultCommand = testCmd.Name
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
type runResult struct {
|
||||||
|
Stdout string
|
||||||
|
Stderr string
|
||||||
|
ExitCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTestApp(app *cli.Command, args ...string) (runResult, error) {
|
||||||
|
outBuf := new(strings.Builder)
|
||||||
|
errBuf := new(strings.Builder)
|
||||||
|
app.Writer = outBuf
|
||||||
|
app.ErrWriter = errBuf
|
||||||
|
exitCode := -1
|
||||||
|
defer test.MockVariableValue(&cli.ErrWriter, app.ErrWriter)()
|
||||||
|
defer test.MockVariableValue(&cli.OsExiter, func(code int) {
|
||||||
|
if exitCode == -1 {
|
||||||
|
exitCode = code // save the exit code once and then reset the writer (to simulate the exit)
|
||||||
|
app.Writer, app.ErrWriter, cli.ErrWriter = io.Discard, io.Discard, io.Discard
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
err := cmd.RunMainApp(app, args...)
|
||||||
|
return runResult{outBuf.String(), errBuf.String(), exitCode}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCliCmd(t *testing.T) {
|
||||||
|
defaultWorkPath := filepath.FromSlash("/tmp/mocked-work-path")
|
||||||
|
defaultCustomPath := filepath.Join(defaultWorkPath, "custom")
|
||||||
|
defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini")
|
||||||
|
defer setting.MockBuiltinPaths(defaultWorkPath, "", "")()
|
||||||
|
|
||||||
|
cli.CommandHelpTemplate = "(command help template)"
|
||||||
|
cli.RootCommandHelpTemplate = "(app help template)"
|
||||||
|
cli.SubcommandHelpTemplate = "(subcommand help template)"
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
env map[string]string
|
||||||
|
cmd string
|
||||||
|
exp string
|
||||||
|
}{
|
||||||
|
// help commands
|
||||||
|
{
|
||||||
|
cmd: "./gitea -h",
|
||||||
|
exp: "DEFAULT CONFIGURATION:",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cmd: "./gitea help",
|
||||||
|
exp: "DEFAULT CONFIGURATION:",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
cmd: "./gitea -c /dev/null -h",
|
||||||
|
exp: "ConfigFile: /dev/null",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
cmd: "./gitea -c /dev/null help",
|
||||||
|
exp: "ConfigFile: /dev/null",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cmd: "./gitea help -c /dev/null",
|
||||||
|
exp: "ConfigFile: /dev/null",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
cmd: "./gitea -c /dev/null test-cmd -h",
|
||||||
|
exp: "ConfigFile: /dev/null",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cmd: "./gitea test-cmd -c /dev/null -h",
|
||||||
|
exp: "ConfigFile: /dev/null",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cmd: "./gitea test-cmd -h -c /dev/null",
|
||||||
|
exp: "ConfigFile: /dev/null",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
cmd: "./gitea -c /dev/null test-cmd help",
|
||||||
|
exp: "ConfigFile: /dev/null",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cmd: "./gitea test-cmd -c /dev/null help",
|
||||||
|
exp: "ConfigFile: /dev/null",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cmd: "./gitea test-cmd help -c /dev/null",
|
||||||
|
exp: "ConfigFile: /dev/null",
|
||||||
|
},
|
||||||
|
|
||||||
|
// parse paths
|
||||||
|
{
|
||||||
|
cmd: "./gitea test-cmd",
|
||||||
|
exp: makePathOutput(defaultWorkPath, defaultCustomPath, defaultCustomConf),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cmd: "./gitea -c /tmp/app.ini test-cmd",
|
||||||
|
exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cmd: "./gitea test-cmd -c /tmp/app.ini",
|
||||||
|
exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
|
||||||
|
cmd: "./gitea test-cmd",
|
||||||
|
exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/custom/conf/app.ini"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
|
||||||
|
cmd: "./gitea test-cmd --work-path /tmp/other",
|
||||||
|
exp: makePathOutput("/tmp/other", "/tmp/other/custom", "/tmp/other/custom/conf/app.ini"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
|
||||||
|
cmd: "./gitea test-cmd --config /tmp/app-other.ini",
|
||||||
|
exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/app-other.ini"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run(c.cmd, func(t *testing.T) {
|
||||||
|
app := newTestApp(cli.Command{
|
||||||
|
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||||
|
_, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
for k, v := range c.env {
|
||||||
|
t.Setenv(k, v)
|
||||||
|
}
|
||||||
|
args := strings.Split(c.cmd, " ") // for test only, "split" is good enough
|
||||||
|
r, err := runTestApp(app, args...)
|
||||||
|
assert.NoError(t, err, c.cmd)
|
||||||
|
assert.NotEmpty(t, c.exp, c.cmd)
|
||||||
|
if !assert.Contains(t, r.Stdout, c.exp, c.cmd) {
|
||||||
|
t.Log("Full output:\n" + r.Stdout)
|
||||||
|
t.Log("Expected:\n" + c.exp)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCliCmdError(t *testing.T) {
|
||||||
|
app := newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return errors.New("normal error") }})
|
||||||
|
r, err := runTestApp(app, "./gitea", "test-cmd")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, 1, r.ExitCode)
|
||||||
|
assert.Empty(t, r.Stdout)
|
||||||
|
assert.Equal(t, "Command error: normal error\n", r.Stderr)
|
||||||
|
|
||||||
|
app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return cli.Exit("exit error", 2) }})
|
||||||
|
r, err = runTestApp(app, "./gitea", "test-cmd")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, 2, r.ExitCode)
|
||||||
|
assert.Empty(t, r.Stdout)
|
||||||
|
assert.Equal(t, "exit error\n", r.Stderr)
|
||||||
|
|
||||||
|
app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return nil }})
|
||||||
|
r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, 1, r.ExitCode)
|
||||||
|
assert.Empty(t, r.Stdout)
|
||||||
|
assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stderr)
|
||||||
|
|
||||||
|
app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return nil }})
|
||||||
|
r, err = runTestApp(app, "./gitea", "test-cmd")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called
|
||||||
|
assert.Empty(t, r.Stdout)
|
||||||
|
assert.Empty(t, r.Stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCliCmdBefore(t *testing.T) {
|
||||||
|
ctxNew := context.WithValue(context.Background(), any("key"), "value")
|
||||||
|
configValues := map[string]string{}
|
||||||
|
setting.CustomConf = "/tmp/any.ini"
|
||||||
|
var actionCtx context.Context
|
||||||
|
app := newTestApp(cli.Command{
|
||||||
|
Before: func(context.Context, *cli.Command) (context.Context, error) {
|
||||||
|
configValues["before"] = setting.CustomConf
|
||||||
|
return ctxNew, nil
|
||||||
|
},
|
||||||
|
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||||
|
configValues["action"] = setting.CustomConf
|
||||||
|
actionCtx = ctx
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
_, err := runTestApp(app, "./gitea", "--config", "/dev/null", "test-cmd")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, ctxNew, actionCtx)
|
||||||
|
assert.Equal(t, "/tmp/any.ini", configValues["before"], "BeforeFunc must be called before preparing config")
|
||||||
|
assert.Equal(t, "/dev/null", configValues["action"])
|
||||||
|
}
|
||||||
+1
-1
@@ -9,7 +9,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
||||||
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|||||||
+7
-7
@@ -12,13 +12,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||||
"code.gitea.io/gitea/models/migrations"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/migrations"
|
||||||
migrate_base "code.gitea.io/gitea/models/migrations/base"
|
migrate_base "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/migrations/base"
|
||||||
"code.gitea.io/gitea/modules/container"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/container"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
||||||
"code.gitea.io/gitea/services/doctor"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/doctor"
|
||||||
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
||||||
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|||||||
+2
-2
@@ -7,8 +7,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
|
||||||
"code.gitea.io/gitea/services/doctor"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/doctor"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
|
|||||||
+9
-9
@@ -11,13 +11,13 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/dump"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/dump"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/json"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/storage"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/util"
|
||||||
|
|
||||||
"gitea.com/go-chi/session"
|
"gitea.com/go-chi/session"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
@@ -203,8 +203,8 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
targetDBType := cmd.String("database")
|
targetDBType := setting.DatabaseType(cmd.String("database"))
|
||||||
if len(targetDBType) > 0 && targetDBType != setting.Database.Type.String() {
|
if targetDBType != "" && targetDBType != setting.Database.Type {
|
||||||
log.Info("Dumping database %s => %s...", setting.Database.Type, targetDBType)
|
log.Info("Dumping database %s => %s...", setting.Database.Type, targetDBType)
|
||||||
} else {
|
} else {
|
||||||
log.Info("Dumping database...")
|
log.Info("Dumping database...")
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user