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.