Created
March 21, 2025 13:36
-
-
Save dzogrim/dc4e203c2ae8b4ade35c7d82a9d63c6b to your computer and use it in GitHub Desktop.
This script exports all issues, milestones, labels, and comments from a Gitea repository, then re-imports them into another Gitea repository via the API, preserving essential metadata.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
set -euo pipefail | |
# Ce script exporte toutes les issues, milestones, labels et commentaires d’un dépôt Gitea, | |
# puis les réimporte dans un autre dépôt Gitea via l’API, en conservant les métadonnées essentielles. | |
# | |
# ⚠️ Ce script n’est pas idempotent : il n’écrase pas les données existantes | |
# mais crée de nouveaux objets (issues, milestones, labels, commentaires). | |
# 👉 Il est donc à utiliser *une seule* fois par cible, sous peine de doublonnage massif. | |
# Pour un re-run, supprimez le projet ou videz les entités concernées avant de relancer ! | |
# === Source === | |
SRC_API="https://git.source.tld/api/v1" | |
SRC_TOKEN="" | |
SRC_OWNER="org" | |
SRC_REPO="rep" | |
# === Destination === | |
DST_API="https://git.dest.tld/api/v1" | |
DST_TOKEN="" | |
DST_OWNER="org" | |
DST_REPO="rep" | |
# === Settings === | |
EXPORT_DIR="./export-gitea" | |
# === Export phase === | |
if [[ -d "$EXPORT_DIR" ]]; then | |
echo "📦 Dossier d'export déjà présent, étape d'export ignorée." | |
else | |
echo "📥 Lancement de l’export depuis $SRC_API..." | |
mkdir -p "$EXPORT_DIR/comments" | |
echo "📥 Export des labels..." | |
curl --insecure -s -H "Authorization: token $SRC_TOKEN" \ | |
"$SRC_API/repos/$SRC_OWNER/$SRC_REPO/labels" \ | |
> "$EXPORT_DIR/labels.json" | |
echo "📥 Export des milestones..." | |
curl --insecure -s -H "Authorization: token $SRC_TOKEN" \ | |
"$SRC_API/repos/$SRC_OWNER/$SRC_REPO/milestones" \ | |
> "$EXPORT_DIR/milestones.json" | |
echo "📥 Export des issues..." | |
curl --insecure -s -H "Authorization: token $SRC_TOKEN" \ | |
"$SRC_API/repos/$SRC_OWNER/$SRC_REPO/issues?state=all&limit=1000" \ | |
> "$EXPORT_DIR/issues.json" | |
echo "📥 Export des commentaires..." | |
jq -r '.[].number' "$EXPORT_DIR/issues.json" | while read -r issue_id; do | |
curl --insecure -s -H "Authorization: token $SRC_TOKEN" \ | |
"$SRC_API/repos/$SRC_OWNER/$SRC_REPO/issues/$issue_id/comments" \ | |
> "$EXPORT_DIR/comments/$issue_id.json" | |
done | |
echo "✅ Export terminé." | |
fi | |
# === Import phase === | |
echo "📤 Import des labels..." | |
jq -c '.[]' "$EXPORT_DIR/labels.json" | while read -r label; do | |
name=$(echo "$label" | jq -r '.name') | |
color=$(echo "$label" | jq -r '.color') | |
desc=$(echo "$label" | jq -r '.description') | |
curl --insecure -s -X POST -H "Authorization: token $DST_TOKEN" \ | |
-H "Content-Type: application/json" \ | |
-d "{\"name\":\"$name\", \"color\":\"$color\", \"description\":\"$desc\"}" \ | |
"$DST_API/repos/$DST_OWNER/$DST_REPO/labels" >/dev/null | |
done | |
echo "📤 Import des milestones..." | |
jq -c '.[]' "$EXPORT_DIR/milestones.json" | while read -r milestone; do | |
title=$(echo "$milestone" | jq -r '.title') | |
desc=$(echo "$milestone" | jq -r '.description') | |
due=$(echo "$milestone" | jq -r '.due_on // empty') | |
if [[ -z "$due" || "$due" == "null" ]]; then | |
payload=$(jq -n --arg title "$title" --arg desc "$desc" \ | |
'{title: $title, description: $desc}') | |
else | |
payload=$(jq -n --arg title "$title" --arg desc "$desc" --arg due "$due" \ | |
'{title: $title, description: $desc, due_on: $due}') | |
fi | |
curl --insecure -s -X POST -H "Authorization: token $DST_TOKEN" \ | |
-H "Content-Type: application/json" \ | |
-d "$payload" \ | |
"$DST_API/repos/$DST_OWNER/$DST_REPO/milestones" >/dev/null | |
done | |
echo "📤 Import des issues avec labels et milestones..." | |
jq -c '.[]' "$EXPORT_DIR/issues.json" | while read -r issue; do | |
title=$(echo "$issue" | jq -r '.title') | |
body=$(echo "$issue" | jq -r '.body // ""') | |
state=$(echo "$issue" | jq -r '.state') | |
original_number=$(echo "$issue" | jq -r '.number') | |
# 🔖 Préparer les labels | |
labels=$(echo "$issue" | jq '[.labels[].name]') | |
# ⏳ Chercher milestone ID correspondant dans la cible | |
milestone_title=$(echo "$issue" | jq -r '.milestone.title // empty') | |
milestone_id=null | |
if [[ -n "$milestone_title" ]]; then | |
ids=($(curl --insecure -s -H "Authorization: token $DST_TOKEN" \ | |
"$DST_API/repos/$DST_OWNER/$DST_REPO/milestones" \ | |
| jq -r --arg t "$milestone_title" '.[] | select(.title==$t) | .id')) | |
if [[ "${#ids[@]}" -gt 1 ]]; then | |
echo "⚠️ Plusieurs milestones nommées '$milestone_title' → ID choisis : ${ids[0]}" | |
fi | |
milestone_id="${ids[0]:-null}" | |
fi | |
# 🪄 Création de l'issue | |
new_issue_json=$(jq -n \ | |
--arg title "$title" \ | |
--arg body "$body" \ | |
--argjson labels "$labels" \ | |
--argjson milestone "$milestone_id" \ | |
'{title: $title, body: $body, labels: $labels, milestone: $milestone}') | |
created=$(curl --insecure -s -X POST -H "Authorization: token $DST_TOKEN" \ | |
-H "Content-Type: application/json" \ | |
-d "$new_issue_json" \ | |
"$DST_API/repos/$DST_OWNER/$DST_REPO/issues") | |
new_issue_id=$(echo "$created" | jq -r '.number') | |
echo "✔️ Issue #$new_issue_id importée : $title" | |
# 🗨️ Import des commentaires | |
comment_file="$EXPORT_DIR/comments/$original_number.json" | |
if [[ -s "$comment_file" ]]; then | |
jq -c '.[]' "$comment_file" | while read -r comment; do | |
content=$(echo "$comment" | jq -r '.body // empty') | |
curl --insecure -s -X POST -H "Authorization: token $DST_TOKEN" \ | |
-H "Content-Type: application/json" \ | |
-d "{\"body\":$(jq -Rs <<< "$content")}" \ | |
"$DST_API/repos/$DST_OWNER/$DST_REPO/issues/$new_issue_id/comments" >/dev/null | |
done | |
fi | |
# 🔒 Fermer l'issue si elle était close | |
if [[ "$state" == "closed" ]]; then | |
curl --insecure -s -X PATCH -H "Authorization: token $DST_TOKEN" \ | |
-H "Content-Type: application/json" \ | |
-d '{"state": "closed"}' \ | |
"$DST_API/repos/$DST_OWNER/$DST_REPO/issues/$new_issue_id" >/dev/null | |
fi | |
done | |
echo "✅ Migration complète terminée !" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment