Corso
Probabilmente ti è già capitato come sviluppatore: ti accorgi che nel tuo ultimo commit c’è un problema. Per annullare l’errore, esegui alcuni comandi e riscrivi la cronologia locale. Quando esegui git push --force per sistemare tutto, i tuoi compagni di squadra ti avvisano che mancano dei commit o che la pipeline ha build non riuscite.
In questo articolo ti spiego cosa fa davvero git push --force, quando usarlo e come evitare errori. Se stai iniziando con Git, ti consiglio il nostro corso Introduction to Git per imparare il controllo di versione. Inoltre, trovo molto utile la Git Cheat Sheet, che puoi scaricare, come riferimento perché contiene tutta la terminologia e i comandi Git più comuni.
Quando potresti pensare di aver bisogno di git push --force
Diciamo che stai pulendo la cronologia dei commit locali prima di pushare il tuo lavoro. Per mettere in ordine la cronologia dei commit prima del merge, esegui git rebase -i main. In locale sembra tutto perfetto, quindi provi ad aggiornare il branch remoto con un semplice git push. Ma invece del solito messaggio di successo, Git mostra un errore.
Potresti trovarti anche in un’altra situazione in cui per errore fai commit di dati sensibili, magari una chiave API o una password, e li pushi sul remoto. Dopo aver riscritto la cronologia in locale per rimuovere il commit problematico, scopri che un push normale non aggiorna il repository remoto e i dati sensibili restano. Al loro posto, ricevi un messaggio di errore.
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to 'origin'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.,
hint: 'git pull ...') before pushing again.
Questo errore “non-fast-forward” è la rete di sicurezza di Git: ti avvisa che qualcosa della tua cronologia non corrisponde a ciò che è sul server. A questo punto, è forte la tentazione di eseguire git push --force per aggirare l’errore.
Può andare bene se sei l’unico a lavorare sul branch. Tuttavia, nei progetti condivisi è facile creare confusione o cancellare il lavoro di qualcun altro.
Cosa fa davvero git push --force
Di solito Git ti consente di pushare le modifiche solo se il tuo branch locale contiene già tutto ciò che è presente sul remoto, compresi i nuovi commit. A volte però potresti riscrivere la cronologia dei commit tramite git rebase, squash o rimozione di dati sensibili. In questi casi, il tuo branch locale sarà diverso da quello remoto. Quando provi a pushare un nuovo commit, Git mostra l’errore, proteggendo il branch remoto dalla perdita di lavoro.
Usare git push --force dice a Git di saltare il controllo di sicurezza e sovrascrivere il branch remoto con ciò che c’è nel tuo branch locale.
Ecco come appare:
git push --force origin main
Questo aggiornerà il branch main remoto per farlo coincidere con il tuo main locale e cancellerà tutti i commit che non sono presenti nel tuo branch locale.
Ora immagina il tuo branch Git come un capitolo di un libro condiviso che il tuo team sta scrivendo insieme. Un push standard è come aggiungere nuovi paragrafi alla fine del capitolo. Invece, git push --force è più potente: strappa via il capitolo attuale e lo sostituisce completamente con la tua versione.
Il rischio di usare git push --force è che i collaboratori possano perdere lavoro se hanno basato le loro modifiche sui commit che hai appena eliminato. Nella prossima sezione vedremo le precauzioni da adottare quando si lavora con il comando --force.
Perché questo può mandare tutto in crisi per il tuo team
Se forzi il push delle modifiche su un branch condiviso come main o develop, è probabile che tu vada incontro a quanto segue:
- I branch locali degli altri vanno fuori sincronia: se i tuoi colleghi hanno fatto pull dei vecchi commit che ora hai cancellato dal remoto, le loro cronologie locali non corrispondono più al server. I loro branch faranno riferimento a commit “scomparsi”, creando confusione e
- Il pull genera merge confusi o errori: quando qualcuno prova a git pull dopo il tuo force push, Git non saprà come conciliare la cronologia riscritta con la sequenza locale di commit. Questo spesso porta a strani merge commit, errori criptici o situazioni in cui si chiede ai colleghi di intervenire manualmente con git fetch, git reset o persino riclonare il branch.
- Le pipeline CI/CD possono fallire: le pipeline spesso si basano su hash di commit coerenti e su una cronologia lineare. Un force push può far fallire i test, ridistribuire codice errato o mandare in crash la ricostruzione su commit obsoleti.
Dai un’occhiata al nostro corso CI/CD for Machine Learning per capire come costruire workflow CI/CD. Ti consiglio anche il corso Introduction to Data Versioning with DVC per imparare la differenza tra versionamento dei dati e del codice.
Per fare un esempio: sei sul branch feature con un collega. Fai pulizia della tua cronologia locale, poi esegui git push --force. Pochi minuti dopo, il tuo collega pusha le sue modifiche. Vede errori in Git. Quando fa pull, ottiene conflitti di merge e una cronologia incasinata. Nel frattempo, la tua pipeline CI fallisce per via di commit mancanti. Ora il tuo collega perde tempo a sbrogliare il branch e il deploy è in ritardo.
Un solo force push scatena tutto questo.
L’opzione più sicura: git push --force-with-lease
Per evitare questi problemi, ti consiglio di usare git push --force-with-lease. Questo comando aggiunge un controllo di sicurezza quando fai push su un branch remoto per evitare commit imprevisti.
git push --force-with-lease origin main
Pensa a --force-with-lease come a “chiedere se la strada è libera prima di buttarti”. Prima di fare qualsiasi cosa, verifica che nessun altro abbia modificato il branch. Se tutto è come l’avevi lasciato, la tua cronologia viene aggiornata. Ma se ci sono nuove modifiche di cui non eri a conoscenza, Git ti ferma e ti invita a rivedere prima la situazione. In questo modo, questo comando protegge sempre i collaboratori da sovrascritture involontarie.
Come recuperare dopo un force push andato male
Se perdi dei commit, non preoccuparti: puoi ancora recuperarli. Git mantiene sempre una traccia delle modifiche al commit corrente, incluse le riscritture, e la memorizza nel reflog.
Ecco come puoi recuperare i commit persi:
Passo 1: usa git reflog per trovare i commit persi
Esegui git reflog per visualizzare la cronologia del tuo branch.
git reflog
Potresti vedere i tuoi commit in questo modo:
abc1234 HEAD@{1}: push: force push
def5678 HEAD@{2}: commit: add new feature
Passo 2: ripristina il commit perso
Quando hai identificato l’hash del commit che vuoi recuperare, usa il comando seguente per raggiungere il branch specifico.
git checkout def5678
Passo 3: crea un nuovo branch
Per recuperare i file in sicurezza, crea un nuovo branch Git.
git checkout -b recovery-branch
Passo 4: ripristina il branch remoto
Ora push dei commit recuperati sul branch:
git push origin recovery-branch
Ricorda che i dati del reflog esistono solo sulle macchine locali. Pertanto, potresti non riuscire a recuperare i commit persi se nessuno dei tuoi colleghi li aveva memorizzati in locale.
Ti consiglio il nostro corso Intermediate Git per approfondire la collaborazione e il lavoro con i branch in Git.
Best practice per usare git push --force in sicurezza
Per ridurre il rischio di rompere qualcosa, ecco alcune buone abitudini da seguire:
-
Preferisci --force-with-lease a --force: questa è un’opzione più sicura che controlla che il branch remoto non sia cambiato dall’ultimo fetch. Ti aiuterà a proteggere i colleghi da sovrascritture accidentali.
-
Evita il force push su branch condivisi: non fare mai force push su branch condivisi come
main,master,developo branch direlease. Fai force push solo sui tuoi branch feature dove sei l’unico contributore, per evitare interruzioni. -
Avvisa il team prima di fare force push: se devi per forza fare push su un branch condiviso, avvisa il tuo team. In questo modo potranno salvare il proprio lavoro ed evitare perdite.
-
Usa branch protetti: puoi anche abilitare la protezione dei branch su piattaforme come GitHub, GitLab e Bitbucket. Questo limita le modifiche dirette e impedisce force push accidentali sui branch importanti.
Se vuoi saperne di più sulla gestione dei repository privati, dai un’occhiata al nostro corso Introduction to GitHub Concepts per capire come rivedere le pull request quando collabori su GitHub.
Conclusione
git push --force è un metodo utile per riscrivere la cronologia del tuo repo, sia che tu voglia pulire i commit sia rimuovere dati sensibili. Usalo sempre con cautela per evitare di interrompere il workflow del tuo team.
Quando possibile, usa --force-with-lease. Ti offre più controllo senza rischiare il lavoro degli altri. Con un po’ di attenzione e comunicazione, puoi mantenere pulita la cronologia di Git senza creare problemi al team.
Ti consiglio di dare un’occhiata ai nostri percorsi di competenze Git Fundamentals e GitHub Foundations per diventare esperto in tutto ciò che riguarda Git.
FAQ
Quali sono i rischi dell’uso di git push --force?
Può sovrascrivere i commit sul branch remoto, portando i colleghi a perdere lavoro o a imbattersi in conflitti di merge difficili da capire.
Come posso usare in sicurezza git push --force-with-lease?
Usa git push --force-with-lease invece di --force per assicurarti che il branch remoto non sia cambiato dall’ultimo fetch e per aiutare a prevenire sovrascritture accidentali.
In quali scenari è necessario git push --force?
git push --force può essere necessario quando hai fatto rebase dei commit locali, hai rimosso dati sensibili dalla cronologia o hai pulito la cronologia dei commit prima di fare il merge di un branch feature.
Come posso annullare un git push --force di cui mi pento?
Per annullare git push --force esegui git reflog per trovare il commit precedente, quindi ripristinalo facendo checkout di quel commit e forzando il push del branch corretto.
Quali sono le best practice per evitare errori con git push --force?
Usa --force-with-lease, evita il force push su branch condivisi, comunica con il tuo team e proteggi i branch importanti sulla tua piattaforma di hosting Git.


