Skip to content

Instantly share code, notes, and snippets.

@dzogrim
Created March 21, 2025 13:36
Show Gist options
  • Save dzogrim/dc4e203c2ae8b4ade35c7d82a9d63c6b to your computer and use it in GitHub Desktop.
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.
#!/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