Permettez-moi de décrire ma situation:
M. Blond et M. Orange travaillent sur la branche A qui dérive de la branche principale lors du commit M1. La branche A a 2 commits: A1 et A2.
M1
\
\
A1 - A2
Pendant ce temps, M. Orange s'est engagé et a poussé 2 autres commits sur la branche principale, M2 et M3.
M1 - M2 - M3
\
\
A1 - A2
M. Blond tire de la télécommande, et après un certain temps décide de rebaser sur la branche principale:
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 - f pour forcer ses changements et "réécrire" l'historique. Maintenant, le référentiel distant ressemble à ceci:
M1 - M2 - M3
\
\
A1` - A2`
Mais M. Orange a également travaillé sur la branche A. Son référentiel local ressemble toujours à ceci:
M1 - M2 - M3
\
\
A1 - A2
Que doit faire M. Orange pour être synchronisé avec une branche A dans le référentiel distant?
Une traction normale ne fonctionnera pas. pull -f forcera-t-il les modifications de la télécommande localement? Je sais que supprimer la version locale de A et la ramener du référentiel distant fera l'affaire, mais cela ne semble pas être un bon moyen d'y parvenir.
Ma recommandation (ou "ce que je ferais si j'étais M. Orange") est de commencer par git fetch
. Maintenant, je vais l'avoir dans mon référentiel, qui est ce que M. Blond avait après son rebase et juste avant de lancer "git Push -f".
M1 - M2 - M3
\ \
\ \
A1 - A2 A1' - A2'
La seule différence importante est que je vais avoir mon étiquette locale A
pointant vers rev A2 et l'étiquette distante remotes/Origin/A
pointant vers A2 '(M. Blond l'a fait dans l'autre sens, étiquette locale A
pointant vers A2' et remotes/Origin/A
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 pointant vers 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 ' . Une voie directe évidente:
$ git branch -a
master
* A
remotes/Origin/master
remotes/Origin/A
$ git branch new_A remotes/Origin/A
$ git rebase -i new_A
puis supprimez entièrement les tours A1 et A2, car ceux modifiés sont dans new_A comme A1 'et A2'. Ou:
$ git checkout -b new_A remotes/Origin/A
$ git format-patch -k --stdout A3..A | git am -3 -k
(les git am -3 -k
la méthode est décrite dans le git-format-patch
page de manuel).
Cela nécessite de comprendre ce que j'ai que M. Blond n'a pas fait avant de faire son rebase
, c'est-à-dire identifier A1, A2, A3, etc.
Si la deuxième approche réussit, je me retrouve avec:
M1 ---- M2 ---- M3
\ \
\ \
A1 - A2 - A3 A1' - A2' - A3'
où mon nom de succursale new_A
pointe vers A3 '(ma branche A
existante 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 mon nom de branche existant A
va maintenant pointer vers A3 '(et je n'ai pas de nom pour l'ancienne branche avec A1- A2-A3, même s'il est toujours dans mon référentiel; pour le trouver, il faut passer par des reflogs ou similaires).
(Si mon A3 doit être modifié pour devenir A3 ', le rebase interactif et la méthode "git am" nécessiteront bien sûr un travail de ma part.)
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 tours A1 et A2 visibles, donnant:
M1 ---- M2 ---- M3
\ \
\ \
A1 - A2 - A3 A1' - A2' -- M
\_______________/
Si vous souhaitez conserver les A1 et A2 d'origine, c'est une bonne chose; si vous voulez vous en débarrasser, c'est une mauvaise chose. Donc "que faire" dépend de "ce que vous voulez que le résultat soit".
Modifier pour ajouter: j'aime mieux 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 est bon, voici les dernières étapes:
$ git branch -m A old_A
$ git branch -m new_A A
puis, si old_A peut être complètement abandonné:
$ git branch -D old_A
ou, de manière équivalente, commencez par la suppression de la branche, puis renommez new_A en A.
(Modifier: voir aussi git rebase --onto
documentation, dans le but de rebaser A3, etc., sur la branche new_A.)
Si M. Orange ne craint pas de perdre ses modifications, il peut aller chercher sur le serveur, puis git checkout A2
pour accéder à son local A2
branche, puis (en supposant que la télécommande est nommée "Origin") git reset --hard Origin/A2
pour réinitialiser son A2
à l'endroit où la télécommande A2
est.
S'il souhaite perdre des modifications, il peut fusionner les modifications du serveur pour les résoudre (à partir de son propre A2
branche, et en supposant à nouveau que la télécommande est nommée "Origin") avec git merge Origin/A2
. Cela va faire un nouveau commit qui est au-dessus de lui et de la télécommande A2
branches avec les changements des deux fusionnés. Ensuite, cela peut être repoussé vers la télécommande.
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 modifications apparaissent sur l'autre sans difficulté.
Supposons que ma branche s'appelle feature/my-feature-branch
. Après avoir terminé le rebase 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 de git pull, car vous vous retrouvez avec un commit de fusion sans signification après pas mal de tracas.
Au lieu de cela, exécutez
git rebase -i Origin/feature/my-feature-branch
Une fois que l'éditeur de texte apparaît, supprimez toutes les validations et remplacez-le par ce qui suit (cela fait en sorte que le rebase se termine sans qu'aucune validation ne soit conservée).
exec echo test
Si vous avez des commits qui doivent être conservés, ceux-ci peuvent être appliqués ici. Dans les deux cas, le rebase se terminera, et maintenant les deux machines sont à nouveau synchronisées, comme en témoigne:
$ git pull
Already up-to-date.
$ git Push
Everything up-to-date