110 votes

Git : Extraction d'une branche rebasée

Laissez-moi vous décrire ma situation :

M. Blond et M. Orange travaillent sur la branche A qui se sépare de la branche master sur le commit M1. La branche A a 2 commits : A1 et A2.

M1  
   \
    \
     A1 - A2

Pendant ce temps, Mr Orange a commis et poussé 2 commits supplémentaires sur la branche master, M2 et M3.

M1  - M2 - M3
   \
    \
     A1 - A2

Mr Blond tire depuis la télécommande, et après un moment, décide de rebaser sur la branche master :

M1  - M2 - M3
   \         \
    \         \
     A1 - A2   A1` - A2`

Maintenant, A1` et A2` sont les commits rebasés qui existent localement chez Mr blond, et A1 et A2 existent à distance. M. Blond pousse ses commits, en utilisant la commande -f pour forcer ses changements et "réécrire" l'histoire. Maintenant, le référentiel distant ressemble à ça :

M1  - M2 - M3
             \
              \
               A1` - A2`

Mais M. Orange a aussi travaillé sur la branche A. Son référentiel local ressemble toujours à ça :

M1  - M2 - M3
   \
    \
     A1 - A2

Que doit faire M. Orange pour être synchronisé avec la branche A du référentiel distant ?

Une traction normale ne fonctionnera pas. Will pull -f forcer les changements de la télécommande localement ? Je sais que supprimer la version locale de A et la ramener depuis le référentiel distant fera l'affaire mais cela ne semble pas être un bon moyen d'y parvenir.

126voto

Gary Fixler Points 3677

Si M. Orange n'a pas peur de perdre ses modifications, il peut aller chercher sur le serveur, puis git checkout A pour accéder à son local A puis (en supposant que la branche distante soit nommée "origin") git reset --hard origin/A pour réinitialiser son A à l'endroit où la télécommande A est.

S'il craint de perdre des modifications, il peut fusionner les modifications du serveur pour les résoudre (à partir de son propre fichier A et en supposant à nouveau que la branche distante est nommée "origin") avec git merge origin/A . Cela va créer un nouveau commit qui se trouve au-dessus de son propre commit et de celui de la télécommande. A avec les changements des deux branches fusionnées ensemble. Ensuite, cela peut être repoussé vers la branche distante.

37voto

torek Points 25463

Ma recommandation (ou, "ce que je ferais si j'étais M. Orange") est de commencer par git fetch . Maintenant j'ai ceci dans mon dépôt, qui est ce que Mr Blond avait après son rebasement et juste avant qu'il n'exécute "git push -f".

M1  - M2 - M3
   \         \
    \         \
     A1 - A2   A1' - A2'

La seule différence importante, c'est que j'aurai mon propre label. A pointant vers le rév A2, et l'étiquette distante remotes/origin/A pointant vers A2' (M. Blond l'avait dans l'autre sens, l'étiquette locale A pointant vers A2' et remotes/origin/A en pointant vers A2).

Si j'ai travaillé sur ma copie de la branche nommée "A", je vais avoir ceci à la place :

M1  ---- M2 ---- M3
   \               \
    \               \
     A1 - A2 - A3    A1' - A2'

(avec mon étiquette locale indiquant A3 plutôt que A2 ; ou A4 ou A5, etc., selon le nombre de modifications que j'ai appliquées). Maintenant, tout ce que j'ai à faire est de rebaser mon A3 (et A4 si nécessaire, etc) sur A2'. Un moyen direct évident :

$ git branch -a
  master
* A
  remotes/origin/master
  remotes/origin/A
$ git branch new_A remotes/origin/A
$ git rebase -i new_A

et ensuite laisser tomber complètement les révisions A1 et A2, puisque les révisions modifiées sont dans new_A comme A1' et A2'. Ou bien :

$ git checkout -b new_A remotes/origin/A
$ git format-patch -k --stdout A3..A | git am -3 -k

(le git am -3 -k est décrite dans le git-format-patch page du manuel).

Il faut savoir ce que j'ai que M. Blond n'avait pas avant de faire son rebase c'est-à-dire en identifiant A1, A2, A3, etc.

Si la deuxième approche est réussie, je me retrouve avec :

M1  ---- M2 ---- M3
   \               \
    \               \
     A1 - A2 - A3    A1' - A2' - A3'

où le nom de ma branche new_A pointe vers A3' (mon existant A pointe toujours vers l'ancien A3). Si j'utilise la première approche et qu'elle réussit, je me retrouve avec la même chose, c'est juste que le nom de ma branche existante A pointera maintenant vers A3' (et je n'ai pas de nom pour l'ancienne branche avec A1-A2-A3, même si elle est toujours dans mon repo ; pour la trouver, il faut passer par les reflogs ou autres).

