When I was browsing through my code archive, I noticed that I was missing some projects and wondered where I had left them after my recent re-install. Just then I noticed that I still had some private repos at Bitbucket. As I just needed a place to back up the code, I decided that I could move it to Codeberg.
Codeberg describes itself as “[…] a non-profit, community-led effort that provides Git hosting and other services for free and open source projects.”. Codeberg e.V. itself is a registered non-profit association based in Berlin, Germany.
It was just about 12 projects, but still manually downloading a repo from Bitbucket, creating a project at codeberg and uploading it there … 12x … I wanted a script for this. I used ChatGPT to create the below script, which was almost fine except for the description of the app passwords. It might not be perfect, but it did the job.
For the below script to work you just need to create an App password on Bitbucket with the Repo-permissions set and an Api token on codeberg. Just add the info to the script below and you should be ready to go. Afterwards I deleted my Bitbucket account.
#!/usr/bin/env bash
set -euo pipefail
# === Configuration ===
BITBUCKET_USER="..."
BITBUCKET_APP_PASSWORD="..."
CODEBERG_USER="..."
CODEBERG_TOKEN="..."
# === Fetch all Bitbucket repos ===
echo "Fetching all Bitbucket repos for user: $BITBUCKET_USER ..."
# If you have more than 100 repos: implement pagination!
repos=$(curl -s -u "$BITBUCKET_USER:$BITBUCKET_APP_PASSWORD" \
"https://api.bitbucket.org/2.0/repositories/$BITBUCKET_USER?pagelen=100" \
| jq -r '.values[].name')
echo "Found repos:"
echo "$repos"
# === For each repo: create & mirror ===
for repo in $repos; do
echo "--------------------------------------------"
echo "Creating repo '$repo' on Codeberg (if not exists)..."
# Create repo
response=$(curl -s -o /dev/null -w "%{http_code}" -X POST "https://codeberg.org/api/v1/user/repos" \
-H "Content-Type: application/json" \
-H "Authorization: token $CODEBERG_TOKEN" \
-d "{\"name\":\"$repo\",\"private\":true}")
if [[ "$response" == "201" ]]; then
echo "Created new repo: $repo"
elif [[ "$response" == "409" ]]; then
echo "Repo already exists: $repo"
else
echo "Failed to create repo: HTTP $response"
continue
fi
# Mirror repo
echo "Cloning --mirror from Bitbucket..."
git clone --mirror "https://$BITBUCKET_USER:$BITBUCKET_APP_PASSWORD@bitbucket.org/$BITBUCKET_USER/$repo.git" || { echo "Clone failed, skipping"; continue; }
echo "Pushing --mirror to Codeberg..."
cd "$repo.git"
git remote set-url origin "https://$CODEBERG_USER:$CODEBERG_TOKEN@codeberg.org/$CODEBERG_USER/$repo.git"
git push --mirror
cd ..
echo "Finished repo: $repo"
done
echo "All repos migrated!"