Je comprends le scénario présenté dans Pro Git à propos de les risques de git rebase
. En gros, l’auteur vous explique comment éviter les commits en double:
Ne rebassez pas les commits que vous avez envoyés dans un référentiel public.
Je vais vous dire ma situation particulière parce que je pense que cela ne correspond pas exactement au scénario de Pro Git et que je finis toujours par avoir des commits en double.
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 commits, je renvoie les modifications à 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 apporter 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 reformule 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 que Origin/dev
contient maintenant deux commits identiques C5'
et C6'
qui sont différents de Git. Maintenant, si je pousse les modifications à Origin/dev
c'est 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 tout à fait l'explication dans Pro Git, alors j'aimerais savoir deux choses:
C5
et C6
après C7
?Vous ne devriez pas utiliser rebase ici, une simple fusion suffira. Le livre Pro Git que vous avez lié explique en gros cette situation. Le fonctionnement interne peut être légèrement différent, mais voici comment je le visualise:
C5
et C6
sont temporairement retirés de dev
C7
est appliqué à dev
C5
et C6
sont lus au-dessus de C7
, créant de nouveaux diffs et donc de nouveaux commitsDonc, dans votre branche dev
, C5
et C6
En réalité, ils n'existent plus: ils sont maintenant C5'
et C6'
. Lorsque vous appuyez sur Origin/dev
, git voit C5'
et C6'
comme nouveaux commet et les pointe vers la fin de l’histoire. En effet, si vous regardez les différences entre C5
et C5'
dans Origin/dev
, vous remarquerez que, même si le contenu est identique, les numéros de ligne sont probablement différents - ce qui rend le hachage du commit différent.
Je vais reformuler la règle de Pro Git: ne redéfinissez jamais les commits qui ont jamais existé ailleurs que dans votre référentiel local. Utilisez fusionner à la place.
Vous avez omis le fait que vous avez exécuté git Push
, Que vous avez rencontré l'erreur suivante et que vous avez ensuite exécuté git pull
:
To [email protected]:username/test1.git
! [rejected] dev -> dev (non-fast-forward)
error: failed to Push some refs to '[email protected]:username/test1.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git Push --help' for details.
Bien que Git ait essayé d’être utile, , son conseil de "tirage" n’est probablement pas ce que vous voulez faire .
Si vous êtes:
git Push --force
Pour mettre à jour la télécommande avec votre rebase commits ( selon la réponse de user4405677 ).git rebase
en premier lieu. Pour mettre à jour dev
avec les modifications de master
, vous devriez, au lieu d'exécuter git rebase master dev
, Exécuter git merge master
Avec dev
( selon la réponse de Justin ).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 commits, vous changerez les hachages de commits; le rebasement (quand il fait quelque chose) changera les hachages de commit. Avec cela, le résultat de l'exécution de git rebase master dev
, Où dev
n'est pas synchronisé avec master
, créera nouveau commits (et donc des hachages ) avec le même contenu que ceux sur dev
mais avec les commits sur master
insérés avant eux.
Vous pouvez vous retrouver dans une situation comme celle-ci de plusieurs façons. Je peux penser à deux choses:
master
sur lesquels vous voulez baser votre travail dev
dev
qui ont déjà été poussés vers une télécommande, que vous allez ensuite modifier (reformulation des messages de validation, des modifications de validation, des modifications de squash, etc.).Faisons mieux comprendre ce qui s'est passé - voici un exemple:
Vous avez un référentiel:
2a2e220 (HEAD, master) C5
ab1bda4 C4
3cb46a9 C3
85f59ab C2
4516164 C1
0e783a3 C0
Vous procédez ensuite pour modifier les commits.
git rebase --interactive HEAD~3 # Three commits before where HEAD is pointing
(C’est là que vous devrez vous fier à ma parole: il existe plusieurs façons de modifier les commits dans Git. Dans cet exemple, j’ai modifié l’heure de C3
, Mais vous insérez de nouvelles commissions, en modifiant messages validés, réordonnancement des commits, écrasement des commits ensemble, etc.)
ba7688a (HEAD, master) C5
44085d5 C4
961390d C3
85f59ab C2
4516164 C1
0e783a3 C0
C’est ici qu’il est important de noter que les hachages de validation sont différents. C'est le 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
).
$ git Push Origin master
To [email protected]:username/test1.git
! [rejected] master -> master (non-fast-forward)
error: failed to Push some refs to '[email protected]:username/test1.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git Push --help' for details.
Si nous courons git pull
, Nous voyons ce journal:
7df65f2 (HEAD, master) Merge branch 'master' of bitbucket.org:username/test1
ba7688a C5
44085d5 C4
961390d C3
2a2e220 (Origin/master) C5
85f59ab C2
ab1bda4 C4
4516164 C1
3cb46a9 C3
0e783a3 C0
Ou, montré d'une autre manière:
Et nous avons maintenant des commises en double localement. Si nous devions exécuter git Push
, Nous les enverrions au serveur.
Pour éviter d'atteindre ce stade, nous aurions pu exécuter git Push --force
(Où nous avons plutôt exécuté git 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écuter git pull
:
Regardez le reflog (git reflog
) Pour voir ce que le commit était avant nous avons couru git pull
.
070e71d HEAD@{1}: pull: Merge made by the 'recursive' strategy.
ba7688a HEAD@{2}: rebase -i (finish): returning to refs/heads/master
ba7688a HEAD@{3}: rebase -i (pick): C5
44085d5 HEAD@{4}: rebase -i (pick): C4
961390d HEAD@{5}: commit (amend): C3
3cb46a9 HEAD@{6}: cherry-pick: fast-forward
85f59ab HEAD@{7}: rebase -i (start): checkout HEAD~~~
2a2e220 HEAD@{8}: rebase -i (finish): returning to refs/heads/master
2a2e220 HEAD@{9}: rebase -i (start): checkout refs/remotes/Origin/master
2a2e220 HEAD@{10}: commit: C5
ab1bda4 HEAD@{11}: commit: C4
3cb46a9 HEAD@{12}: commit: C3
85f59ab HEAD@{13}: commit: C2
4516164 HEAD@{14}: commit: C1
0e783a3 HEAD@{15}: commit (initial): C0
Ci-dessus, nous voyons que ba7688a
Était l'engagement que nous avions avant d'exécuter git pull
. Avec ce hash en main, nous pouvons revenir à cela (git reset --hard ba7688a
) Et ensuite exécuter git Push --force
.
Et nous avons fini.
Si, d'une manière ou d'une autre, vous n'avez pas remarqué que les commits étaient dupliqués et que vous avez continué à travailler au-dessus des commits en double, vous vous êtes vraiment mis à semer la pagaille. La taille du gâchis est proportionnelle au nombre de commits que vous avez au dessus des doublons.
A quoi ça ressemble:
3b959b4 (HEAD, master) C10
8f84379 C9
0110e93 C8
6c4a525 C7
630e7b4 C6
070e71d (Origin/master) Merge branch 'master' of bitbucket.org:username/test1
ba7688a C5
44085d5 C4
961390d C3
2a2e220 C5
85f59ab C2
ab1bda4 C4
4516164 C1
3cb46a9 C3
0e783a3 C0
Ou, montré d'une autre manière:
Dans ce scénario, nous voulons supprimer les commits en double, mais conserver les commits que nous avons basés sur eux. Nous souhaitons conserver les c6 à c10. Comme pour la plupart des choses, il y a plusieurs façons de s'y prendre:
Non plus:
cherry-pick
chaque commit (C6 à C10 inclus) sur cette nouvelle branche et traite cette nouvelle branche comme canonique.git rebase --interactive $commit
, Où $commit
Est le commit antérieur aux deux commits dupliqués.2. Ici, nous pouvons supprimer les lignes pour les doublons.1 Peu importe lequel des deux que vous choisissez, que ba7688a
Ou 2a2e220
Fonctionne correctement.
2 Dans l'exemple, ce serait 85f59ab
.
Définissez advice.pushNonFastForward
sur false
:
git config --global advice.pushNonFastForward false
Je pense que vous avez omis un détail important lors de la description de vos étapes. Plus précisément, votre dernière étape, git Push
sur dev, vous aurait en fait provoqué une erreur, car vous ne pouvez normalement pas appliquer les modifications non rapides.
Alors vous avez fait git pull
avant le dernier Push, ce qui a entraîné une fusion avec C6 et C6 'en tant que parents, ce qui explique pourquoi les deux resteront répertoriés dans le journal. Un format de journal plus joli aurait peut-être rendu plus évident le fait qu'ils sont des branches fusionnées de validations dupliquées.
Ou vous avez fait un git pull --rebase
_ (ou sans --rebase
si cela est implicite dans votre configuration), ce qui a ramené les C5 et C6 d'origine dans votre développement local (et redéfini les bases suivantes en un nouveau hachage, C7 'C5' 'C6' ').
Un moyen de s'en sortir aurait pu être git Push -f
pour forcer la Push quand elle a donné l'erreur et effacer C5 C6 de Origin, mais si quelqu'un d'autre les faisait également tirer avant de les effacer, vous auriez beaucoup plus de problèmes ... en gros tous ceux qui ont C5 C6 devrait prendre des mesures spéciales pour s'en débarrasser. C'est exactement pourquoi ils disent que vous ne devriez jamais refonder tout ce qui a déjà été publié. C'est toujours faisable si on dit que "l'édition" est au sein d'une petite équipe.
J'ai découvert que dans mon cas, ce problème était la conséquence d'un problème de configuration de Git. (Impliquant tirer et fusionner)
Description du problème:
Sympthoms: Commits dupliqués sur une branche enfant après une nouvelle base, impliquant de nombreuses fusions pendant et après la nouvelle base.
Workflow: Voici les étapes du workflow que j'effectuais:
En conséquence de ce flux de travail, la duplication de tous les commits de "Feature-branch" depuis la précédente refonte ... :
--- (Le problème était dû à l'extraction des modifications de la branche enfant avant la base. La configuration d'extraction par défaut de Git est "fusion". Cela modifie les index des commits effectués sur la branche enfant.
La solution: dans le fichier de configuration Git, configurez pull pour fonctionner en mode rebase:
...
[pull]
rebase = preserve
...
J'espère que cela peut aider JN Grx