.,-:::::/     ...      :::.:::::::::::::::::::::.:::::::..       :::.    :::..-:::::':::.   :::::::..  .        :   
,;;-'````'   .;;;;;;;.   ;;`;;;;;;;;;''''`;;;```.;;;;;;``;;;;   ,;;`;;;;,  `;;;;;;'''' ;;`;;  ;;;;``;;;; ;;,.    ;;;  
[[[   [[[[[[,[[     \[[,,[[ '[[,  [[      `]]nnn]]' [[[,/[[[' ,['  [n[[[[[. '[[[[[,,==,[[ '[[, [[[,/[[[' [[[[, ,[[[[, 
"$$c.    "$$$$$,     $$c$$$cc$$$c $$       $$$""    $$$$$$c   $$    $$$$ "Y$c$$`$$$"`c$$$cc$$$c$$$$$$c   $$$$$$$$"$$$ 
 `Y8bo,,,o88"888,_ _,88P888   888,88,      888o     888b "88boY8,  ,8888    Y8d8888   888   888888b "88bo888 Y88" 888o
   `'YMUP"YMM "YMMMMMP" YMM   ""` MMM      YMMMb    MMMM   "W" "YmmP MMM     YYM"MM,  YMM   ""`MMMM   "W"MMM  M'  "MMM

Migrating from Gitlab to self hosted Gitblit

With GitLab being a powerful project and source code management tool it can be difficult to manage on your own, when self hosting, or has many features which are not needed by every user.

At the moment, I tend to host many services on my own and having hosted GitLab was fairly easy to do, but the system resources required by GitLab grew with every release and 90% of the features provided were not utilized.

After looking into some other solutions (e.g: Gitolite, webgit, gitosis, gitea, …), I found Gitblit which all features I need, with out beeing as resource hungry as GitLab.

For my Gitblit GO instance, I set up a small Alpine Linux VM, adapted the Running Gitblit behind Apache to work with Nginx and started on migrating all repositories to my Gitblit instance.

Gitblit provides multiple ways to administer repositories:

  • Web frontend
    • used by Gitblit Manager (GUI)
  • SSH
    • provided by powertools plugin

The easiest way to automate repository creation turned out to be with the Gitblit Powertools plugin.

The script is intended to be executed within a Linux environment, it might be possible to adapt it to support Windows and to have a SSH identity configured for Gitblit SSH.

Use the requirements.txt to install modules needed by Python (pip install [--user] -r requirements.txt).

File content: requirements.txt


Run the code python gl2gb.py after adjusting the settings to your needs.

File content: gl2gb.py

#!/usr/bin/env python

import time
import os
from subprocess import run, DEVNULL
import tarfile
import gitlab
import paramiko
from rich.progress import Progress

progress = Progress(transient=True)

ssh = paramiko.SSHClient()

# TODO: Change the next five lines to match your needs.
GITLAB_HOST = 'https://gitlab.com'
GITLAB_TOKEN = 'PersonalAccessToken_api'
GITBLIT_HOST = 'gitblit'  # Change: Gitblit host address
GITBLIT_PORT = 29419  # Change: Gitblit ssh port

GITBLIT_DIR = '/'  # or 'project_name/' or f'~{GITBLIT_USER}' for private

gl = gitlab.Gitlab(GITLAB_HOST, private_token=GITLAB_TOKEN)
ssh.connect(GITBLIT_HOST, port=GITBLIT_PORT)

progress.log('Fetching project list...')
for project in progress.track(gl.projects.list(all=True)):
    if os.path.exists(f'repos/{project.path}.done'):
        progress.log(f'{project.name}: Already exported and cloned')

    progress.log(f'{project.name}: Creating export...')
    export = project.exports.create()
    while export.export_status != 'finished':

    progress.log(f'{project.name}: Downloading export...')
    with open(f'exports/{project.path}.tgz', 'wb') as f:
        export.download(streamed=True, action=f.write)

    # progress.log(f'Preparing {project.name} for re-import')
    with tarfile.open(f'exports/{project.path}.tgz', 'r:gz') as t:
        progress.log(f'{project.name}: Extracting ./project.bundle...')
        except KeyError:

        progress.log(f'{project.name}: Cloning into bare repository...')
        ret = run(['/usr/bin/git', 'clone', '--mirror', './project.bundle',
                  stderr=DEVNULL, stdout=DEVNULL)

        progress.log(f'{project.name}: Creating new repository...')
        ssh.exec_command(f'gb repos new {GITBLIT_DIR}/{project.path}')

        progress.log(f'{project.name}: Setting remote...')
        ret = run(['/usr/bin/git', '--git-dir', f'repos/{project.path}',
                   'remote', 'set-url', 'origin',
                  stderr=DEVNULL, stdout=DEVNULL)

        progress.log(f'{project.name}: Pushing local repository to remote...')
        ret = run(['/usr/bin/git', '--git-dir', f'repos/{project.path}',
                  stderr=DEVNULL, stdout=DEVNULL)

        os.rename(f'repos/{project.path}', f'repos/{project.path}.done')


Depending on the size and amount of your repositories, the migration can take a few moments. The repositories are downloaded and stored in the subfolder ./exports/, the processed repository is stored in ./repos/. After importing a repository, the directory within ./repos/ is renamed by appending the suffix .done. Should the program run into an exception during processing, it will skip all repositories already completed and pick up at the last unsuccessful one. If you encounter a problem, try to fix it, make the code more robust or configure it correctly.

Update #1: A more recent version of the program can be downloaded/cloned here.