Je comprends le scénario présenté dans Pro Git sur les dangers du rebasage . L'auteur vous indique essentiellement comment éviter les commits dupliqués:
Ne rebasez pas les commits que vous avez poussés vers un référentiel public.
Je vais vous parler de ma situation particulière car je pense que cela ne correspond pas exactement au scénario Pro Git et je me retrouve toujours avec des commits dupliqués.
Disons que j'ai deux succursales distantes avec leurs homologues locaux:
origin/master origin/dev
| |
master dev
Les quatre branches contiennent les mêmes commits et je vais commencer le développement dans dev
:
origin/master : C1 C2 C3 C4
master : C1 C2 C3 C4
origin/dev : C1 C2 C3 C4
dev : C1 C2 C3 C4
Après quelques validations, je pousse les changements à origin/dev
:
origin/master : C1 C2 C3 C4
master : C1 C2 C3 C4
origin/dev : C1 C2 C3 C4 C5 C6 # (2) git push
dev : C1 C2 C3 C4 C5 C6 # (1) git checkout dev, git commit
Je dois revenir à master
pour faire une solution rapide:
origin/master : C1 C2 C3 C4 C7 # (2) git push
master : C1 C2 C3 C4 C7 # (1) git checkout master, git commit
origin/dev : C1 C2 C3 C4 C5 C6
dev : C1 C2 C3 C4 C5 C6
Et revenons à dev
je rebase les modifications pour inclure la solution rapide dans mon développement actuel:
origin/master : C1 C2 C3 C4 C7
master : C1 C2 C3 C4 C7
origin/dev : C1 C2 C3 C4 C5 C6
dev : C1 C2 C3 C4 C7 C5' C6' # git checkout dev, git rebase master
Si j'affiche l'historique des commits avec GitX / gitk, je remarque qu'il origin/dev
contient maintenant deux commits identiques C5'
et C6'
qui sont différents de Git. Maintenant, si je pousse les changements, origin/dev
voici le résultat:
origin/master : C1 C2 C3 C4 C7
master : C1 C2 C3 C4 C7
origin/dev : C1 C2 C3 C4 C5 C6 C7 C5' C6' # git push
dev : C1 C2 C3 C4 C7 C5' C6'
Peut-être que je ne comprends pas parfaitement l'explication dans Pro Git, alors j'aimerais savoir deux choses:
- Pourquoi Git duplique-t-il ces commits lors du rebasage? Y a-t-il une raison particulière de faire cela au lieu de simplement postuler
C5
etC6
aprèsC7
? - Comment puis-je éviter cela? Serait-il sage de le faire?
origin/dev
. Lorsqu'ildev
est rebasé, son historique est modifié (C5 / C6 temporairement supprimé et réappliqué après C7). La modification de l'historique des dépôts poussés est généralement une très mauvaise idée ™ à moins que vous ne sachiez ce que vous faites. Dans ce cas simple, le problème pourrait être résolu en effectuant une poussée forcée dedev
àorigin/dev
après le rebase et en informant toute autre personne travaillant sur leorigin/dev
fait qu'elle est probablement sur le point de passer une mauvaise journée. La meilleure réponse, encore une fois, est "ne faites pas ça ... utilisez plutôt la fusion"Réponse courte
Vous avez omis le fait que vous avez exécuté
git push
, obtenu l'erreur suivante, puis exécutégit pull
:Bien que Git essaie d'être utile, ses conseils «git pull» ne sont probablement pas ce que vous voulez faire .
Si vous êtes:
git push --force
pour mettre à jour la télécommande avec vos commits post-rebase ( selon la réponse de user4405677 ).git rebase
en premier lieu. Pour mettredev
à jour avec les modifications demaster
, vous devriez, au lieu de courirgit rebase master dev
, exécutergit merge master
pendant que vous êtes allumédev
( selon la réponse de Justin ).Une explication un peu plus longue
Chaque hachage de commit dans Git est basé sur un certain nombre de facteurs, dont l'un est le hachage du commit qui le précède.
Si vous réorganisez les validations, vous modifierez les hachages de validation; le rebasage (lorsqu'il fait quelque chose) changera les hachages de validation. Avec cela, le résultat de l'exécution
git rebase master dev
, oùdev
est désynchronisé avecmaster
, créera de nouveaux commits (et donc des hachages) avec le même contenu que ceux surdev
mais avec les commitsmaster
insérés avant eux.Vous pouvez vous retrouver dans une situation comme celle-ci de plusieurs manières. Je peux penser à deux façons:
master
lesquels vous souhaitez baser votredev
travaildev
qui ont déjà été poussés vers une télécommande, que vous procédez ensuite à la modification (reformuler les messages de commit, réorganiser les commits, squash commits, etc.)Comprenons mieux ce qui s'est passé - voici un exemple:
Vous avez un référentiel:
Vous procédez ensuite à la modification des commits.
(C'est là que vous devrez me croire sur parole: il existe plusieurs façons de modifier les commits dans Git. Dans cet exemple, j'ai changé l'heure de
C3
, mais vous insérez de nouveaux commits, modifiez les messages de commit, réorganisez les commits, squashing s'engage ensemble, etc.)C'est là qu'il est important de noter que les hachages de validation sont différents. C'est un comportement attendu puisque vous avez changé quelque chose (n'importe quoi) à leur sujet. C'est bon, MAIS:
Essayer de pousser vous montrera une erreur (et un indice que vous devriez exécuter
git pull
).Si nous exécutons
git pull
, nous voyons ce journal:Ou, montré d'une autre manière:
Et maintenant, nous avons des commits en double localement. Si nous
git push
devions exécuter, nous les enverrions au serveur.Pour éviter d'arriver à ce stade, nous aurions pu courir
git push --force
(où nous avons plutôt courugit pull
). Cela aurait envoyé nos commits avec les nouveaux hachages au serveur sans problème. Pour résoudre le problème à ce stade, nous pouvons réinitialiser avant d'exécutergit pull
:Regardez le reflog (
git reflog
) pour voir ce que le hachage commettras était avant nous avons courugit pull
.Ci-dessus, nous voyons que
ba7688a
c'était le commit auquel nous étions avant de courirgit pull
. Avec ce hachage de validation en main, nous pouvons revenir à cela (git reset --hard ba7688a
) et ensuite exécutergit push --force
.Et nous avons terminé.
Mais attendez, j'ai continué à baser le travail sur les commits dupliqués
Si vous n'avez pas remarqué que les commits étaient dupliqués et que vous continuiez à travailler sur des commits en double, vous avez vraiment fait un gâchis pour vous-même. La taille du désordre est proportionnelle au nombre de commits que vous avez au-dessus des doublons.
À quoi ça ressemble:
Ou, montré d'une autre manière:
Dans ce scénario, nous voulons supprimer les validations en double, mais conserver les validations que nous avons basées sur elles - nous souhaitons conserver C6 à C10. Comme pour la plupart des choses, il existe plusieurs façons de procéder:
Soit:
cherry-pick
chaque commit (C6 à C10 inclus) sur cette nouvelle branche, et traitez cette nouvelle branche comme canonique.git rebase --interactive $commit
, où$commit
est le commit avant les deux commits dupliqués 2 . Ici, nous pouvons carrément supprimer les lignes des doublons.1 Peu importe lequel des deux vous choisissez, l'un
ba7688a
ou l' autre2a2e220
fonctionne bien.2 Dans l'exemple, ce serait
85f59ab
.TL; DR
Définir
advice.pushNonFastForward
surfalse
:la source
git push
de--force-with-lease
nos jours car c'est un meilleur défautJe pense que vous avez sauté un détail important lors de la description de vos étapes. Plus précisément, votre dernière étape,
git push
sur le développement, vous aurait en fait généré une erreur, car vous ne pouvez normalement pas pousser les modifications non rapides.C'est ce que vous avez fait
git pull
avant la dernière poussée, qui a abouti à un commit de fusion avec C6 et C6 'comme parents, c'est pourquoi les deux resteront répertoriés dans le journal. Un format de journal plus joli aurait pu rendre plus évident le fait qu'il s'agit de branches fusionnées de commits dupliqués.Ou vous avez fait un
git pull --rebase
(ou sans explicite--rebase
si cela est impliqué par votre configuration) à la place, qui a récupéré les C5 et C6 d'origine dans votre développement local (et rebasé plus loin les suivants en de nouveaux hachages, C7 'C5' 'C6' ').Une façon de s'en sortir aurait pu être
git push -f
de forcer la poussée quand elle a donné l'erreur et d'effacer le C5 C6 de l'origine, mais si quelqu'un d'autre les avait également retirés avant de les effacer, vous auriez beaucoup plus de problèmes. En gros, tous ceux qui ont C5 C6 devraient faire des mesures spéciales pour s'en débarrasser. C'est exactement pourquoi ils disent que vous ne devriez jamais rebaser tout ce qui est déjà publié. C'est toujours faisable si la «publication» se fait au sein d'une petite équipe.la source
git pull
est cruciale. Votre recommandationgit push -f
, bien que dangereuse, est probablement ce que les lecteurs recherchent.git push --force
, juste pour voir ce que Git allait faire. J'ai beaucoup appris sur Git depuis lors et fait aujourd'huirebase
partie de mon flux de travail normal. Cependant, je le faisgit push --force-with-lease
pour éviter d'écraser le travail de quelqu'un d'autre.--force-with-lease
est un bon défaut, je vais également laisser un commentaire sous ma réponseJ'ai découvert que dans mon cas, ce problème était la conséquence d'un problème de configuration Git. (Impliquant l'extraction et la fusion)
Description du problème:
Sympthoms: Commits dupliqués sur la branche enfant après le rebase, impliquant de nombreuses fusions pendant et après le rebase.
Workflow: voici les étapes du workflow que j'effectuais:
Comme conséquences de ce workflow, duplication de tous les commits de "Feature-branch" depuis le rebase précédent ... :-(
Le problème était dû à l'attraction des modifications de la branche enfant avant le rebase. La configuration d'extraction par défaut de Git est "merge". Cela modifie les index des validations effectuées sur la branche enfant.
La solution: dans le fichier de configuration Git, configurez pull pour qu'il fonctionne en mode rebase:
J'espère que cela peut aider JN Grx
la source
Vous avez peut-être tiré d'une branche distante différente de votre actuelle. Par exemple, vous avez peut-être retiré de Master lorsque votre branche développe le suivi du développement. Git extraira consciencieusement des commits en double s'il est extrait d'une branche non suivie.
Si cela se produit, vous pouvez effectuer les opérations suivantes:
où
n == <number of duplicate commits that shouldn't be there.>
Ensuite, assurez-vous que vous tirez de la bonne branche, puis exécutez:
Tirer avec
--rebase
garantira que vous n'ajoutez pas de commits superflus qui pourraient brouiller l'historique des commits.Voici un peu de prise de main pour git rebase.
la source