git push: come inviare le modifiche al repository remoto
git push invia i commit locali del branch corrente al repository remoto e vi sposta il puntatore del branch. La prima volta si pubblica il branch con git push -u origin nome-branch, poi basta git push. Se sul server ci sono commit altrui, bisogna prima recuperarli con git pull, non fare un force push.
Cosa fa git push
Il comando git push prende i commit del vostro branch locale che non sono ancora sul server, li trasferisce al repository remoto (di solito si chiama origin) e lì sposta il puntatore del branch sul vostro ultimo commit. I file della cartella di lavoro e le modifiche non committate non vanno da nessuna parte: si invia solo la cronologia, cioè i commit.
È importante capire che push non è una «sincronizzazione». Funziona in una sola direzione: dal repository locale a quello remoto. Per ricevere le modifiche altrui servono git fetch o git pull. Confondere queste direzioni è all'origine di quasi tutti i problemi con cui i principianti si rivolgono al mentor.
Un'altra regola di Git: per impostazione predefinita il push è consentito solo come «avanzamento rapido» (fast-forward). Il server accetta i vostri commit se proseguono la cronologia già presente sul server. Se qualcuno ha inviato i propri commit prima di voi, il vostro push verrà rifiutato: è una protezione, non un errore.
Sintassi del comando
git push [<remote>] [<branch-locale>[:<branch-remoto>]]
Se eseguite git push senza argomenti, Git prende il branch corrente e lo invia al branch impostato come upstream (tracciato). La forma esplicita è utile quando l'upstream non è ancora configurato o volete inviare il branch con un altro nome:
git push origin main # inviare il main locale in origin/main
git push origin feature/login # inviare il branch feature/login
git push origin fix:hotfix-123 # inviare il fix locale come hotfix-123 sul server
git push origin HEAD # inviare il branch corrente con lo stesso nome
Come inviare le modifiche la prima volta
Il percorso tipico da un nuovo progetto al codice su GitHub o GitLab è questo. Prima create un repository vuoto sul server, poi collegate quello locale e inviate il branch.
git init
git add .
git commit -m "Primo commit"
git branch -M main
git remote add origin git@github.com:user/project.git
git push -u origin main
Il flag -u (forma estesa --set-upstream) collega il branch locale main a quello remoto origin/main. Da quel momento nel branch basta scrivere git push e git pull, e git status inizierà a mostrare di quanti commit siete avanti o indietro rispetto al server.
Per verificare a cosa punta il remote usate git remote -v. Se l'indirizzo è sbagliato, correggetelo con git remote set-url origin <nuovo-indirizzo>, invece di eliminare e ricreare il repository.
Come inviare un nuovo branch
Avete creato un branch per un'attività, fatto alcuni commit e scrivete git push. Git risponde:
fatal: The current branch feature/login has no upstream branch.
To push the current branch and set the remote as upstream, use
git push --set-upstream origin feature/login
Non è un errore ma un suggerimento: il branch non ha ancora un corrispondente sul server. Eseguite il comando proposto o la forma breve git push -u origin feature/login. Perché Git lo faccia automaticamente per tutti i nuovi branch, attivate l'impostazione (disponibile da Git 2.37):
git config --global push.autoSetupRemote true
Perché git push viene rifiutato: rejected, non-fast-forward
La situazione più frequente nel lavoro di squadra:
! [rejected] main -> main (fetch first)
error: failed to push some refs to 'github.com:user/project.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally.
Un collega ha inviato i suoi commit prima di voi. La vostra cronologia e quella del server si sono separate, e se Git accettasse il vostro push i commit del collega andrebbero persi. La soluzione corretta è prima recuperare le modifiche e integrarvi le vostre:
git pull --rebase # recuperare i commit altrui e spostare i propri sopra
# in caso di conflitti: correggere i file, git add, git rebase --continue
git push
Invece di --rebase si può fare un normale git pull: in quel caso comparirà un merge commit. Entrambe le varianti sono corrette; nella maggior parte dei team, per i propri commit locali si preferisce il rebase, così la cronologia resta lineare. L'importante è non «risolvere» il rifiuto con --force.
git push --force e --force-with-lease
L'invio forzato serve quando avete riscritto consapevolmente la cronologia del vostro branch: avete fatto git commit --amend, git rebase o unito dei commit prima della review. Un push normale non accetterà quel branch, perché i vecchi commit sul server non sono più antenati di quelli nuovi.
git push --force-with-lease
--force-with-lease sovrascrive il branch sul server solo se si trova ancora dove l'ha visto l'ultima volta il vostro Git locale (secondo il riferimento origin/<branch>). Se un collega ha fatto in tempo a pushare qualcosa, il comando rifiuterà e non cancellerete il suo lavoro. Il normale --force (-f) non fa questa verifica e sovrascrive tutto in silenzio.
C'è un dettaglio: se prima eseguite git fetch, il riferimento origin/<branch> si aggiorna e la protezione smette di funzionare, perché Git considera che abbiate «visto» i commit altrui. Quindi non fate fetch subito prima di un force push senza guardare cosa è arrivato.
| Comando | Cosa fa | Quando usarlo |
|---|---|---|
git push | Solo fast-forward | Sempre, come impostazione predefinita |
git push --force-with-lease | Sovrascrittura con verifica | Il proprio branch dopo rebase o amend |
git push --force | Sovrascrittura senza verifiche | Quasi mai; mai sui branch condivisi |
Sui branch condivisi (main, develop) non si fa push forzato: per chiunque abbia già scaricato la vecchia cronologia, questa divergerà da quella del server. Di solito questi branch sono anche protetti dalle impostazioni del repository, e il server risponderà protected branch hook declined.
Come inviare i tag
Un normale git push non invia i tag. Vanno trasmessi esplicitamente:
git tag -a v1.2.0 -m "Release 1.2.0"
git push origin v1.2.0 # un solo tag
git push --tags # tutti i tag locali
git push --follow-tags # commit e tag annotati che puntano a essi
--follow-tags è più comodo di --tags: non invia tag leggeri locali creati per caso, ma solo quelli annotati e raggiungibili dai commit inviati.
Come eliminare un branch sul server
Anche l'eliminazione si fa con push:
git push origin --delete feature/login
# sintassi vecchia, fa la stessa cosa:
git push origin :feature/login
Il branch locale rimane; potete eliminarlo con git branch -d feature/login. Ai colleghi resterà il riferimento origin/feature/login finché non eseguono git fetch --prune.
Flag utili di git push
| Flag | Scopo |
|---|---|
-u, --set-upstream | Memorizzare il branch remoto come upstream |
-n, --dry-run | Mostrare cosa verrebbe inviato senza inviare nulla |
--all | Inviare tutti i branch locali |
--tags / --follow-tags | Inviare i tag |
-d, --delete | Eliminare un branch o un tag sul server |
--force-with-lease | Riscrittura sicura della cronologia |
-v, --verbose | Output dettagliato |
--no-verify | Saltare l'hook pre-push (da usare consapevolmente) |
Errori frequenti con git push e come risolverli
Permission denied (publickey)
Il server non ha accettato la vostra chiave SSH. Verificate che la chiave esista (ls ~/.ssh), che la parte pubblica sia aggiunta all'account e che la connessione funzioni: ssh -T git@github.com. Se il remote è configurato via HTTPS, Git chiederà login e token, non la chiave: controllate git remote -v.
Authentication failed via HTTPS
GitHub e molti altri servizi non accettano più da tempo la password dell'account tramite HTTPS. Serve un token di accesso personale oppure il passaggio a SSH. Per salvare il token aiuta il credential helper: git config --global credential.helper store (lo conserva in chiaro) o il gestore di credenziali del vostro sistema operativo.
src refspec main does not match any
Non avete un branch con quel nome. Il più delle volte nel repository vuoto non è stato ancora fatto nessun commit, oppure il branch si chiama master. Controllate git branch e fate un commit o rinominate il branch: git branch -M main.
Everything up-to-date, anche se ci sono modifiche
Avete dimenticato il commit. git push invia commit, non file. Eseguite git status, poi git add e git commit. Seconda possibilità: vi trovate in un branch diverso da quello che vi aspettate di vedere sul server.
File troppo grande
I servizi limitano la dimensione dei file (su GitHub 100 MB). Se un file del genere è già finito nella cronologia, non basta eliminarlo con un nuovo commit: bisogna riscrivere la cronologia o toglierlo dai commit non ancora inviati (git reset --soft, poi un nuovo commit). Per i file binari di grandi dimensioni si usa Git LFS.
Il ciclo di lavoro con git push
git switch -c feature/x: un branch per l'attività.- Commit in piccoli passi logici.
git fetchegit rebase origin/main: allinearsi al branch principale aggiornato.git push -u origin feature/x: pubblicare il branch.- Aprire una pull request; dopo la review, commit aggiuntivi e un normale
git push. - Se avete dovuto riscrivere la cronologia:
git push --force-with-lease.
Prima dell'invio è utile vedere cosa andrà esattamente sul server: git log origin/main..HEAD --oneline mostra i commit che sul server non ci sono ancora.
Cosa configurare una volta per tutte
Alcune impostazioni globali evitano la maggior parte delle sorprese durante l'invio. push.default stabilisce cosa invia git push senza argomenti. Il valore predefinito nelle versioni moderne è simple: si invia solo il branch corrente e solo all'upstream con lo stesso nome. Di solito non serve cambiarlo, ma conviene verificare che in una vecchia configurazione non sia rimasto matching, che invia insieme tutti i branch con nome corrispondente.
git config --global push.default simple
git config --global push.autoSetupRemote true
git config --global push.followTags true
git config --global pull.rebase true
push.followTags rende --follow-tags il comportamento predefinito, mentre pull.rebase evita merge commit superflui quando il push viene rifiutato e bisogna prima recuperare le modifiche altrui.
L'hook pre-push
Il file .git/hooks/pre-push viene eseguito prima dell'invio. Se termina con un codice diverso da zero, il push non avviene. I team spesso vi lanciano test o linter, perché il codice rotto non arrivi sul server. Aggirare l'hook con --no-verify ha senso solo quando capite esattamente perché fallisce.
Riepilogo
git push pubblica i vostri commit nel repository remoto e funziona in una sola direzione. Fate il primo invio di un branch con -u, in caso di rifiuto recuperate prima le modifiche altrui con pull, e inviate la cronologia riscritta del vostro branch solo con --force-with-lease. Il lavoro con i repository remoti, push, pull e chiavi SSH è affrontato in pratica nel corso su Git.
Domande frequenti
git push invia le modifiche non committate?
No. Il push trasferisce solo i commit; le modifiche nella cartella di lavoro e nell'index restano in locale finché non ne fate il commit.
Come annullare un git push?
Nel proprio branch si può annullare il commit in locale (git reset) e inviare con git push --force-with-lease. In un branch condiviso è più sicuro creare un commit inverso con git revert e fare un push normale.
Si può inviare un solo commit?
Sì, indicandone l'hash: git push origin abc1234:main invia tutta la cronologia fino a quel commit incluso, ma non i commit successivi. Non si può inviare un commit senza i suoi antenati.
Che differenza c'è tra git push e git push origin main?
Senza argomenti Git usa l'upstream configurato del branch corrente, mentre la forma esplicita indica direttamente remote e branch e funziona anche senza upstream configurato.
Come fare push su due repository contemporaneamente?
Aggiungete un secondo remote e inviate a entrambi per nome, oppure aggiungete a origin due indirizzi di push con git remote set-url --add --push origin indirizzo (da eseguire per entrambi gli indirizzi, compreso quello originale, altrimenti il push andrà solo al secondo).
Questo argomento è trattato in modo approfondito, con pratica, nel corso «Git da zero a sviluppatore sicuro».
Vai al corso