web-dev-qa-db-fra.com

Dans quels cas "git pull" peut-il être dangereux?

J'ai un collègue qui prétend que git pull est nocif et se fâche lorsque quelqu'un l'utilise.

La commande git pull semble être le moyen canonique de mettre à jour votre référentiel local. L'utilisation de git pull crée-t-elle des problèmes? Quels problèmes crée-t-il? Existe-t-il un meilleur moyen de mettre à jour un référentiel git?

402
Richard Hansen

Sommaire

Par défaut, git pull crée des validations de fusion qui ajoutent du bruit et de la complexité à l'historique du code. De plus, pull permet de ne pas trop penser aux conséquences que vos modifications pourraient avoir sur vos modifications.

La commande git pull est sécurisée tant qu'elle effectue uniquement des fusions en avance rapide. Si git pull est configuré pour effectuer uniquement des fusions en avance rapide et lorsqu'une fusion en avance rapide n'est pas possible, alors Git se fermera avec une erreur. Cela vous donnera l'occasion d'étudier les commits entrants, de réfléchir à la manière dont ils pourraient affecter vos commits locaux et de décider du meilleur plan d'action (fusion, refonte, réinitialisation, 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 uniquement en avance rapide. Avec les versions de Git comprises 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 vous recommande de configurer un alias git up comme ceci:

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

et en utilisant git up au lieu de git pull. Je préfère cet alias à git pull --ff-only parce que:

  • cela 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 pull n'est pas mauvais s'il est utilisé correctement. Plusieurs modifications récentes de Git ont rendu plus facile l'utilisation de git pull correctement, mais malheureusement, le comportement par défaut d'un simple git pull pose 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
  • interrompre ce que vous faites pour examiner le travail de quelqu'un d'autre est agaçant avec git pull
  • il est difficile de rebaser correctement sur la branche distante
  • il ne nettoie pas les branches supprimées dans le dépôt distant

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

Histoire non linéaire

Par défaut, la commande git pull équivaut à exécuter git fetch suivi de git merge @{u}. S'il existe des validations non traitées dans le référentiel local, la partie fusion de git pull cré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 commits de fusion sont par nature 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, il est facile de passer en revue une série d’engagements normaux.
  • La résolution des conflits de fusion est délicate et les erreurs restent souvent non détectées car les commits de fusion sont difficiles à analyser.
  • Les fusions peuvent remplacer les effets des commits réguliers. Le code n'est plus la somme des commits incrémentiels, ce qui entraîne des malentendus sur ce qui a réellement changé.
  • Les commits de fusion peuvent perturber certains schémas d'intégration continue (par exemple, ne construisez automatiquement que le chemin du premier parent selon la convention supposée, selon laquelle les seconds parents indiquent des travaux en cours incomplets).

Bien sûr, il y a un moment et un lieu pour les fusions, mais comprendre quand et où fusionner devrait ou ne devrait pas être utilisé peut améliorer l'utilité de votre référentiel.

Notez que le but de Git est de faciliter le partage et l'utilisation de l'évolution d'une base de code, et non d'enregistrer avec précision l'historique tel qu'il s'est déroulé. (Si vous êtes en désaccord, considérez la commande rebase et la raison de sa création.) Les validations de fusion créées par git pull ne transmettent pas de sémantique utile aux autres. Elles indiquent simplement que quelqu'un d'autre est arrivé. Push to the repository avant d'avoir terminé vos modifications. Pourquoi ces commits de fusion s’ils n’ont pas de sens pour les autres et peuvent être dangereux?

Il est possible de configurer git pull de manière à rebaser au lieu de fusionner, mais cela pose également des problèmes (voir plus loin). Au lieu de cela, git pull devrait être configuré pour effectuer uniquement des fusions à avance rapide.

Réintroduction des commits refondus

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

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

Modification 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 que git pull soit terminé. Il peut y avoir des conflits de fusion que vous devez résoudre avant de pouvoir faire quoi que ce soit, cela pourrait introduire un fichier journal 50GiB dans votre répertoire de travail parce que quelqu'un l'a poussé accidentellement, cela pourrait renommer un répertoire dans lequel vous travaillez, etc.

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

Difficulté à réviser les engagements d'autres personnes

Supposons que vous soyez en train de faire des changements et que quelqu'un d'autre veuille que vous examiniez certains commits qu'ils ont simplement envoyés. L'opération de fusion (ou de refonte) de git pull 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 pouvez utiliser git stash puis git pull, mais que faites-vous lorsque vous avez terminé votre révision? Pour revenir à votre position actuelle, vous devez annuler la fusion créée par git pull et appliquer la stash.

git remote update -p (ou git fetch --all -p) ne modifie pas le répertoire de travail ni l'index, de sorte que son exécution est sûre à tout moment, même si vous avez effectué des modifications planifiées et/ou non mises en scène. Vous pouvez mettre en pause ce que vous faites et consulter le commit de quelqu'un d'autre sans vous soucier de cacher ou de terminer le commit sur lequel vous travaillez. git pull ne vous donne pas cette souplesse.

Se baser sur une branche distante

Un modèle d'utilisation courant de Git consiste à faire un git pull pour intégrer les dernières modifications suivies d'un git rebase @{u} pour éliminer la validation de fusion introduite par git pull. Il est assez courant que Git ait quelques options de configuration pour réduire ces deux étapes à une seule étape en demandant à git pull d’effectuer une rebase au lieu d’une fusion (voir le branch.<branch>.rebase, branch.autosetuprebase et pull.rebase options).

Malheureusement, si vous souhaitez conserver une validation de fusion non validée (par exemple, une validation fusionnant une branche de fonctionnalité injectée dans master), ni un rebase-pull (git pull avec branch.<branch>.rebase set to true) ni une fusion-extraction (le comportement par défaut de git pull _) suivie d'un rebase fonctionnera. En effet, git rebase élimine les fusions (il linéarise le DAG) sans l'option --preserve-merges. L'opération d'extraction de base ne peut pas être configurée pour conserver les fusions et une fusion d'extraction suivie d'un git rebase -p @{u} n'éliminera pas la fusion provoquée par l'extraction de fusion. Mise à jour: Git v1.8.5 ajouté git pull --rebase=preserve et git config pull.rebase preserve. Ceci provoque git pull à faire git rebase --preserve-merges après avoir récupéré les commits en amont. (Merci à funkaster pour le heads-up!)

Nettoyage des branches supprimées

git pull ne supprime pas les branches de suivi distantes correspondant aux branches supprimées du référentiel distant. Par exemple, si quelqu'un supprime la branche foo du référentiel distant, vous verrez toujours Origin/foo.

Cela conduit les utilisateurs à ressusciter accidentellement des branches mortes, car ils pensent être toujours actifs.

Une meilleure alternative: utilisez git up au lieu de git pull

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

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

Cet alias télécharge tous les derniers commits de toutes les branches en amont (élagage des branches mortes) et tente d'avancer rapidement la branche locale vers la dernière validation sur la branche en amont. En cas de succès, il n'y a pas d'engagement local, il n'y a donc pas de risque de fusion. L'avance rapide échouera s'il existe des validations locales (non impulsées), ce qui vous donne l'occasion d'examiner 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 aucune modification locale. Contrairement à git pull, git up ne vous laissera jamais tomber dans une invite qui vous demandera de résoudre un conflit de fusion.

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

Voici une alternative à l'alias git up ci-dessus:

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

Cette version de git up a le même comportement que le précédent alias git up, sauf que:

  • le message d'erreur est un peu plus crypté si votre branche locale n'est pas configurée avec une branche en amont
  • il repose sur une fonctionnalité non documentée (l'argument -p, transmise à fetch), susceptible de changer dans les futures versions de Git

Si vous utilisez Git 2.0 ou plus récent

Avec Git 2.0 et les versions plus récentes, vous pouvez configurer git pull pour n'effectuer que des fusions en avance rapide par défaut:

git config --global pull.ff only

Cela fait que git pull se comporte comme git pull --ff-only, mais il ne récupère toujours pas tous les commits en amont ni ne nettoie les anciennes branches Origin/*, aussi je préfère toujours git up.

542
Richard Hansen

Ma réponse, tirée de la discussion qui est survenue sur HackerNews:

Je suis tenté de simplement répondre à la question en utilisant la loi de Betteridge: Pourquoi est-ce que _git pull_ est considéré comme dangereux? Ce n'est pas.

  • Les non-linéarités ne sont pas intrinsèquement mauvaises. S'ils représentent l'histoire réelle, ils vont bien.
  • La réintroduction accidentelle de commits rebasés en amont est le résultat d'une réécriture erronée de l'historique en amont. Vous ne pouvez pas réécrire l’historique lorsque celui-ci est répliqué dans plusieurs dépôts.
  • La modification du répertoire de travail est un résultat attendu. d’utilité discutable, à savoir face au comportement de hg/monotone/darcs/other_dvcs_predating_git, mais là encore pas intrinsèquement mauvais.
  • Une pause pour examiner le travail des autres est nécessaire pour une fusion et est à nouveau un comportement attendu sur git pull. Si vous ne voulez pas fusionner, vous devriez utiliser git fetch. Encore une fois, c’est une idiosyncrasie de git par rapport aux précédents dvcs populaires, mais c’est un comportement attendu et pas intrinsèquement mauvais.
  • Il est bon d’avoir du mal à rebaser une branche distante. Ne réécrivez pas l’histoire à moins d’en avoir absolument besoin. Je ne peux pas pour la vie de comprendre cette poursuite d'une (fausse) histoire linéaire
  • Ne pas nettoyer les branches, c'est bien. Chaque dépôt sait ce qu'il veut. Git n'a aucune notion de relation maître-esclave.
194
Sérgio Carvalho

Ce n'est pas considéré comme dangereux si vous utilisez correctement Git. Je vois en quoi 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é.

26
Hunt Burdick

La réponse acceptée prétend

L'opération 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

Le docs dire

Lorsque preserve, transmet également --preserve-merges à 'git rebase' afin que les commits de fusion validés localement ne soient pas aplatis par l'exécution de 'git pull'.

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

Cette autre discussion précédente explique ce que fait la variante de préservation et de fusion de la base, et en quoi elle est beaucoup plus complexe que celle de base: Que fait exactement la "base" de réserve - fusion de git) (et pourquoi?). )

17
Marc Liyanage

Si vous allez dans l'ancien dépôt git , git up , le pseudonyme suggéré par ceux-ci est différent. https://github.com/aanand/git-up

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

Cela fonctionne parfaitement pour moi.

0
Nathan Redblur