git fetch e git pull: qual è la differenza e quale scegliere
git fetch scarica solo i nuovi commit dal server e aggiorna i branch remoti come origin/main, senza toccare i vostri file e branch. git pull fa la stessa cosa e integra subito le modifiche nel branch corrente con merge o rebase. Fetch è sempre sicuro, pull è comodo quando siete pronti ad accettare subito le modifiche altrui.
La risposta breve
git fetch scarica dal server i nuovi commit e aggiorna i riferimenti dei branch remoti (origin/main, origin/feature-x), ma non modifica né il vostro branch corrente né i file nella working directory. git pull è git fetch più l'integrazione immediata di quanto scaricato nel branch corrente: con un merge (di default) o con un rebase.
Da qui la regola pratica: fetch si può lanciare in qualsiasi momento, non rompe nulla. Pull modifica il vostro branch e può finire con un conflitto, quindi conviene lanciarlo con la working directory pulita e sapendo cosa sta per arrivare.
Dove vivono i branch remoti
Perché la differenza diventi evidente, bisogna capire che nel repository locale esistono due tipi di branch. Quelli locali, main e feature-x, li fate avanzare voi con i vostri commit. I branch remoti di tracking, come origin/main, sono segnalibri del tipo «così era il server al momento dell'ultima sincronizzazione». Non si possono spostare a mano: li aggiorna solo la comunicazione con il server, cioè fetch, pull e push.
git branch -a
* main
remotes/origin/HEAD -> origin/main
remotes/origin/main
remotes/origin/feature-x
git status confronta il vostro branch proprio con il segnalibro origin/main, non con il server reale. Per questo il messaggio «Your branch is up to date with 'origin/main'» può mentire: se non fate fetch da tempo, Git semplicemente non sa dei nuovi commit dei colleghi.
Cosa fa git fetch
$ git fetch
remote: Enumerating objects: 7, done.
remote: Counting objects: 100% (7/7), done.
Unpacking objects: 100% (4/4), done.
From github.com:team/project
3f2a1c9..8b7d4e2 main -> origin/main
L'ultima riga dice l'essenziale: il riferimento origin/main si è spostato da 3f2a1c9 a 8b7d4e2. Il vostro main è rimasto dov'era. Ora potete guardare con calma cosa è arrivato:
git status # Your branch is behind 'origin/main' by 2 commits
git log main..origin/main --oneline # i commit che non avete
git diff main origin/main # cosa cambierà nei file
Varianti utili di fetch:
git fetch --all: scarica le modifiche da tutti i remote (utile se un fork ha siaoriginsiaupstream);git fetch --prune(-p): elimina i segnalibri locali dei branch che non esistono più sul server;git fetch origin main: scarica un solo branch;git fetch --tags: scarica in più tutti i tag.
Perché il prune sia sempre attivo, abilitatelo una volta: git config --global fetch.prune true.
Cosa fa git pull
git pull esegue due passaggi di fila. Prima un fetch per il branch upstream del branch corrente, poi l'integrazione di origin/<branch> nel vostro branch. Letteralmente equivale a:
git fetch origin
git merge origin/main # oppure git rebase origin/main con pull --rebase
Il risultato dell'integrazione dipende da come si sono separate le storie:
- non avete nuovi commit: avviene un fast-forward, il branch viene semplicemente portato avanti;
- ci sono nuovi commit sia da voi sia sul server: con merge si crea un commit di merge, con rebase i vostri commit vengono riposizionati sopra quelli del server;
- le stesse righe sono state modificate da entrambe le parti: conflitto, da risolvere a mano.
Confronto tra git fetch e git pull
| git fetch | git pull | |
|---|---|---|
| Scarica i commit | Sì | Sì |
| Aggiorna origin/* | Sì | Sì |
| Modifica il branch corrente | No | Sì |
| Modifica i file nella working directory | No | Sì |
| Può causare un conflitto | No | Sì |
| Richiede una working directory pulita | No | Preferibilmente |
| Coinvolge altri branch | Aggiorna tutti i segnalibri tracciati | Integra solo nel corrente |
| Quando usarlo | Vedere cosa è cambiato; prima di un rebase; negli script | Mettersi rapidamente in pari con il server nel proprio branch |
git pull: merge o rebase
Da Git 2.27, quando le storie divergono e non c'è una configurazione esplicita, pull mostra un avviso, e nelle versioni recenti invece di un merge silenzioso si ferma con l'errore «Need to specify how to reconcile divergent branches». Git vi chiede di scegliere una strategia una volta per tutte:
git config --global pull.rebase false # merge (il comportamento predefinito di un tempo)
git config --global pull.rebase true # rebase
git config --global pull.ff only # solo fast-forward, altrimenti rifiuto
Merge conserva la storia così com'è e aggiunge un commit «Merge branch 'main' of github.com:...». Se fate pull ogni giorno in un branch con commit locali, la storia si riempie di questi merge tecnici.
Rebase riposiziona i vostri commit non inviati sopra quelli freschi del server. La storia resta lineare e i conflitti si risolvono un commit alla volta. Per i commit locali non pubblicati è di solito la scelta migliore. Rebase è pericoloso solo per commit già inviati, su cui altri stanno costruendo il proprio lavoro.
ff only è la modalità più rigida: pull funziona solo se non avete commit vostri. Se la storia è divergente, decidete esplicitamente cosa fare. Molti sviluppatori esperti la impostano proprio per i branch principali.
Se nella working directory ci sono modifiche non committate e il pull serve subito, torna utile git pull --rebase --autostash: Git mette da parte le modifiche, esegue il rebase e le rimette al loro posto.
Lo scenario tipico: fetch, guardare, integrare
L'approccio «prima fetch» dà il controllo su ciò che entra nel vostro branch. È particolarmente utile prima di aprire una pull request e dopo una lunga pausa.
git fetch
git log --oneline --graph HEAD origin/main # come si sono separati i branch
git rebase origin/main # oppure git merge origin/main
git push
Un altro caso frequente è aggiornare il proprio branch feature con il main recente senza passare su di esso:
git switch feature-x
git fetch
git rebase origin/main
Qui pull non va bene: git pull nel branch feature scaricherebbe le modifiche del suo upstream (origin/feature-x), non del branch principale. Si potrebbe scrivere git pull --rebase origin main, ma la variante con fetch è più chiara.
Sincronizzazione con il server: come tenere il branch aggiornato
Chi cerca «git sync» di solito intende il ciclo «prendere il lavoro altrui, inviare il proprio». Nel Git puro non esiste un comando sync (il pulsante Sync c'è negli IDE e nei client grafici, e internamente fa pull e poi push). A mano, un ciclo affidabile è questo:
git status: verificare che tutto sia committato o messo da parte congit stash;git pull --rebase: scaricare le modifiche e riposizionare i propri commit in cima;- risolvere eventuali conflitti: correggere i file,
git add,git rebase --continue; - lanciare i test;
git push.
Per sincronizzare un fork con il repository originale:
git remote add upstream https://github.com/original/project.git
git fetch upstream
git switch main
git merge --ff-only upstream/main
git push origin main
Problemi frequenti con git pull
Your local changes would be overwritten by merge
Pull vuole modificare file che state modificando e non avete committato. Opzioni: fare commit, mettere da parte (git stash, poi pull e git stash pop) o usare --autostash. Non serve cancellare le modifiche per fare pull.
Conflitto dopo il pull
Git si ferma e mostra i file in conflitto in git status. Apriteli, scegliete la variante giusta tra i marcatori <<<<<<< e >>>>>>>, poi git add e git commit (con merge) o git rebase --continue (con rebase). Ci avete ripensato? git merge --abort o git rebase --abort riportano tutto com'era prima del pull.
There is no tracking information for the current branch
Il branch non ha un upstream configurato e Git non sa da dove scaricare. Indicatelo esplicitamente: git pull origin main, oppure configurate il collegamento una volta: git branch --set-upstream-to=origin/main.
refusing to merge unrelated histories
Il repository locale e quello remoto sono stati creati in modo indipendente, ad esempio sul server è stato aggiunto un README alla creazione. Se siete sicuri di volerli unire: git pull origin main --allow-unrelated-histories.
Un pull andato male: come tornare indietro
Se il merge dopo il pull è già concluso e il risultato non vi soddisfa, riportate il branch allo stato precedente: git reset --hard ORIG_HEAD. Il comando elimina le modifiche non committate, quindi prima controllate git status. Nel peggiore dei casi la posizione precedente del branch si trova in git reflog.
Cosa scegliere
- Volete sapere cosa c'è di nuovo senza cambiare nulla:
git fetch. - Lavorate nel vostro branch e vi mettete semplicemente in pari con il server:
git pull --rebase(o pull conpull.rebase trueconfigurato). - Aggiornate il branch principale senza commit vostri:
git pull --ff-only. - Dovete integrare
mainin un branch feature:git fetche poigit rebase origin/mainogit merge origin/main. - Scrivete uno script o una CI: fetch e comandi espliciti, senza pull.
Come appare sul grafo dei commit
Immaginate che voi e un collega siate partiti dal commit C. Voi avete fatto in locale il commit D, e il collega ha inviato al server E. Dopo git fetch il vostro repository conosce entrambe le linee: main punta a D, origin/main a E, e hanno l'antenato comune C.
D <- main (HEAD)
/
A---B---C
\
E <- origin/main
Da qui decide tutto il metodo di integrazione. git merge origin/main crea un commit M con due genitori, D ed E. git rebase origin/main copia D sopra E come nuovo commit D' con un hash diverso, e la storia diventa una linea retta. git pull esegue semplicemente questi stessi passaggi in automatico, senza pause tra l'uno e l'altro. Proprio quella pausa, la possibilità di guardare il grafo prima dell'integrazione, è il valore principale di un fetch separato.
Fetch e pull negli IDE
In VS Code, IntelliJ IDEA e altri editor i pulsanti hanno nomi diversi, ma dietro ci sono gli stessi comandi. «Fetch» è git fetch, «Pull» o «Update Project» è un pull con la strategia scelta nelle impostazioni (merge o rebase), «Sync Changes» è pull seguito da push. Prima di premere Sync su un branch condiviso conviene sapere quale strategia è impostata, altrimenti nella storia compariranno commit di merge inattesi. Molti IDE fanno anche fetch periodici in background, quindi il contatore dei commit in arrivo nella barra di stato di solito è aggiornato.
In sintesi
git fetch aggiorna ciò che sapete del server, git pull applica anche questa conoscenza al branch corrente. Fetch è sicuro in qualsiasi momento, pull è comodo ma modifica il branch e può portare un conflitto, quindi configuratene la strategia una volta e tenete pulita la working directory. La differenza tra push, pull e fetch, il lavoro con i remote e con GitHub sono trattati in pratica nel corso su Git.
Domande frequenti
Bisogna fare git fetch prima di git pull?
No, pull esegue da solo il fetch come primo passaggio. Un fetch separato serve quando volete prima guardare le modifiche e poi decidere come integrarle.
git fetch può cancellare le mie modifiche?
No. Fetch modifica solo i riferimenti remoti come origin/main e non tocca i branch locali, l'indice e la working directory.
git pull aggiorna tutti i branch?
No, pull integra le modifiche solo nel branch corrente. I segnalibri origin/* degli altri branch vengono aggiornati grazie al fetch, ma i branch locali restano dove sono.
Ogni quanto fare git fetch?
Prima di iniziare a lavorare, prima di creare una pull request e prima del push. Molti IDE fanno fetch automaticamente in background, ed è sicuro.
Cos'è FETCH_HEAD?
Un riferimento di servizio in cui fetch registra l'ultima cosa scaricata. Ad esempio, dopo git fetch origin main si può eseguire git merge FETCH_HEAD.
Questo argomento è trattato in modo approfondito, con pratica, nel corso «Git da zero a sviluppatore sicuro».
Vai al corso