Rebasage d'un commit de fusion Git

183

Prenons le cas suivant:

J'ai du travail dans une branche thématique et maintenant je suis prêt à reprendre le master:

* eb3b733 3     [master] [origin/master]
| * b62cae6 2   [topic]
|/  
* 38abeae 1

J'effectue la fusion à partir du maître, je résous les conflits et maintenant j'ai:

*   8101fe3 Merge branch 'topic'  [master]
|\  
| * b62cae6 2                     [topic]
* | eb3b733 3                     [origin/master]
|/  
* 38abeae 1

Maintenant, la fusion m'a pris un certain temps, alors je fais une autre récupération et remarque que la branche principale distante a de nouveaux changements:

*   8101fe3 Merge branch 'topic'  [master]
|\  
| * b62cae6 2                     [topic]
| | * e7affba 4                   [origin/master]
| |/  
|/|   
* | eb3b733 3
|/  
* 38abeae 1

Si j'essaye 'git rebase origin / master' de master, je suis obligé de résoudre à nouveau tous les conflits, et je perds également le commit de fusion:

* d4de423 2       [master]
* e7affba 4       [origin/master]
* eb3b733 3
| * b62cae6 2     [topic]
|/  
* 38abeae 1

Existe-t-il un moyen propre de rebaser le commit de fusion afin que je me retrouve avec un historique comme celui que je montre ci-dessous?

*   51984c7 Merge branch 'topic'  [master]
|\  
| * b62cae6 2                     [topic]
* | e7affba 4                     [origin/master]
* | eb3b733 3
|/  
* 38abeae 1
jipumarino
la source
74
TL; DR:git rebase --preserve-merges origin/master
Ilia K.
6
En ce qui concerne la nécessité de résoudre à nouveau les conflits, vous voudrez peut-être jeter un œil à git rerere .
Parker Coates
git config --global pull.rebase preserve pour toujours préserver les commits de fusion lors d'un rebase
galath
4
Attention: à partir de Git 2.18 (Q2 2018, 5 ans plus tard), git --rebase-mergesremplacera à terme l'ancien git --preserve-merges. Voir Que fait exactement « rebase --preserve-merges» de Git (et pourquoi?)
VonC
1
--preserve-mergesest obsolète. Utilisationgit rebase --rebase-merges origin/master
Arjun Sreedharan

Réponses:

127

Il y a deux options ici.

La première consiste à effectuer un rebase interactif et à modifier la validation de fusion, à refaire la fusion manuellement et à continuer le rebase.

Une autre consiste à utiliser l' --rebase-mergesoption on git rebase, qui est décrite comme suit dans le manuel: "Par défaut, un rebase supprimera simplement les commits de fusion de la liste des tâches et placera les commits rebasés dans une seule branche linéaire. Avec --rebase- fusionne, le rebase essaiera à la place de préserver la structure de branchement dans les commits qui doivent être rebasés, en recréant les commits de fusion. Tous les conflits de fusion résolus ou les modifications manuelles dans ces commits de fusion devront être résolus / réappliqués manuellement. "

siride
la source
16
J'ai essayé l'option -p, et cela laisse en effet l'historique des commit comme je le voulais, mais cela me force à résoudre à nouveau les conflits, même dans les fichiers qui n'ont pas été édités dans origin / master. À partir de votre première suggestion, quelle serait la séquence précise des commandes?
jipumarino
2
@jipumarino: git rebase -i (dites-lui de modifier le commit de fusion), quand il arrive au commit de fusion, git reset --hard HEAD ^, git merge, corrige les conflits, git commit, git rebase --continue. Vous voudrez peut-être aussi regarder git rerere qui est censé aider avec ce genre de chose (mais je ne l'ai jamais utilisé, donc je ne peux pas offrir de conseils ou d'aide).
siride
2
Merci. J'ai activé rerere et essayé avec rebase -p et cela fonctionne comme il se doit.
jipumarino
3
Voici un excellent article de blog décrivant cette situation exacte: Rebasing Merge Commits in Git
kynan
1
rere n'est pas la solution, car vous devez toujours résoudre les fusions manuellement la première fois.
Flimm le
29

Ok, c'est une vieille question et elle a déjà accepté la réponse par @siride, mais cette réponse n'était pas suffisante dans mon cas, car --preserve-mergesvous oblige à résoudre tous les conflits une deuxième fois. Ma solution basée sur l'idée @Tobi Bmais avec des commandes pas à pas exactes

Nous allons donc commencer sur un tel état en nous basant sur l'exemple de la question:

*   8101fe3 Merge branch 'topic'  [HEAD -> master]
|\  
| * b62cae6 2                     [topic]
| |
| | * f5a7ca8 5                   [origin/master]
| | * e7affba 4
| |/  
|/|   
* | eb3b733 3
|/  
* 38abeae 1

Notez que nous avons 2 commits devant master, donc le cherry-pick ne fonctionnerait pas.

  1. Tout d'abord, créons l'historique correct que nous voulons:

    git checkout -b correct-history # create new branch to save master for future
    git rebase --strategy=ours --preserve-merges origin/master
    

    Nous utilisons --preserve-mergespour enregistrer notre validation de fusion dans l'historique. Nous utilisons --strategy=ourspour ignorer tous les conflits de fusion car nous ne nous soucions pas du contenu de cette validation de fusion, nous n'avons besoin que d'un bel historique maintenant.

    L'histoire ressemblera à ça (en ignorant le maître):

    *   51984c7 Merge branch 'topic'  [HEAD -> correct-history]
    |\  
    | * b62cae6 2                     [topic]
    * | f5a7ca8 5                     [origin/master]
    * | e7affba 4
    * | eb3b733 3
    |/  
    * 38abeae 1
    
  2. Obtenons l'index correct maintenant.

    git checkout master # return to our master branch
    git merge origin/master # merge origin/master on top of our master
    

    Nous pouvons avoir des conflits de fusion supplémentaires ici, mais ce ne serait que des conflits de fichiers modifiés entre 8101fe3et f5a7ca8, mais n'inclut pas les conflits déjà résolus à partir detopic

    L'historique ressemblera à ceci (en ignorant l'historique correct):

    *   94f1484 Merge branch 'origin/master'  [HEAD -> master]
    |\  
    * | f5a7ca8 5                   [origin/master]
    * | e7affba 4
    | *   8101fe3 Merge branch 'topic'
    | |\  
    | | * b62cae6 2                     [topic]
    |/ /
    * / eb3b733 3
    |/  
    * 38abeae 1
    
  3. La dernière étape consiste à combiner notre branche avec un historique correct et une branche avec un index correct

    git reset --soft correct-history
    git commit --amend
    

    Nous utilisons reset --softpour réinitialiser notre branche (et notre historique) à l'historique correct, mais laissons l'index et l'arbre de travail tels quels. Ensuite, nous commit --amendréécrivons notre validation de fusion, qui avait un index incorrect, avec notre bon index de master.

    À la fin, nous aurons un tel état (notez un autre identifiant du commit supérieur):

    *   13e6d03 Merge branch 'topic'  [HEAD -> master]
    |\  
    | * b62cae6 2                     [topic]
    * | f5a7ca8 5                     [origin/master]
    * | e7affba 4
    * | eb3b733 3
    |/  
    * 38abeae 1
    
Ivan Naydonov
la source
C'est génial et cela a beaucoup aidé! Mais je n'ai pas compris l'astuce avec commit --amend: pouvez-vous ajouter plus d'informations à ce sujet? Que se passe-t-il exactement après l'avoir exécuté - j'ai remarqué que le SHA du commit a changé - mais pourquoi? Ou que se passe-t-il si vous ne l'exécutez pas?
ZenJ
1
@ZenJ git commit --amendajoute les modifications au dernier commit (HEAD, dans ce cas le commit de fusion). Étant donné que le contenu de la validation change, le hachage est mis à jour.
Dries Staelens
1
Pour les personnes qui n'ont pas activé la fonction «rerere» avant de résoudre les conflits, cette solution est excellente car elle vous évite d'avoir à résoudre à nouveau les conflits. Merci!
Shackleford
6

Étant donné que je viens de perdre une journée à essayer de comprendre cela et que j'ai trouvé une solution avec l'aide d'un collègue, j'ai pensé que je devrais intervenir.

Nous avons une grande base de code et nous devons faire face à 2 branches fortement modifiées en même temps. Il y a une branche principale et une branche secondaire si vous le souhaitez.

Pendant que je fusionne la branche secondaire dans la branche principale, le travail se poursuit dans la branche principale et au moment où j'en ai terminé, je ne peux pas appliquer mes modifications car elles sont incompatibles.

J'ai donc besoin de "rebaser" ma "fusion".

Voici comment nous l'avons finalement fait:

1) notez le SHA. ex .: c4a924d458ea0629c0d694f1b9e9576a3ecf506b

git log -1

2) Créez l'historique approprié, mais cela interrompra la fusion.

git rebase -s ours --preserve-merges origin/master

3) notez le SHA. ex .: 29dd8101d78

git log -1

4) Maintenant, réinitialisez à l'endroit où vous étiez auparavant

git reset c4a924d458ea0629c0d694f1b9e9576a3ecf506b --hard

5) Maintenant, fusionnez le maître actuel dans votre branche de travail

git merge origin/master
git mergetool
git commit -m"correct files

6) Maintenant que vous avez les bons fichiers, mais le mauvais historique, obtenez le bon historique en plus de vos modifications avec:

git reset 29dd8101d78 --soft

7) Et puis - modifiez les résultats de votre validation de fusion d'origine

git commit --amend

Voila!

Claude Péloquin
la source
1

Il semble que vous souhaitiez supprimer votre première fusion. Vous pouvez suivre la procédure suivante:

git checkout master      # Let's make sure we are on master branch
git reset --hard master~ # Let's get back to master before the merge
git pull                 # or git merge remote/master
git merge topic

Cela vous donnerait ce que vous voulez.

Antoine Pelisse
la source
4
Avec rerere activé, cela semble donner le même résultat que la solution rebase -p donnée ci-dessus par siride.
jipumarino
0
  • Depuis votre validation de fusion
  • Choisissez le nouveau changement qui devrait être facile
  • copiez vos trucs
  • refaire la fusion et résoudre les conflits en copiant simplement les fichiers de votre copie locale;)
Tobi B
la source
1
Cette réponse semble bonne mais serait plus utile si vous donniez les commandes git réelles, au cas où un utilisateur serait nouveau sur git
Louise Davies