In quali casi `git pull` potrebbe essere dannoso?


409

Ho un collega che afferma che git pullè dannoso e si arrabbia ogni volta che qualcuno lo usa.

Il git pullcomando sembra essere il modo canonico per aggiornare il repository locale. L'uso git pullcrea problemi? Quali problemi crea? Esiste un modo migliore per aggiornare un repository git?



8
Oppure puoi semplicemente git pull --rebasee impostare questa strategia come predefinita per i nuovi rami git config branch.autosetuprebase
knoopx

4
knoopx ha ragione, aggiungendo --rebaseflag per git pullsincronizzare local con remote quindi ripete le modifiche locali in cima al local aggiornato. Quindi quando spingi tutto ciò che stai facendo è aggiungere i tuoi nuovi commit alla fine del telecomando. Abbastanza semplice.
Heath Lilley,

4
Grazie @BenMcCormick. Lo avevo già fatto, ma la discussione sulla validità della domanda sembra svolgersi in questi commenti sotto la domanda. E penso che porre una domanda per creare una piattaforma per presentare la tua opinione personale in quanto il fatto non è ciò per cui la struttura di domande e risposte di SO è davvero.
mcv,

4
@RichardHansen, sembra solo un modo per ingannare il sistema di punti, specialmente con la tua risposta che ha una differenza di tono così drastica e un intervallo di tempo così breve. Usando il tuo modello di domande e risposte, potremmo solo porre domande e rispondere a noi stessi usando le nostre conoscenze precedenti. A quel punto, dovresti solo prendere in considerazione la scrittura di un post sul blog in quanto è molte volte più appropriato. Una domanda e risposta cerca specificamente la conoscenza di altre persone. Un post sul blog mostra il tuo.
Josh Brown,

Risposte:


546

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.


6
@brianz: git remote update -pè equivalente a git fetch --all -p. Ho l'abitudine di scrivere git remote update -pperché una volta fetchnon avevo l' -popzione. Per quanto riguarda il comando !, vedere la descrizione di alias.*in git help config. Dice "Se l'espansione dell'alias è preceduta da un punto esclamativo, verrà trattata come un comando shell".
Richard Hansen,

13
Git 2.0 aggiunge una pull.ffconfigurazione che sembra raggiungere la stessa cosa, senza alias.
Danny Thomas,

51
Alcuni dei motivi sembrano "tirare può causare problemi quando altri fanno cose folli". No, è roba da matti come il ripristino di un commit da un repository a monte che causa problemi. Il rebase IMO è sicuro solo quando lo fai localmente su un commit che non è stato ancora inviato. Ad esempio, quando tiri prima di spingere, la reimpostazione degli commit locali aiuta a mantenere la tua cronologia lineare (anche se la cronologia lineare non è un grosso problema). Ancora,git up sembra un'alternativa interessante.
mcv,

16
La maggior parte dei tuoi punti è perché stai facendo qualcosa di sbagliato: stai provando a rivedere il codice nel tuo ramo di lavoro . Non è una buona idea, basta creare un nuovo ramo, tirare --rebase = Preserv e quindi lanciare quel ramo (o fonderlo se lo desideri).
funkaster

5
Il punto di @ funkaster qui ha molto senso, specialmente in riferimento a: "Difficoltà a rivedere gli impegni degli altri". Questo non è il flusso di recensioni che la maggior parte degli utenti di Git usa, è qualcosa che non ho mai visto consigliato da nessuna parte ed è la causa di tutto il lavoro extra non necessario descritto sotto l'intestazione, non git pull.
Ben Regenspan,

195

La mia risposta, estratta dalla discussione nata su HackerNews:

