Skip to content

Instantly share code, notes, and snippets.

@lucagiovagnoli
Last active July 7, 2025 05:42
Show Gist options
  • Select an option

  • Save lucagiovagnoli/b1366144f6d06aaa67d21c7126b86b70 to your computer and use it in GitHub Desktop.

Select an option

Save lucagiovagnoli/b1366144f6d06aaa67d21c7126b86b70 to your computer and use it in GitHub Desktop.
Migrate all repos from bitbucket to github 2025
import os
import sys
import unicodedata
from github.Repository import Repository
from pybitbucket37.bitbucket import Client as BbClient, Bitbucket
from github import Github
APP_KEYS = {
# 'bitbucket': 'Get it from https://bitbucket.org/account/user/YOUR_USERNAME/app-passwords',
# 'github': 'Get it from https://github.com/settings/tokens',
}
GH_USER = ''
BB_USER = ''
BB_EMAIL = ''
WORKING_DIR = '~/bitbucket_migration'
def bitbucket():
from pybitbucket37.auth import BasicAuthenticator
return BbClient(
BasicAuthenticator(
BB_USER,
APP_KEYS['bitbucket'],
BB_EMAIL
)
)
def github():
return Github(APP_KEYS['github'])
def bb_get_repositories(client: BbClient):
# noinspection PyUnresolvedReferences
return Bitbucket(client).repositoriesByOwnerAndRole(owner=client.get_username(), role='owner')
def bb_clone_repo(slug: str):
import subprocess
dest = os.path.join(WORKING_DIR, '%s.git' % slug)
if os.path.isdir(dest):
# Assume we have already cloned the repo.
return True
args = [
'git',
'clone',
'--bare',
'git@bitbucket.org:%s/%s.git' % (BB_USER, slug)
]
process = subprocess.Popen(args, cwd=WORKING_DIR, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
stdout, stderr = process.communicate()
if stderr is not None:
print(stderr.decode())
return False
print('Clone: %s' % stdout.decode())
return True
def gh_get_repo(repo_slug: str):
from github import UnknownObjectException
try:
return github().get_repo('%s/%s' % (GH_USER, repo_slug))
except UnknownObjectException:
return None
def gh_repo_create(name: str, desc: str, is_private: bool):
return github().get_user().create_repo(
name,
description=desc,
private=is_private,
has_issues=False,
has_wiki=False,
has_downloads=False,
has_projects=False,
auto_init=False,
)
def gh_repo_push(slug: str):
import subprocess
repo_dir = os.path.join(WORKING_DIR, '%s.git' % slug)
args = [
'git',
'push',
'--mirror',
'git@github.com:%s/%s.git' % (GH_USER, slug)
]
process = subprocess.Popen(args, cwd=repo_dir, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
stdout, stderr = process.communicate()
if stderr is not None:
print(stderr.decode())
return False
print('Push: %s' % stdout.decode())
return True
def process_repo(slug: str, desc: str, private: bool):
gh_repo = gh_get_repo(slug)
if gh_repo is None:
print("Must create: %s" % slug)
gh_repo_create(slug, desc, private)
bb_clone_repo(slug)
gh_repo_push(slug)
return True
def remove_control_characters(s):
return ''.join(c for c in s if not unicodedata.category(c).startswith('C'))
if __name__ == '__main__':
# Create CWD if needed
WORKING_DIR = os.path.expanduser(WORKING_DIR)
for repo in list(bb_get_repositories(bitbucket())):
print('Processing: %s (Priv: %s)' % (repo['slug'], repo['is_private']))
process_repo(
repo['slug'],
remove_control_characters(repo['description']),
private=True
)
PyGithub
pybitbucket37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment