Sommario
Per impostazione predefinita, git pullcrea commit di unione che aggiungono rumore e complessità alla cronologia del codice. Inoltre, pullrende facile non pensare a come le modifiche potrebbero essere influenzate dalle modifiche in arrivo.
Il git pullcomando è 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 upalias come questo:
git config --global alias.up '!git remote update -p; git merge --ff-only @{u}'
e usando git upinvece di git pull. Preferisco questo alias git pull --ff-onlypiuttosto 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 pullnon è male se viene usato correttamente. Diverse modifiche recenti a Git hanno reso più semplice l'uso git pullcorretto, ma sfortunatamente il comportamento predefinito di una pianura git pullha 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 pullcomando è equivalente a running git fetchseguito da git merge @{u}. Se nel repository locale sono presenti commit non compressi, la parte di git pullunione 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 rebasecomando e perché è stato creato.) I commit di unione creati da git pullnon 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 pullrebase anziché l'unione, ma anche questo ha dei problemi (discussi più avanti). Invece, git pulldovrebbe 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 pullunirà 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 pullviene 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 pullL'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 stashe 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 pulle 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 pullnon ti dà quella flessibilità.
Rebasing su un ramo remoto
Un modello di utilizzo Git comune è quello di eseguire un git pullper apportare le ultime modifiche seguite da un git rebase @{u}per eliminare il commit di merge git pullintrodotto. E 'abbastanza comune che Git ha alcune opzioni di configurazione per ridurre questi due passi per un solo passo dicendo git pulldi eseguire un rebase invece di una fusione (vedi branch.<branch>.rebase, branch.autosetuprebasee pull.rebaseopzioni).
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 pullcon branch.<branch>.rebaseset su true) né un pull-pull (il git pullcomportamento predefinito ) seguito da un rebase funzionerà. Questo perché git rebaseelimina le fusioni (linearizza il DAG) senza l' --preserve-mergesopzione. 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=preservee git config pull.rebase preserve. Queste cause git pullda fare git rebase --preserve-mergesdopo il recupero i commit a monte. (Grazie a funkaster per l'heads-up!)
Pulizia dei rami eliminati
git pullnon elimina i rami di tracciamento remoti corrispondenti ai rami che sono stati eliminati dal repository remoto. Ad esempio, se qualcuno elimina il ramo foodal 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 upinvece digit pull
Invece git pull, consiglio di creare e utilizzare il seguente git upalias:
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 upnon 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 upall'alias sopra :
git config --global alias.up 'pull --ff-only --all -p'
Questa versione di git upha lo stesso comportamento git updell'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'
-pargomento 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 pullin modo predefinito solo le fusioni con avanzamento rapido:
git config --global pull.ff only
Questo fa sì che si comporti git pullcome git pull --ff-only, ma non recupera ancora tutti i commit a monte o ripulisce i vecchi origin/*rami, quindi preferisco ancora git up.