(Si mon A3 a besoin d'être modifié pour devenir A3', la méthode interactive rebase et la méthode "git am" nécessiteront toutes deux un travail de ma part, bien sûr).

Bien sûr, il est également possible de simplement git merge (comme dans la réponse de Gary Fixler), mais cela créera un commit de fusion ("M" sans numéro, ci-dessous) et gardera les revs A1 et A2 visibles, donnant :

M1  ---- M2 ---- M3
   \               \
    \               \
     A1 - A2 - A3    A1' - A2' -- M
                 \_______________/

Si vous voulez préserver les originaux A1 et A2, c'est une bonne chose ; si vous voulez vous en débarrasser, c'est une mauvaise chose. Ainsi, "ce qu'il faut faire" dépend de "ce que vous voulez que le résultat soit".

Modification pour ajouter : je préfère la méthode format-patch car elle laisse mon ancien nom de branche A pendant que je m'assure que tout est bon. En supposant que tout fonctionne et que es Bien, voici les dernières étapes :

$ git branch -m A old_A
$ git branch -m new_A A

et ensuite, si l'ancienne version peut être entièrement abandonnée :

$ git branch -D old_A

ou, de manière équivalente, commencer par la branche delete, puis renommer new_A en A.

(Edit : voir aussi git rebase --onto dans le but de rebaser A3, etc., sur la branche new_A).

31voto

FiddleStix Points 121

TLDR

Sauf si j'ai mal compris la question, la réponse est que M. Orange doit git pull --rebase .

Lecture plus longue

Cas simple

Si nous supposons que les modifications apportées par A1 et A2 sont les mêmes que celles apportées par A1' et A2' (même si leurs hashs de commit ne sont pas les mêmes), alors M. Orange peut passer d'une situation où il dispose de

M1  - M2 - M3
   \
    \
     A1 - A2

localement pour avoir

M1  - M2 - M3
             \
              \
               A1` - A2`

soit en faisant

git pull --rebase

o

git branch -d A
git checkout A

Un cas plus intéressant

Si nous supposons, comme ci-dessus, que les changements effectués par A1 et A2 sont les mêmes et que M. Orange a effectué un commit supplémentaire, alors il aura localement

M1  - M2 - M3
   \
    \
     A1 - A2 - A3O

et l'origine sera

M1  - M2 - M3
             \
              \
               A1` - A2`

et, encore une fois, M. Orange peut accéder

M1  - M2 - M3
             \
              \
               A1` - A2` - A3O

con

git pull --rebase

Un cas plus réaliste

Si nous supposons, comme ci-dessus, que les modifications apportées par A1 et A2 sont les mêmes, que M. Orange a fait un commit supplémentaire et que M. Blonde a fait un commit supplémentaire, alors M. Orange aura localement ceci

M1  - M2 - M3
   \
    \
     A1 - A2 - A3O

et, en supposant que M. Blonde a poussé, l'origine sera

M1  - M2 - M3
             \
              \
               A1` - A2` - A3B

et M. Orange peut accéder à

M1  - M2 - M3
             \
              \
               A1` - A2` - A3B - A3O

en faisant

git pull --rebase

bien que, cette fois, il puisse avoir besoin de régler des conflits.

21voto

rmayer06 Points 2447

Je développe simultanément sur deux machines virtuelles, à des fins de configuration. Par conséquent, je rebase fréquemment sur une machine et j'ai besoin que les changements apparaissent sur l'autre sans difficulté.

Supposons que ma branche s'appelle feature/my-feature-branch . Après avoir terminé le rebasement sur la première VM, je fais un git fetch sur la seconde. Le message suivant apparaît :

$ git status
On branch feature/my-feature-branch
Your branch and 'origin/feature/my-feature-branch' have diverged,
and have 21 and 24 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)

Eh bien, ne faites pas un git pull, parce que vous vous retrouvez avec un commit de fusion sans signification après pas mal d'agitation.

Au lieu de cela, exécutez

git rebase -i origin/feature/my-feature-branch

Une fois que l'éditeur de texte apparaît, supprimez tous les commits, et remplacez-les par ce qui suit (cela fait en sorte que le rebasement se termine sans qu'aucun commit ne soit conservé).

exec echo test

Si vous avez des commits qui doivent être conservés, alors ils peuvent être appliqués ici. Dans tous les cas, le rebasement sera terminé, et maintenant les deux machines sont de nouveau synchronisées, comme le montre le tableau suivant :

$ git pull
Already up-to-date.
$ git push
Everything up-to-date

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X