Sincronizar repositorio de GitHub con GitLab

Objetivo: cada vez que se haga un push a la rama main en GitHub, se debe replicar automáticamente el contenido (commits, ramas, tags) en un repositorio espejo de GitLab, usando GitHub Actions.

1. Generar el token de acceso en GitLab

El token es lo que le da permiso a GitHub Actions para hacer push hacia GitLab.

Opción A — Token de proyecto (recomendado)

  1. Entrar al repositorio destino en GitLab (ej. gitlab.com/usuario/repo).
  2. Ir a Settings → Access Tokens.
  3. Crear un token nuevo:
    • Name: github-sync (o cualquier nombre descriptivo)
    • Expiration date: definir una fecha (renovar cuando expire)
    • Role: Maintainer
    • Scopes: solo write_repository
  4. Copiar el token generado (solo se muestra una vez).

Opción B — Token personal

  • Perfil → Edit profile → Access Tokens
  • Mismo procedimiento, pero válido para todos los repos a los que se tenga acceso.

⚠️ No usar el scope api. Da acceso total a la API de GitLab (crear/borrar proyectos, etc.), mucho más permiso del necesario. Con write_repository alcanza.

⚠️ Rol necesario: si la rama main en GitLab está protegida (lo normal por defecto), el token debe tener rol Maintainer. Con Developer no alcanza para pushear a ramas protegidas.

2. Guardar el token como secret en GitHub

  1. Ir al repositorio en GitHub.
  2. Settings → Secrets and variables → Actions.
  3. Pestaña SecretsNew repository secret.
  4. Completar:
    • Name: GITLAB_TOKEN
    • Secret: pegar el token copiado de GitLab
  5. Add secret.

3. Crear el workflow de GitHub Actions

Crear el archivo .github/workflows/sync-gitlab.yml en el repositorio de GitHub, con el siguiente contenido:

name: Sincronizar con GitLab
on:
  push:
    branches:
      - main
jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - name: Clonar repositorio
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Enviar cambios a GitLab
        run: |
          # 1. Eliminamos el remoto si existía de un intento previo para evitar bloqueos
          git remote remove gitlab || true
 
          # 2. Añadimos el remoto usando el secreto GITLAB_TOKEN
          git remote add gitlab https://oauth2:${{ secrets.GITLAB_TOKEN }}@gitlab.com/USUARIO/REPO.git
 
          # 3. Enviamos los cambios de forma limpia
          git push --prune gitlab +refs/remotes/origin/*:refs/heads/* +refs/tags/*:refs/tags/*

Reemplazar USUARIO/REPO.git por la ruta real del repositorio destino en GitLab (ej. daeshu/roms43.git).

Notas sobre el script:

  • git remote remove gitlab || true → evita que falle si el remoto no existe aún (normal en la primera ejecución). El mensaje error: No such remote: 'gitlab' que puede aparecer en los logs no es un error real, es solo la salida de este comando.
  • fetch-depth: 0 → trae el historial completo, necesario para poder empujar todas las ramas y tags.
  • El + antes de los refs indica force push, por eso GitLab debe permitirlo explícitamente (ver paso 4).

4. Requisitos del lado de GitLab

4.1 El repositorio debe existir

El push no crea el proyecto automáticamente en GitLab. Hay que crearlo manualmente antes (vacío o no) con el mismo nombre usado en la URL del workflow.

4.2 Permitir force push en la rama protegida

Por defecto, GitLab protege la rama main y rechaza force push, lo que genera este error:

remote: GitLab: You are not allowed to force push code to a protected branch on this project.
! [remote rejected] origin/main -> main (pre-receive hook declined)

Para solucionarlo:

  1. En GitLab: Settings → Repository → Protected branches.
  2. Buscar main.
  3. En Allowed to force push, habilitarlo (para el rol Maintainer como mínimo).
  4. Guardar.

4.3 Confirmar el rol del token

Verificar que el token usado en GITLAB_TOKEN tenga rol Maintainer (no Developer), ya que solo Maintainer puede modificar ramas protegidas incluso con el force push habilitado.

4.4 Si sigue fallando: desactivar la protección de la rama

En algunos casos, aunque se habilite “Allowed to force push”, GitLab igual rechaza el push mientras la rama siga marcada como protegida. Si el error persiste después de los pasos anteriores, la solución directa es desactivar por completo la protección de la rama:

  1. En GitLab: Settings → Repository → Protected branches.
  2. Buscar main en la lista de ramas protegidas.
  3. Hacer clic en Unprotect (o eliminar la regla de protección para esa rama).

⚠️ Al desproteger la rama, cualquier persona con permiso de escritura podrá hacer push directo o force push a main sin restricciones, incluyendo sobreescribir el historial. Esto es aceptable en un repo espejo (que solo recibe copias automáticas), pero no se recomienda si en GitLab también se trabaja directamente sobre ese repositorio.

5. Probar la sincronización

  1. Hacer un cambio y push a main en GitHub.
  2. Ir a la pestaña Actions del repositorio en GitHub.
  3. Verificar que el workflow “Sincronizar con GitLab” corra sin errores.
  4. Revisar el repositorio en GitLab para confirmar que los commits, ramas y tags se replicaron correctamente.

Checklist rápido

  • Token generado en GitLab (rol Maintainer, scope write_repository)
  • Secret GITLAB_TOKEN creado en GitHub (Settings → Secrets and variables → Actions)
  • Archivo .github/workflows/sync-gitlab.yml creado con la URL correcta del repo GitLab
  • Repositorio destino existe en GitLab
  • “Allowed to force push” habilitado en la rama protegida main
  • Si el push sigue fallando: rama main desprotegida (Unprotect) en GitLab
  • Primer push de prueba exitoso, verificado en la pestaña Actions