Double mirror Gitea pour mes 28 repos GitHub (srv-a + nas), via repos/migrate avec mirror: true (auto-refresh 24h). Code OK. Sauf que issues, PR, labels, releases ne sont jamais arrivés — alors que je passais bien issues: true, ... dans le payload. Aucune erreur, juste 0 issues.
Le test qui révèle
Même payload, mais mirror: false :
curl -X POST -H "Authorization: token $TOKEN" http://gitea/api/v1/repos/migrate \
-d '{"clone_addr":"https://...@github.com/monuser/projet-b.git",
"repo_owner":"github-mirror", "repo_name":"projet-b", "mirror":false,
"issues":true, "pull_requests":true, "labels":true,
"releases":true, "wiki":true, "service":"github"}'
SELECT COUNT(*) FROM issue WHERE repo_id=(SELECT id FROM repository WHERE name='projet-b'); -- 185
185 issues importées. Avec mirror: true, tout identique : 0. Silencieusement incompatibles.
Pourquoi
mirror: true crée un repo RepositoryTypeMirror : Gitea fait juste git fetch --mirror périodique. Les issues/PR sont des objets Gitea (tables issue, comment…) sans équivalent git natif ; les peupler demande l’API source, faite seulement au migrate one-shot. Gitea (1.22) ne traite issues: true que dans la branche non-mirror ; en mode mirror, acceptées mais ignorées, sans warning.
La conséquence : abandonner mirror: true
Pour code + meta complète, il faut mirror: false. Mais on perd l’auto-refresh 24h. Solution : cron quotidien destroy/recreate, skip-if-unchanged :
new_ts=$(curl https://api.github.com/repos/X/Y | jq -r .updated_at)
old_ts=$(cat /var/lib/gitea-meta-sync/Y.ts || echo "")
if [ "$new_ts" = "$old_ts" ] && repo_exists_in_gitea; then skip
else delete_repo_in_gitea; migrate_mirror_false_all_meta; echo "$new_ts" > .../Y.ts; fi
Coût : re-clone complet à chaque changement. 28 repos × quelques MB = ~50 MB/jour. Idempotent, incrémental en pratique.
Le piège qui a brouillé le diag
Le PAT GitHub Contents: Read-only clone le code mais pas les issues : il faut Issues (et Pull requests) : Read-only. Sinon 403 "Resource not accessible" — que Gitea ne propage pas : import raté en silence.
Lessons learned
- Jamais
mirror: true+issues: true: silencieusement incompatible,issuesignoré. - Code + meta =
mirror: false+ cron destroy/recreate avec state fileupdated_at. - Vérifier les scopes PAT : GitHub
Contents/Issues/Pull requests: Read; Gitea sourceread:issue. - Tester un curl API minimal avant de scripter :
mirror: falsevstrue= diff vue en 2 min. - Faire confiance aux données, pas aux logs : « Created 201 » partout, SQLite à 0. Script « réussi » sans résultat → regarde l’état réel.