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
il est facile de ne pas penser à la façon dont vos modifications pourraient être affectées par les modifications entrantes.
La git pull
commande est sûre tant qu'elle n'effectue que des fusions rapides. Si git pull
est 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 up
alias comme celui-ci:
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:
- 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 pull
n'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 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
- 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 pull
commande équivaut à l'exécution git fetch
suivie 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 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 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 rebase
commande et pourquoi elle a été créée.) Les validations de fusion créées par git pull
ne 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 pull
pour rebaser au lieu de fusionner, mais cela a aussi des problèmes (discuté plus loin). Au lieu de cela, git pull
doit ê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 pull
fusionnera 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 pull
soit 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 pull
L'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 stash
et 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 pull
et 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 pull
ne vous donne pas cette flexibilité.
Rebasage sur une branche distante
Un modèle d'utilisation Git courant consiste à effectuer un git pull
pour apporter les dernières modifications, suivi d'un git rebase @{u}
pour éliminer le commit de fusion git pull
introduit. Il est assez courant que Git a quelques options de configuration pour réduire ces deux étapes à une seule étape en disant git pull
d'effectuer un rebasage au lieu d'une fusion (voir branch.<branch>.rebase
, branch.autosetuprebase
et pull.rebase
options).
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 pull
avec branch.<branch>.rebase
défini sur true
) ni un merge-pull (le git pull
comportement par défaut ) suivi d'un rebase fonctionnera. En effet, git rebase
élimine les fusions (il linéarise le DAG) sans --preserve-merges
option. 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=preserve
et git config pull.rebase preserve
. Celles-ci entraînent git pull
une opération git rebase --preserve-merges
après avoir récupéré les validations en amont. (Merci à funkaster pour le heads-up!)
Nettoyage des branches supprimées
git pull
ne 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 foo
du 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 up
au lieu degit pull
Au lieu de git pull
, je recommande de créer et d'utiliser l' git up
alias 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 up
ne 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 up
alias ci-dessus :
git config --global alias.up 'pull --ff-only --all -p'
Cette version de git up
a le même comportement que l' git up
alias 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'
-p
argument, 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 pull
pour ne faire que des fusions à avance rapide par défaut:
git config --global pull.ff only
Cela provoque un git pull
comportement 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
.