Fiw-Gentoo-Dots/tools/ci/mirror.py
fi3w0 ca8c5de013
Some checks are pending
Check and mirror to GitHub / checks (push) Waiting to run
Check and mirror to GitHub / github-mirror (push) Blocked by required conditions
Add Forgejo CI and GitHub mirroring after checks
2026-10-03 23:28:26 +02:00

64 lines
3.3 KiB
Python

#!/usr/bin/env python3
"""Publish Forgejo branches/tags to GitHub without deleting or forcing refs."""
import argparse
import os
import shlex
import subprocess
import tempfile
from pathlib import Path
from urllib.parse import urlsplit
REPO = Path(__file__).resolve().parents[2]
SOURCE = 'https://git.fiwlabs.dev/fiwdev/Fiw-Gentoo-Dots.git'
GITHUB_HOST = 'github.com'
GITHUB_REPOSITORY = 'Fi3w0/Fiw-Gentoo-Dots'
TARGET = f'ssh://git@{GITHUB_HOST}/{GITHUB_REPOSITORY}.git'
def mirror(source=SOURCE, target=TARGET, key=None, source_ref=None, source_sha=None):
environment = dict(os.environ, GIT_TERMINAL_PROMPT='0')
environment.pop('GH_MIRROR_SSH_KEY', None)
with tempfile.TemporaryDirectory(prefix='fiw-github-mirror-') as temporary:
root = Path(temporary)
remote = urlsplit(target)
if remote.scheme == 'ssh' and remote.hostname == GITHUB_HOST:
if not key or not key.strip():
raise RuntimeError('Add the GH_MIRROR_SSH_KEY repository secret in Forgejo before mirroring.')
identity = root / 'identity'
identity.write_text(key.strip() + '\n')
identity.chmod(0o600)
environment['GIT_SSH_COMMAND'] = shlex.join([
'ssh', '-F', '/dev/null', '-i', str(identity),
'-o', 'BatchMode=yes', '-o', 'IdentitiesOnly=yes',
'-o', 'StrictHostKeyChecking=yes', '-o', 'UserKnownHostsFile=' + str(REPO / 'tools/ci/github_known_hosts')])
snapshot = root / 'source.git'
subprocess.run(['git', 'clone', '--bare', '--no-local', source, str(snapshot)], env=environment, check=True)
if source_ref:
if not source_ref.startswith(('refs/heads/', 'refs/tags/')):
raise RuntimeError('Mirror source must be a branch or tag ref.')
subprocess.run(['git', 'check-ref-format', source_ref], env=environment, check=True)
actual = subprocess.run(['git', '-C', str(snapshot), 'rev-parse', '--verify', source_ref + '^{commit}'], env=environment, text=True, capture_output=True)
reviewed = subprocess.run(['git', '-C', str(snapshot), 'rev-parse', '--verify', (source_sha or '') + '^{commit}'], env=environment, text=True, capture_output=True)
if actual.returncode or reviewed.returncode or actual.stdout != reviewed.stdout:
print('Source ref changed or was removed since this CI run; a newer run can publish it.')
return
subprocess.run(['git', '-C', str(snapshot), 'push', '--atomic', target,
'refs/heads/*:refs/heads/*', 'refs/tags/*:refs/tags/*'], env=environment, check=True)
print('Forgejo branches and tags published to GitHub.')
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--source', default=SOURCE)
parser.add_argument('--target', default=TARGET)
parser.add_argument('--key-file', type=Path, help='Local deploy key for an explicit manual sync')
args = parser.parse_args()
key = args.key_file.read_text() if args.key_file else os.environ.get('GH_MIRROR_SSH_KEY')
mirror(args.source, args.target, key, os.environ.get('MIRROR_SOURCE_REF'), os.environ.get('MIRROR_SOURCE_SHA'))
if __name__ == '__main__':
try:
main()
except (OSError, RuntimeError, subprocess.CalledProcessError) as error:
raise SystemExit(str(error))