web-dev-qa-db-fra.com

Comment récupérer / resynchroniser après que quelqu'un a poussé un rebase ou une réinitialisation dans une branche publiée?

Nous avons tous entendu qu'il ne fallait jamais rebaser un travail publié, que c'était dangereux, etc. Cependant, je n'ai vu aucune recette publiée pour savoir comment gérer la situation au cas où un rebaser est publié.

Maintenant, notez que cela n'est vraiment possible que si le référentiel n'est cloné que par un groupe de personnes connu (et de préférence petit), de sorte que quiconque pousse le rebase ou la réinitialisation puisse informer tout le monde qu'il devra faire attention la prochaine fois qu'il récupérer (!).

Une solution évidente que j'ai vue fonctionnera si vous n'avez pas de commit local sur foo et qu'elle est rebasée:

git fetch
git checkout foo
git reset --hard Origin/foo

Cela supprimera simplement l'état local de foo au profit de son historique selon le référentiel distant.

Mais comment gérer la situation si l'on a commis des changements locaux importants sur cette branche?

86

Revenir en synchronisme après un rebase poussé n'est vraiment pas si compliqué dans la plupart des cas.

git checkout foo
git branch old-foo Origin/foo # BEFORE fetching!!
git fetch
git rebase --onto Origin/foo old-foo foo
git branch -D old-foo

C'est à dire. vous définissez d'abord un signet pour l'emplacement d'origine de la branche distante, puis vous l'utilisez pour rejouer vos validations locales à partir de ce point sur la branche distante rebasée.

Le remodelage est comme la violence: s'il ne résout pas votre problème, vous en avez juste besoin de plus. ☺

Vous pouvez le faire sans le signet bien sûr, si vous recherchez le pré-rebase Origin/foo ID de validation, et utilisez-le.

C'est aussi ainsi que vous traitez la situation où vous avez oublié de créer un signet avant aller chercher. Rien n'est perdu - il vous suffit de vérifier le reflog de la branche distante:

git reflog show Origin/foo | awk '
    PRINT_NEXT==1 { print $1; exit }
    /fetch: forced-update/ { PRINT_NEXT=1 }'

Cela affichera l'ID de validation qui Origin/foo a souligné avant la dernière extraction qui a changé son histoire.

Vous pouvez alors simplement

git rebase --onto Origin/foo $commit foo
74

À partir de git 1.9/2.0 Q1 2014, vous n'aurez pas à marquer l'origine de votre branche précédente avant de la rebaser sur la branche amont réécrite, comme décrit dans Aristotle Pagaltzis 's answer =:
Voir commit 07d406b et commit d96855f :

Après avoir travaillé sur la branche topic créée avec git checkout -b topic Origin/master, L'historique de la branche de suivi à distance Origin/master Peut avoir été rembobiné et reconstruit, conduisant à un historique de cette forme:

                   o---B1
                  /
  ---o---o---B2--o---o---o---B (Origin/master)
          \
           B3
            \
             Derived (topic)

Origin/master pointait sur les commits B3, B2, B1 et maintenant il pointe sur B et votre topic la branche a été démarrée par dessus quand Origin/master était à B3.

Ce mode utilise le reflog de Origin/master Pour trouver B3 Comme point de départ, afin que le topic puisse être rebasé au-dessus du mis à jour Origin/master par:

$ fork_point=$(git merge-base --fork-point Origin/master topic)
$ git rebase --onto Origin/master $fork_point topic

C'est pourquoi la commande git merge-base a une nouvelle option:

--fork-point::

Recherchez le point auquel une branche (ou tout historique menant à <commit>) Se bifurque à partir d'une autre branche (ou de toute référence) <ref>.
Cela ne recherche pas seulement l'ancêtre commun des deux commits, mais prend également en compte le reflog de <ref> Pour voir si l'historique menant à <commit> Issu d'une incarnation antérieure de la branche <ref>.


La commande "git pull --rebase" Calcule le point de départ de la branche en cours de rebasage à l'aide des entrées de reflog de la branche "base" (généralement une branche de suivi à distance) sur laquelle le travail de la branche était basé pour faire face au cas où la branche "base" a été rembobinée et reconstruite.

Par exemple, si l'historique ressemblait à où:

  • la pointe actuelle de la branche "base" est à B, mais une extraction antérieure a observé que sa pointe était auparavant B3, puis B2, puis B1 Avant d'arriver au commit actuel, et
  • la branche qui est rebasée au-dessus de la dernière "base" est basée sur la validation B3,

il essaie de trouver B3 en passant par la sortie de "git rev-list --reflog base" (c'est-à-dire B, B1, B2, B3) Jusqu'à ce qu'il trouve une validation qui est un ancêtre de l'astuce actuelle "Derived (topic)".

En interne, nous avons get_merge_bases_many() qui peut calculer cela en une seule fois.
Nous voudrions une base de fusion entre Derived et une validation de fusion fictive qui résulterait de la fusion de toutes les astuces historiques de "base (Origin/master)".
Lorsqu'un tel commit existe, nous devrions obtenir un seul résultat, qui correspond exactement à l'une des entrées de reflog de "base".


Git 2.1 (Q3 2014) ajoutera rendre cette fonctionnalité plus robuste à cela: voir commit 1e0dacd by John Keeping (johnkeeping)

gérer correctement le scénario où nous avons la topologie suivante:

    C --- D --- E  <- dev
   /
  B  <- master@{1}
 /
o --- B' --- C* --- D*  <- master

où:

  • B' Est une version corrigée de B qui n'est pas identique au patch avec B;
  • C* Et D* Sont identiques au patch à C et D respectivement et entrent en conflit textuellement s'ils sont appliqués dans le mauvais ordre;
  • E dépend textuellement de D.

Le résultat correct de git rebase master dev Est que B est identifié comme le point de départ de dev et master, de sorte que C, D, E sont les commits qui doivent être rejoués sur master; mais C et D sont identiques au patch avec C* et D* et peuvent donc être supprimés, de sorte que le résultat final est:

o --- B' --- C* --- D* --- E  <- dev

Si le point de départ n'est pas identifié, la sélection de B sur une branche contenant B' Entraîne un conflit et si les validations identiques au patch ne sont pas correctement identifiées, la sélection de C sur une branche contenant D (ou de manière équivalente D*) entraîne un conflit.

11
VonC

Je dirais que la section récupération après rebase en amont de la page de manuel git-rebase couvre à peu près tout cela.

Ce n'est vraiment pas différent de récupérer à partir de votre propre rebase - vous déplacez une branche et rebase toutes les branches qui l'avaient dans leur histoire vers sa nouvelle position.

11
Cascabel