Sommario
Per impostazione predefinita, git pull
crea commit di unione che aggiungono rumore e complessità alla cronologia del codice. Inoltre, pull
rende facile non pensare a come le modifiche potrebbero essere influenzate dalle modifiche in arrivo.
Il git pull
comando è sicuro purché esegua solo fusioni con avanzamento rapido. Se git pull
è configurato per eseguire solo fusioni di avanzamento rapido e quando un'unione di avanzamento rapido non è possibile, Git uscirà con un errore. Questo ti darà l'opportunità di studiare i commit in entrata, pensare a come potrebbero influenzare i tuoi commit locali e decidere il miglior modo di agire (unire, rebase, resettare, ecc.).
Con Git 2.0 e versioni successive, puoi eseguire:
git config --global pull.ff only
per modificare il comportamento predefinito su solo avanzamento rapido. Con le versioni Git tra 1.6.6 e 1.9.x dovrai prendere l'abitudine di digitare:
git pull --ff-only
Tuttavia, con tutte le versioni di Git, consiglio di configurare un git up
alias come questo:
git config --global alias.up '!git remote update -p; git merge --ff-only @{u}'
e usando git up
invece di git pull
. Preferisco questo alias git pull --ff-only
piuttosto che:
- funziona con tutte le versioni (non antiche) di Git,
- recupera tutti i rami a monte (non solo il ramo su cui stai attualmente lavorando) e
- pulisce i vecchi
origin/*
rami che non esistono più a monte.
Problemi con git pull
git pull
non è male se viene usato correttamente. Diverse modifiche recenti a Git hanno reso più semplice l'uso git pull
corretto, ma sfortunatamente il comportamento predefinito di una pianura git pull
ha diversi problemi:
- introduce non linearità non necessarie nella storia
- facilita la reintroduzione accidentale di commit che sono stati intenzionalmente reimpostati a monte
- modifica la directory di lavoro in modi imprevedibili
- mettere in pausa ciò che stai facendo per rivedere il lavoro di qualcun altro è fastidioso
git pull
- rende difficile il riposizionamento corretto sul ramo remoto
- non pulisce i rami che sono stati eliminati nel repository remoto
Questi problemi sono descritti in maggior dettaglio di seguito.
Storia non lineare
Per impostazione predefinita, il git pull
comando è equivalente a running git fetch
seguito da git merge @{u}
. Se nel repository locale sono presenti commit non compressi, la parte di git pull
unione crea un commit di unione.
Non c'è nulla di intrinsecamente negativo nei merge commit, ma possono essere pericolosi e dovrebbero essere trattati con rispetto:
- I commit di unione sono intrinsecamente difficili da esaminare. Per capire cosa sta facendo una fusione, devi capire le differenze tra tutti i genitori. Un diff convenzionale non trasmette bene queste informazioni multidimensionali. Al contrario, una serie di commit normali è facile da rivedere.
- Unire la risoluzione dei conflitti è complicato e gli errori spesso non vengono rilevati per molto tempo perché gli impegni di unione sono difficili da rivedere.
- Le fusioni possono sostituire tranquillamente gli effetti dei commit regolari. Il codice non è più la somma dei commit incrementali, che porta a fraintendimenti su ciò che è effettivamente cambiato.
- Unire i commit può interrompere alcuni schemi di integrazione continua (ad esempio, costruire automaticamente solo il percorso del primo genitore secondo la convenzione presunta secondo cui i secondi genitori indicano lavori incompleti).
Ovviamente c'è un tempo e un posto per le fusioni, ma capire quando e quando non si devono usare le fusioni può migliorare l'utilità del proprio repository.
Nota che lo scopo di Git è di facilitare la condivisione e il consumo dell'evoluzione di una base di codice, non di registrare con precisione la cronologia esattamente come si è svolta. (Se non sei d'accordo, considera il rebase
comando e perché è stato creato.) I commit di unione creati da git pull
non trasmettono utili semantiche ad altri: dicono solo che qualcun altro è passato al repository prima che tu avessi finito con le tue modifiche. Perché questi merge si impegnano se non sono significativi per gli altri e potrebbero essere pericolosi?
È possibile configurare il git pull
rebase anziché l'unione, ma anche questo ha dei problemi (discussi più avanti). Invece, git pull
dovrebbe essere configurato per eseguire solo fusioni con avanzamento rapido.
Reintroduzione di impegni rebased-out
Supponiamo che qualcuno rinnovi un ramo e la forza lo spinga. Questo in genere non dovrebbe accadere, ma a volte è necessario (ad esempio, per rimuovere un file di registro 50GiB che è stato accidentalmente inserito e inviato). L'unione eseguita git pull
unirà la nuova versione del ramo upstream nella versione precedente che esiste ancora nel repository locale. Se si spinge il risultato, forcelle e torce inizieranno a farsi strada.
Alcuni potrebbero sostenere che il vero problema siano gli aggiornamenti forzati. Sì, è generalmente consigliabile evitare spinte di forza ogni volta che è possibile, ma a volte sono inevitabili. Gli sviluppatori devono essere pronti a gestire gli aggiornamenti forzati, perché a volte accadranno. Questo significa non fondersi ciecamente nei vecchi impegni tramite un ordinario git pull
.
Modifiche alla directory di lavoro a sorpresa
Non c'è modo di prevedere come apparirà la directory o l'indice di lavoro fino a quando non git pull
viene eseguita. Potrebbero esserci conflitti di unione che devi risolvere prima di poter fare qualsiasi altra cosa, potrebbe introdurre un file di registro 50GiB nella directory di lavoro perché qualcuno lo ha spinto accidentalmente, potrebbe rinominare una directory in cui stai lavorando, ecc.
git remote update -p
(o git fetch --all -p
) ti consente di esaminare gli impegni di altre persone prima di decidere di unirti o di riformarti, consentendoti di formare un piano prima di agire.
Difficoltà a rivedere gli impegni degli altri
Supponi di essere nel mezzo di apportare alcune modifiche e che qualcun altro vuole che tu riveda alcuni commit che hanno appena spinto. git pull
L'operazione di unione (o rebase) modifica la directory di lavoro e l'indice, il che significa che la directory di lavoro e l'indice devono essere puliti.
Potresti usare git stash
e poi git pull
, ma cosa fai quando hai finito di recensire? Per tornare al punto in cui eri, devi annullare l'unione creata da git pull
e applicare lo stash.
git remote update -p
(o git fetch --all -p
) non modifica la directory o l'indice di lavoro, quindi è sicuro eseguirlo in qualsiasi momento, anche se sono state apportate modifiche temporanee e / o non. Puoi mettere in pausa ciò che stai facendo e rivedere il commit di qualcun altro senza preoccuparti di riporre o terminare il commit su cui stai lavorando. git pull
non ti dà quella flessibilità.
Rebasing su un ramo remoto
Un modello di utilizzo Git comune è quello di eseguire un git pull
per apportare le ultime modifiche seguite da un git rebase @{u}
per eliminare il commit di merge git pull
introdotto. E 'abbastanza comune che Git ha alcune opzioni di configurazione per ridurre questi due passi per un solo passo dicendo git pull
di eseguire un rebase invece di una fusione (vedi branch.<branch>.rebase
, branch.autosetuprebase
e pull.rebase
opzioni).
Sfortunatamente, se si dispone di un commit di merge senza compressione che si desidera conservare (ad esempio, un commit che unisce un ramo di funzionalità push in master
), né un pull-pull ( git pull
con branch.<branch>.rebase
set su true
) né un pull-pull (il git pull
comportamento predefinito ) seguito da un rebase funzionerà. Questo perché git rebase
elimina le fusioni (linearizza il DAG) senza l' --preserve-merges
opzione. L'operazione rebase-pull non può essere configurata per conservare le fusioni e un merge-pull seguito da a git rebase -p @{u}
non eliminerà l'unione causata dal merge-pull. Aggiornamento: Git v1.8.5 aggiunto git pull --rebase=preserve
e git config pull.rebase preserve
. Queste cause git pull
da fare git rebase --preserve-merges
dopo il recupero i commit a monte. (Grazie a funkaster per l'heads-up!)
Pulizia dei rami eliminati
git pull
non elimina i rami di tracciamento remoti corrispondenti ai rami che sono stati eliminati dal repository remoto. Ad esempio, se qualcuno elimina il ramo foo
dal repository remoto, vedrai comunque origin/foo
.
Questo porta gli utenti a resuscitare accidentalmente i rami uccisi perché pensano di essere ancora attivi.
Un'alternativa migliore: utilizzare git up
invece digit pull
Invece git pull
, consiglio di creare e utilizzare il seguente git up
alias:
git config --global alias.up '!git remote update -p; git merge --ff-only @{u}'
Questo alias scarica tutti gli ultimi commit da tutti i rami a monte (potando i rami morti) e cerca di far avanzare rapidamente il ramo locale all'ultimo commit sul ramo a monte. In caso di successo, allora non c'erano impegni locali, quindi non c'erano rischi di unire conflitti. L'avanzamento rapido non riuscirà se ci sono commit locali (non compressi), dandoti l'opportunità di rivedere i commit upstream prima di agire.
Questo modifica ancora la tua directory di lavoro in modi imprevedibili, ma solo se non hai cambiamenti locali. Diversamente git pull
, git up
non ti lascerà mai al prompt aspettandoti di risolvere un conflitto di unione.
Un'altra opzione: git pull --ff-only --all -p
Di seguito è un'alternativa git up
all'alias sopra :
git config --global alias.up 'pull --ff-only --all -p'
Questa versione di git up
ha lo stesso comportamento git up
dell'alias precedente , tranne:
- il messaggio di errore è un po 'più enigmatico se il tuo ramo locale non è configurato con un ramo a monte
- si basa su una caratteristica non documentata (l'
-p
argomento a cui viene passato fetch
) che potrebbe cambiare nelle versioni future di Git
Se stai utilizzando Git 2.0 o versioni successive
Con Git 2.0 e versioni successive puoi configurare git pull
in modo predefinito solo le fusioni con avanzamento rapido:
git config --global pull.ff only
Questo fa sì che si comporti git pull
come git pull --ff-only
, ma non recupera ancora tutti i commit a monte o ripulisce i vecchi origin/*
rami, quindi preferisco ancora git up
.