Dans quels cas «git pull» pourrait-il être nocif?

409

J'ai un collègue qui prétend que git pullc'est nocif et s'énerve chaque fois que quelqu'un l'utilise.

La git pullcommande semble être le moyen canonique de mettre à jour votre référentiel local. Est-ce que l'utilisation git pullcrée des problèmes? Quels problèmes cela crée-t-il? Existe-t-il un meilleur moyen de mettre à jour un référentiel git?

Richard Hansen
la source
53
@ j.karlsson: meta.stackexchange.com/questions/17463/…
Richard Hansen
8
Ou vous pouvez simplement git pull --rebaseet définir cette stratégie par défaut pour les nouvelles branches git config branch.autosetuprebase
knoopx
4
knoopx a raison, en ajoutant un --rebaseindicateur pour git pullsynchroniser le local avec le distant, puis rejoue vos modifications locales en plus du local mis à jour. Ensuite, lorsque vous appuyez sur, tout ce que vous faites est d'ajouter vos nouveaux validations à la fin de la télécommande. Assez simple.
Heath Lilley
4
Merci @BenMcCormick. Je l'avais déjà fait, mais la discussion concernant la validité de la question semble avoir lieu dans ces commentaires sous la question. Et je pense que poser une question pour créer une plate-forme pour présenter votre opinion personnelle car le fait n'est pas à quoi sert vraiment la structure de questions et réponses de SO.
mcv
4
@RichardHansen, cela semble juste être un moyen de tromper le système de points, en particulier avec votre réponse ayant une différence de ton si drastique et un intervalle de temps si court. En utilisant votre modèle de Q&R, nous pourrions tous simplement poser des questions et y répondre nous-mêmes en utilisant nos connaissances précédentes. À ce stade, vous devriez simplement envisager d'écrire un article de blog car cela est beaucoup plus approprié. Un Q&A recherche spécifiquement les connaissances des autres. Un article de blog expose le vôtre.
Josh Brown

Réponses:

546

Sommaire

Par défaut, git pullcrée des validations de fusion qui ajoutent du bruit et de la complexité à l'historique du code. De plus, pullil est facile de ne pas penser à la façon dont vos modifications pourraient être affectées par les modifications entrantes.

La git pullcommande est sûre tant qu'elle n'effectue que des fusions rapides. Si git pullest configuré pour effectuer uniquement des fusions d'avance rapide et lorsqu'une fusion d'avance rapide n'est pas possible, Git se terminera avec une erreur. Cela vous donnera l'occasion d'étudier les validations entrantes, de réfléchir à la façon dont elles pourraient affecter vos validations locales et de décider de la meilleure solution (fusionner, rebaser, réinitialiser, etc.).

Avec Git 2.0 et plus récent, vous pouvez exécuter:

git config --global pull.ff only

pour modifier le comportement par défaut pour une avance rapide uniquement. Avec les versions de Git entre 1.6.6 et 1.9.x, vous devrez prendre l'habitude de taper:

git pull --ff-only

Cependant, avec toutes les versions de Git, je recommande de configurer un git upalias comme celui-ci:

git config --global alias.up '!git remote update -p; git merge --ff-only @{u}'

