metagit

metagit

Self-hosted git remote that mirrors your repos to several hostings and archives overwritten refs.

To a developer it looks like an ordinary git remote. metagit keeps a full copy of the repository and sends branches and tags to several git hostings: GitHub, Bitbucket, Codeberg and any other with git access.

View on GitHub Quick start

git push metagit main

  • github delivered
  • codeberg queued
  • bitbucket error, retry later

Why

If a project lives on one hosting, you can lose it: the account gets blocked, the hosting shuts down, a mistaken force-push wipes out the history. metagit keeps copies in several places and does not lose previous states of branches.

How it works

metagit is the source of truth

Sync is one-way: changes arrive only through a push to metagit, the hostings get a copy.

Delivery is asynchronous

A push succeeds as soon as metagit has saved the changes. Then metagit waits a few seconds for the delivery results and prints them in the push output:

remote: metagit: github: delivered
remote: metagit: codeberg: queued
remote: metagit: bitbucket: error: fatal: Authentication failed ... (retry later)

A failed delivery is retried automatically with a growing interval, from 15 seconds to 30 minutes.

Only branches and tags are sent

Branches and tags on a hosting that metagit did not send there, for example branches for pull requests, are left alone.

Nothing is lost

Before a force-push, a branch deletion or a tag move, the previous value is saved in refs/metagit/archive/push/.... For protected branches and tags such pushes are rejected.

Changes made on a hosting

If a branch on a hosting was changed bypassing metagit (for example, a merge in the hosting's web interface), metagit saves the hosting's state in refs/metagit/archive/targets/<id>/..., overwrites the branch with its own value using --force-with-lease and records an event in the project log.

Notifications

The status of every target and the event log are in the web console. A command of your choice runs on delivery errors and recovery, deliveries, divergences and destructive pushes; there are ready scripts for a webhook, Telegram and email.

Web console

Project page of the web console: clone addresses at the top, a line runs from them to four targets. codeberg is disabled, github has an error with its text and the next retry time, gitlab is pending, hosting-b is synced.
A project and its targets in every state. Full size: light theme, dark theme.
Projects list of the web console: every project with its clone addresses, a lamp for every target and the number of targets in each state.
The projects list with a lamp for every target. Full size: light theme, dark theme.

Quick start

You need Linux or macOS with git 2.32 or newer and an OpenSSH client (ssh). To build: Go 1.26 or newer, Node.js 24 or newer.

Ready binaries for Linux and macOS (amd64 and arm64) are on the releases page.

  1. Build

    You get one binary, bin/metagit, with the web console built in.

    make build
  2. Create the owner

    metagit has exactly one owner, and it is created only by a command on the server. The password is asked in the terminal.

    ./bin/metagit owner create --name dev --data-dir ./data
  3. Start the service

    The web console, the API and git over HTTP listen on :8080, git over SSH on :2222.

    ./bin/metagit serve --data-dir ./data
  4. Create a token and a project

    Open the web console and sign in. In Settings, create a git access token (it is shown once) and, if you need SSH, add your public key. Then create a project.

  5. Push to metagit

    The project page shows its clone addresses.

    # HTTP(S): any login, the password is a metagit token
    git remote add metagit https://git.example.com/foo.git
    # SSH
    git remote add metagit ssh://git@git.example.com:2222/foo.git
    
    git push metagit main
  6. Add targets

    A target is a repository on a hosting: a URL and credentials. Create the repository on the hosting beforehand.

    Use https://... with a login and a token (for example, a GitHub fine-grained personal access token with write access to contents), or ssh://... or git@host:path with a private SSH key without a passphrase (for example, a deploy key with write access).

Installing on a server (a system user, the master key, systemd, TLS) and all metagit serve settings are described in INSTALL.md.

Security

Not in this version