Aggiornare una repository Git remota con una pipeline GitLab CI/CD

Questa guida spiega come configurare una pipeline di GitLab per aggiornare automaticamente una repository Git remota e illustra le principali best practice per farlo in modo sicuro, ripetibile e osservabile.

Prerequisiti

  • Progetto ospitato su GitLab con Runners disponibili.
  • Permessi di scrittura sul branch di destinazione.
  • Un token con permessi di scrittura alla repository: consigliato un Project Access Token o Deploy Token con scope write_repository (in alternativa un Personal Access Token limitato).

Strategia generale

  1. La pipeline parte su un evento (push, tag, merge, schedule o trigger).
  2. Un job prepara l’area di lavoro, applica le modifiche (es. aggiornamento versioni o contenuti generati) e le committa.
  3. Il job esegue il push verso il branch remoto o apre una merge request automatica, a seconda della policy di protezione del branch.

Variabili d’ambiente consigliate

Imposta variabili protette nelle Settings > CI/CD > Variables:

  • GIT_PUSH_USER_NAME e GIT_PUSH_USER_EMAIL per firmare i commit di bot.
  • REPO_PUSH_TOKEN con scope minimo necessario (write_repository), marcata come Protected e Masked.
  • Eventuali credenziali per registri o servizi esterni.

Pipeline di base: struttura

stages:
  - test
  - build
  - update

default:
image: alpine:3.20
before_script:
 - apk add --no-cache git bash curl
 - git config --global user.name "\${GIT\_PUSH\_USER\_NAME:-ci-bot}"
 - git config --global user.email "\${GIT\_PUSH\_USER\_EMAIL:[-ci-bot@example.local](mailto:-ci-bot@example.local)}"
 # Evita problemi di "detected dubious ownership" nei container recenti
 - git config --global --add safe.directory "\$CI\_PROJECT\_DIR"

variables:
 GIT_STRATEGY: fetch        # più veloce di clone completo nella maggior parte dei casi
 GIT_DEPTH: "50"            # shallow fetch per velocizzare
 FF_USE_FASTZIP: "true"     # migliora performance dei cache/artifacts recenti

cache:
 key: "$CI_COMMIT_REF_SLUG"
 paths:
  - .cache/ 

Esempio 1: aggiornare file e effettuare un push sullo stesso branch

Usa un token sicuro via HTTPS; evita SSH in ambienti ephemeral. Qui si ipotizza che il branch non sia Protected o che l’utente/il token abbiano permessi di push sul branch.

update:push-current-branch:
  stage: update
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  script:
    - |
      # Applica modifiche (esempio: bump patch in package.json)
      ./scripts/bump_version.sh  # <-- il tuo script
      # Staging & commit
      git add -A
      if git diff --cached --quiet; then
        echo "Nessuna modifica: salto il push."
        exit 0
      fi
      git commit -m "chore(ci): aggiornamenti automatici [skip ci]"
    - |
      # Reimposta l'origin con token per evitare di loggare il token nella history
      git remote set-url origin "https://oauth2:${REPO_PUSH_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git"
      # Evita conflitti: rebase veloce e push
      git pull --rebase origin "$CI_COMMIT_BRANCH"
      git push origin "HEAD:$CI_COMMIT_BRANCH"
  dependencies: []
  needs: []
  allow_failure: false
  artifacts:
    when: always
    expire_in: 1 week
    reports:
      dotenv: update.env

Esempio 2: aprire una merge request automatica

Per branch protetti, preferisci aprire una MR invece di pushare direttamente.

update:open-mr:
  stage: update
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  image: alpine:3.20
  script:
    - apk add --no-cache git bash curl jq
    - base_branch="$CI_DEFAULT_BRANCH"
    - work_branch="ci/update-$(date +%Y%m%d-%H%M%S)"
    - git checkout -b "$work_branch"
    - ./scripts/generate_docs.sh
    - git add -A
    - |
      if git diff --cached --quiet; then
        echo "Nessuna modifica: esco."
        exit 0
      fi
    - git commit -m "docs: aggiorna documentazione (auto)"
    - git remote set-url origin "https://oauth2:${REPO_PUSH_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git"
    - git push origin "$work_branch"
    - |
      # Crea MR via API
      curl --fail -sS --request POST \
        --header "PRIVATE-TOKEN: ${REPO_PUSH_TOKEN}" \
        --data-urlencode "source_branch=${work_branch}" \
        --data-urlencode "target_branch=${base_branch}" \
        --data-urlencode "remove_source_branch=true" \
        --data-urlencode "title=Aggiornamenti automatici: ${work_branch}" \
        "https://${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/merge_requests" \
        | jq -r '.web_url'

Gestione credenziali e sicurezza

  • Usa variabili protette e mascherate per i token; non inserirli nello YAML in chiaro.
  • Limita lo scope e l’expiration dei token; preferisci token di progetto a quelli personali.
  • Abilita i job di update solo su branch specifici con rules e only/except.
  • Evita di stampare il token nei log. Non usare echo del remote contenente credenziali.
  • Per branch protetti, utilizza approvazioni MR e Code Owners.

Conflitti e sincronizzazione

Quando la pipeline committa, possono verificarsi conflitti con nuovi push:

  • Esegui git pull --rebase prima del push per mantenere una storia lineare.
  • Se il rebase fallisce, fallisci il job e riprova nella run successiva o apri una MR.
  • Valuta l’uso di un branch dedicato al bot (es. ci/updates) con MR ricorrente.

Ottimizzazioni di performance

  • GIT_DEPTH per ridurre la storia scaricata; aumenta il valore per step che leggono tag/versioni.
  • Cache per dipendenze ripetibili e artifacts per passare build output tra stage.
  • Runner con tag coerenti e immagini leggere (Alpine) quando possibile.

Osservabilità e audit

  • Usa messaggi di commit chiari e convenzionali (es. chore(ci): ...).
  • Contrassegna i commit bot con [skip ci] quando non vuoi innescare pipeline ricorsive.
  • Pubblica log sintetici come artifact di testo (es. changelog generato).

Best practice riassunte

  • Principio del minimo privilegio: token con scope limitato, scadenza breve, variabili protette.
  • Branch protetti e MR automatiche: preferiscile per modifiche generate.
  • Evitare loop di pipeline: usa [skip ci] nei commit generati.
  • Idempotenza: gli script devono poter essere eseguiti più volte senza effetti collaterali inattesi.
  • Tracciabilità: messaggi di commit coerenti, artifact dei log e changelog aggiornati.
  • Gestione conflitti: rebase prima del push, fallimento esplicito se non risolvibile automaticamente.
  • Separazione dei compiti: job di test/build distinti dai job di aggiornamento.
  • Velocità senza sacrificare la correttezza: GIT_DEPTH e cache dove possibile, ma fetch completo quando servono tag o history.

Verifiche finali

  1. Il token configurato ha lo scope minimo e non scade prima dell’esecuzione pianificata.
  2. Le regole del job impediscono trigger in branch inattesi.
  3. Gli script di aggiornamento producono modifiche deterministiche e testate.
  4. Se il branch è protetto, l’apertura MR è automatica e con revisori assegnati via regole del progetto.
Torna su