et en utilisant git upau lieu de git pull. Je préfère cet alias à git pull --ff-onlyparce que:

  • il fonctionne avec toutes les versions (non anciennes) de Git,
  • il récupère toutes les branches en amont (pas seulement la branche sur laquelle vous travaillez actuellement), et
  • il nettoie les anciennes origin/*branches qui n'existent plus en amont.

Problèmes avec git pull

git pulln'est pas mauvais s'il est utilisé correctement. Plusieurs modifications récentes de Git ont facilité son utilisation git pull, mais malheureusement le comportement par défaut d'un plain git pullpose plusieurs problèmes:

  • il introduit des non-linéarités inutiles dans l'histoire
  • il est facile de réintroduire accidentellement des commits qui ont été intentionnellement rebasés en amont
  • il modifie votre répertoire de travail de manière imprévisible
  • suspendre ce que vous faites pour revoir le travail de quelqu'un d'autre est ennuyeux avec git pull
  • il est difficile de rebaser correctement sur la branche distante
  • il ne nettoie pas les branches qui ont été supprimées dans le référentiel distant

Ces problèmes sont décrits plus en détail ci-dessous.

Histoire non linéaire

Par défaut, la git pullcommande équivaut à l'exécution git fetchsuivie de git merge @{u}. S'il y a des validations non poussées dans le référentiel local, la partie de fusion de git pullcrée une validation de fusion.

Les commits de fusion n'ont rien de intrinsèquement mauvais, mais ils peuvent être dangereux et doivent être traités avec respect:

  • Les validations de fusion sont intrinsèquement difficiles à examiner. Pour comprendre ce que fait une fusion, vous devez comprendre les différences pour tous les parents. Un diff conventionnel ne transmet pas bien cette information multidimensionnelle. En revanche, une série de validations normales est facile à examiner.
  • La résolution des conflits de fusion est délicate et les erreurs restent souvent non détectées pendant une longue période car les validations de fusion sont difficiles à contrôler.
  • Les fusions peuvent discrètement remplacer les effets des validations régulières. Le code n'est plus la somme des validations incrémentielles, ce qui conduit à des malentendus sur ce qui a réellement changé.
  • Les validations de fusion peuvent perturber certains schémas d'intégration continue (par exemple, construire automatiquement uniquement le chemin du premier parent en vertu de la convention supposée que les seconds parents pointent vers des travaux incomplets en cours).

Bien sûr, il y a un temps et un lieu pour les fusions, mais comprendre quand les fusions doivent et ne doivent pas être utilisées peut améliorer l'utilité de votre référentiel.

Notez que le but de Git est de faciliter le partage et la consommation de l'évolution d'une base de code, et non d'enregistrer précisément l'historique exactement tel qu'il s'est déroulé. (Si vous êtes en désaccord, considérez la rebasecommande et pourquoi elle a été créée.) Les validations de fusion créées par git pullne transmettent pas de sémantique utile aux autres: elles disent simplement que quelqu'un d'autre a poussé vers le référentiel avant que vous ayez terminé vos modifications. Pourquoi ces fusions sont-elles commises si elles n'ont pas de sens pour les autres et pourraient être dangereuses?

Il est possible de configurer git pullpour rebaser au lieu de fusionner, mais cela a aussi des problèmes (discuté plus loin). Au lieu de cela, git pulldoit être configuré pour effectuer uniquement des fusions à avance rapide.

Réintroduction des engagements remaniés

Supposons que quelqu'un rebase une branche et que la force la pousse. Cela ne devrait généralement pas se produire, mais c'est parfois nécessaire (par exemple, pour supprimer un fichier journal de 50 Go qui a été accidentellement validé et poussé). La fusion effectuée par git pullfusionnera la nouvelle version de la branche en amont dans l'ancienne version qui existe toujours dans votre référentiel local. Si vous poussez le résultat, les fourches et les torches commenceront à venir.

Certains diront que le vrai problème est de forcer les mises à jour. Oui, il est généralement conseillé d'éviter autant que possible les poussées de force, mais elles sont parfois inévitables. Les développeurs doivent être prêts à gérer les mises à jour forcées, car elles se produisent parfois. Cela signifie ne pas fusionner aveuglément dans les anciens commits via un ordinaire git pull.

Modifications du répertoire de travail surprise

Il n'y a aucun moyen de prédire à quoi ressemblera le répertoire de travail ou l'index jusqu'à ce qu'il git pullsoit terminé. Il peut y avoir des conflits de fusion que vous devez résoudre avant de pouvoir faire quoi que ce soit d'autre, il peut introduire un fichier journal de 50 Go dans votre répertoire de travail parce que quelqu'un l'a poussé accidentellement, il peut renommer un répertoire dans lequel vous travaillez, etc.

git remote update -p(ou git fetch --all -p) vous permet de consulter les validations des autres avant de décider de fusionner ou de rebaser, ce qui vous permet d'élaborer un plan avant de prendre des mesures.

Difficulté à revoir les engagements des autres

Supposons que vous êtes en train de faire des changements et que quelqu'un d'autre veuille que vous passiez en revue certains commits qu'ils viennent de pousser. git pullL'opération de fusion (ou de rebase) de modifie le répertoire de travail et l'index, ce qui signifie que votre répertoire de travail et votre index doivent être propres.

Vous pourriez utiliser git stashet ensuite git pull, mais que faites-vous lorsque vous avez terminé la révision? Pour revenir à l'endroit où vous vous trouviez, vous devez annuler la fusion créée par git pullet appliquer la cachette.

git remote update -p(ou git fetch --all -p) ne modifie pas le répertoire de travail ou l'index, il est donc sûr de l'exécuter à tout moment, même si vous avez des modifications par étapes et / ou par étapes. Vous pouvez suspendre ce que vous faites et revoir le commit de quelqu'un d'autre sans vous soucier de cacher ou de terminer le commit sur lequel vous travaillez. git pullne vous donne pas cette flexibilité.

Rebasage sur une branche distante

Un modèle d'utilisation Git courant consiste à effectuer un git pullpour apporter les dernières modifications, suivi d'un git rebase @{u}pour éliminer le commit de fusion git pullintroduit. Il est assez courant que Git a quelques options de configuration pour réduire ces deux étapes à une seule étape en disant git pulld'effectuer un rebasage au lieu d'une fusion (voir branch.<branch>.rebase, branch.autosetuprebaseet pull.rebaseoptions).

Malheureusement, si vous avez un commit de fusion non poussé que vous souhaitez conserver (par exemple, un commit fusionnant une branche de fonctionnalité poussée dans master), ni un rebase-pull ( git pullavec branch.<branch>.rebasedéfini sur true) ni un merge-pull (le git pullcomportement par défaut ) suivi d'un rebase fonctionnera. En effet, git rebaseélimine les fusions (il linéarise le DAG) sans --preserve-mergesoption. L'opération de rebase-pull ne peut pas être configurée pour conserver les fusions, et un merge-pull suivi d'un git rebase -p @{u}ne éliminera pas la fusion provoquée par le merge-pull. Mise à jour: Git v1.8.5 ajouté git pull --rebase=preserveet git config pull.rebase preserve. Celles-ci entraînent git pullune opération git rebase --preserve-mergesaprès avoir récupéré les validations en amont. (Merci à funkaster pour le heads-up!)

Nettoyage des branches supprimées

git pullne taille pas les branches de suivi à distance correspondant aux branches qui ont été supprimées du référentiel distant. Par exemple, si quelqu'un supprime une branche foodu référentiel distant, vous verrez toujours origin/foo.

Cela conduit les utilisateurs à ressusciter accidentellement des branches tuées car ils pensent qu'ils sont toujours actifs.

Une meilleure alternative: utiliser git upau lieu degit pull

Au lieu de git pull, je recommande de créer et d'utiliser l' git upalias suivant :

git config --global alias.up '!git remote update -p; git merge --ff-only @{u}'

Cet alias télécharge toutes les dernières validations de toutes les branches en amont (élagage des branches mortes) et tente d'avancer rapidement la branche locale vers la dernière validation de la branche en amont. En cas de succès, il n'y avait pas de commits locaux, il n'y avait donc aucun risque de conflit de fusion. L'avance rapide échouera s'il y a des validations locales (non poussées), ce qui vous donne la possibilité de revoir les validations en amont avant de prendre des mesures.

Cela modifie toujours votre répertoire de travail de manière imprévisible, mais uniquement si vous n'avez aucun changement local. Contrairement à git pull, git upne vous déposera jamais à une invite vous demandant de résoudre un conflit de fusion.

Une autre option: git pull --ff-only --all -p

Ce qui suit est une alternative à l' git upalias ci-dessus :

git config --global alias.up 'pull --ff-only --all -p'

Cette version de git upa le même comportement que l' git upalias précédent , sauf:

  • le message d'erreur est un peu plus cryptique si votre branche locale n'est pas configurée avec une branche en amont
  • il s'appuie sur une fonctionnalité non documentée (l' -pargument, qui est passé à fetch) qui pourrait changer dans les futures versions de Git

Si vous utilisez Git 2.0 ou une version plus récente

Avec Git 2.0 et plus récent, vous pouvez configurer git pullpour ne faire que des fusions à avance rapide par défaut:

git config --global pull.ff only

Cela provoque un git pullcomportement similaire git pull --ff-only, mais il ne récupère toujours pas toutes les validations en amont ni ne nettoie les anciennes origin/*branches, donc je préfère toujours git up.

Richard Hansen
la source
6
@brianz: git remote update -pest équivalent à git fetch --all -p. Je suis l'habitude de taper git remote update -pparce que une fois fetchne pas l' -poption. Concernant l'interligne !, voir la description de alias.*dans git help config. Il dit: "Si l'extension d'alias est précédée d'un point d'exclamation, elle sera traitée comme une commande shell."
Richard Hansen
13
Git 2.0 ajoute une pull.ffconfiguration qui semble réaliser la même chose, sans alias.
Danny Thomas
51
Certaines des raisons retentissent comme "tirer peut causer des problèmes quand d'autres font des trucs fous". Non, ce sont des trucs fous comme rebaser un commit à partir d'un dépôt en amont qui pose problème. Le rebase IMO n'est sûr que lorsque vous le faites localement sur un commit qui n'a pas encore été poussé. Comme, par exemple, lorsque vous tirez avant de pousser, le rebasage des validations locales aide à garder votre historique linéaire (bien que l'historique linéaire ne soit pas si grave). Pourtant, cela git upressemble à une alternative intéressante.
mcv
16
La plupart de vos points sont parce que vous faites quelque chose de mal: vous essayez de revoir le code dans votre propre branche de travail . Ce n'est pas une bonne idée, il suffit de créer une nouvelle branche, de tirer --rebase = préserver puis de lancer cette branche (ou de la fusionner si vous le souhaitez).
funkaster
5
Le point de @ funkaster ici a beaucoup de sens, en particulier en ce qui concerne: "Difficulty Reviewing Other People Commits". Ce n'est pas le flux d'avis que la plupart des utilisateurs de Git utilisent, c'est quelque chose que je n'ai jamais vu recommandé nulle part et c'est la cause de tout le travail supplémentaire inutile décrit sous l'en-tête, non git pull.
Ben Regenspan
195

Ma réponse, tirée de la discussion qui a surgi sur HackerNews:

Je me sens tenté de répondre à la question en utilisant la loi Betteridge des titres: pourquoi est-ce git pullconsidéré comme dangereux? Ça ne l'est pas.

  • Les non-linéarités ne sont pas intrinsèquement mauvaises. S'ils représentent l'histoire réelle, ils sont ok.
  • La réintroduction accidentelle de commits rebasés en amont est le résultat d'une réécriture erronée de l'histoire en amont. Vous ne pouvez pas réécrire l'historique lorsque l'historique est répliqué sur plusieurs référentiels.
  • La modification du répertoire de travail est un résultat attendu; d'une utilité discutable, à savoir face au comportement de hg / monotone / darcs / other_dvcs_predating_git, mais encore une fois pas intrinsèquement mauvais.
  • Une pause pour examiner le travail des autres est nécessaire pour une fusion, et est encore une fois un comportement attendu sur git pull. Si vous ne souhaitez pas fusionner, vous devez utiliser git fetch. Encore une fois, c'est une idiosyncrasie de git par rapport aux dvcs populaires précédents, mais c'est un comportement attendu et pas intrinsèquement mauvais.
  • Il est difficile de rebaser contre une branche distante. Ne réécrivez pas l'historique, sauf si vous en avez absolument besoin. Je ne peux pas pour la vie de moi comprendre cette poursuite d'une (fausse) histoire linéaire
  • Ne pas nettoyer les branches, c'est bien. Chaque repo sait ce qu'il veut garder. Git n'a aucune notion de relations maître-esclave.
Sérgio Carvalho
la source
13
Je suis d'accord. Il n'y a rien de fondamentalement nocif git pull. Cependant, cela pourrait entrer en conflit avec certaines pratiques nuisibles, comme vouloir réécrire l'histoire plus que ce qui est strictement nécessaire. Mais git est flexible, donc si vous voulez l'utiliser d'une manière différente, faites-le par tous les moyens. Mais c'est parce que vous (enfin, @Richard Hansen) voulez faire quelque chose d'inhabituel dans git, et non pas parce que git pullc'est dangereux.
mcv
28
Je ne pourrais pas être plus d'accord. Les gens préconisent git rebaseet considèrent git pullnuisibles? Vraiment?
Victor Moroz
10
Ce serait bien de voir quelqu'un créer un graphique, avec la moralité comme axe, et classer les commandes git comme bonnes, mauvaises ou quelque part entre les deux. Ce graphique différerait entre les développeurs, mais il en dirait beaucoup sur une utilisation de git.
michaelt
5
Mon problème avec git pullsans l' --rebaseoption est la direction de fusion qu'elle crée. Lorsque vous regardez la différence, toutes les modifications de cette fusion appartiennent désormais à la personne qui a tiré, plutôt qu'à la personne qui a apporté les modifications. J'aime un flux de travail où la fusion est réservée à deux branches distinctes (A -> B), donc la validation de la fusion est claire sur ce qui a été introduit, et le rebasage est réservé à la mise à jour sur la même branche (distant A -> local A )
Craig Kochis
4
Alors, qu'est-ce que ça vous fait de savoir si quelqu'un a fait un pull juste quelques secondes avant quelqu'un d'autre ou l'inverse? Je pense que ce n'est que du bruit et ne fait qu'obscurcir l'histoire vraiment pertinente. Cela diminue même la valeur de l'histoire. Une bonne histoire doit être a) propre et b) avoir réellement l'histoire importante.
David Ongaro
26

Il n'est pas considéré comme dangereux si vous utilisez correctement Git. Je vois comment cela vous affecte négativement compte tenu de votre cas d'utilisation, mais vous pouvez éviter les problèmes simplement en ne modifiant pas l'historique partagé.

Hunt Burdick
la source
10
Pour développer cela: si tout le monde travaille sur sa propre branche (ce qui, à mon avis, est la bonne façon d'utiliser git), ce git pulln'est pas un problème. Branchement dans git est bon marché.
AlexQueue
18

La réponse acceptée revendique

L'opération de rebase-pull ne peut pas être configurée pour conserver les fusions

mais à partir de Git 1.8.5 , qui postdate cette réponse, vous pouvez le faire

git pull --rebase=preserve

ou

git config --global pull.rebase preserve

ou

git config branch.<name>.rebase preserve

Les docs disent

Lorsque vous preserve,passez également --preserve-mergesà «git rebase» afin que les validations de fusion validées localement ne soient pas aplaties en exécutant «git pull».

Cette discussion précédente contient des informations et des diagrammes plus détaillés: git pull --rebase --preserve-merges . Cela explique également pourquoi git pull --rebase=preserven'est pas le même que git pull --rebase --preserve-merges, ce qui ne fait pas la bonne chose.

Cette autre discussion précédente explique ce que fait réellement la variante conserve-fusionne de rebase, et comment elle est beaucoup plus complexe qu'un rebase ordinaire: que fait exactement "rebase --preserve-merges" de git (et pourquoi?)

Marc Liyanage
la source
0

Si vous allez dans l'ancien dépôt git, git l'alias qu'ils suggèrent est différent. https://github.com/aanand/git-up

git config --global alias.up 'pull --rebase --autostash'

Cela fonctionne parfaitement pour moi.

Nathan Redblur
la source