Mi sento tentato di rispondere alla domanda usando la legge Betteridge dei titoli: perché è git pullconsiderato dannoso? Non lo è.

  • Le non linearità non sono intrinsecamente cattive. Se rappresentano la storia attuale sono ok.
  • La reintroduzione accidentale di commit reimpostati a monte è il risultato di una riscrittura errata della storia a monte. Non è possibile riscrivere la cronologia quando la cronologia viene replicata lungo diversi repository.
  • La modifica della directory di lavoro è un risultato previsto; di utilità discutibile, vale a dire di fronte al comportamento di hg / monotone / darcs / other_dvcs_predating_git, ma di nuovo non intrinsecamente negativo.
  • Fare una pausa per rivedere il lavoro degli altri è necessario per una fusione ed è di nuovo un comportamento atteso su git pull. Se non vuoi unire, dovresti usare git fetch. Ancora una volta, questa è un'idiosincrasia di git rispetto ai precedenti dvcs popolari, ma è un comportamento previsto e non intrinsecamente negativo.
  • Rendere difficile il rebase contro un ramo remoto è buono. Non riscrivere la storia a meno che non sia assolutamente necessario. Per la mia vita non riesco a capire questa ricerca di una storia (falsa) lineare
  • Non pulire i rami va bene. Ogni repo sa cosa vuole tenere. Git non ha alcuna idea di relazioni padrone-schiavo.

13
Sono d'accordo. Non c'è nulla di intrinsecamente dannoso git pull. Tuttavia, potrebbe essere in conflitto con alcune pratiche dannose, come voler riscrivere la storia più di quanto sia strettamente necessario. Ma git è flessibile, quindi se vuoi usarlo in un modo diverso, fallo sicuramente. Ma è perché tu (beh, @Richard Hansen) vuoi fare qualcosa di insolito in git, e non perché git pullè dannoso.
mcv,

28
Non potrei essere più d'accordo. Le persone sostengono git rebasee considerano git pulldannose? Veramente?
Victor Moroz,

10
Sarebbe bello vedere qualcuno creare un grafico, con la moralità come asse, e classificare i comandi git come buoni, cattivi o intermedi. Questo grafico differirebbe tra gli sviluppatori, anche se direbbe molto su uno che usa git.
michaelt

5
Il mio problema con git pullsenza l' --rebaseopzione è la direzione di unione che crea. Quando guardi il diff, tutti i cambiamenti in quell'unione ora appartengono alla persona che ha tirato, piuttosto che alla persona che ha apportato le modifiche. Mi piace un flusso di lavoro in cui la fusione è riservata a due rami separati (A -> B), quindi il commit di unione è chiaro ciò che è stato introdotto e il rebasing è riservato per l'aggiornamento sullo stesso ramo (remoto A -> locale A )
Craig Kochis,

4
Quindi cosa ti fa sapere se qualcuno ha fatto un tiro pochi secondi prima di qualcun altro o viceversa? Penso che sia solo rumore e offuschi solo la storia davvero rilevante. Ciò riduce anche il valore della storia. Una buona storia dovrebbe essere a) pulita eb) in realtà avere una storia importante.
David Ongaro,

26

Non è considerato dannoso se stai usando Git correttamente. Vedo come ti influenza negativamente dato il tuo caso d'uso, ma puoi evitare problemi semplicemente non modificando la cronologia condivisa.


10
Per approfondire questo: se tutti lavorano sul proprio ramo (che secondo me è il modo corretto di usare git), git pullnon c'è nessun tipo di problema. Ramificarsi in git è economico.
AlexQueue,

18

La risposta accettata rivendica

L'operazione rebase-pull non può essere configurata per preservare le fusioni

ma a partire da Git 1.8.5 , che può posticipare la risposta, puoi farlo

git pull --rebase=preserve

o

git config --global pull.rebase preserve

o

git config branch.<name>.rebase preserve

I documenti dicono

Quando preserve,passa anche --preserve-mergesa 'git rebase' in modo che i commit di unione commessi localmente non vengano appiattiti eseguendo 'git pull'.

Questa discussione precedente contiene informazioni e diagrammi più dettagliati: git pull --rebase --preserve-merges . Spiega anche perché git pull --rebase=preservenon è la stessa git pull --rebase --preserve-mergescosa, che non fa la cosa giusta.

Quest'altra precedente discussione spiega cosa fa effettivamente la variante preserv-merges di rebase e come è molto più complessa di una rebase normale: cosa fa esattamente "rebase --preserve-merges" di git (e perché?)


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.