Je suis novice dans les complexités de branchement de Git. Je travaille toujours sur une seule branche et je commets les changements, puis je pousse périodiquement vers mon origine distante.
Récemment, j'ai fait une réinitialisation de certains fichiers pour les sortir du commit staging, et plus tard j'ai fait un rebase -i
pour me débarrasser de quelques commits locaux récents. Maintenant, je suis dans un état que je ne comprends pas vraiment.
Dans ma zone de travail, le git log
montre exactement ce à quoi je m'attends - je suis sur la bonne voie avec les commits que je ne voulais pas voir partir, et les nouveaux commits là, etc.
Mais je viens juste de pousser vers le dépôt distant, et ce qui s'y trouve est différent - deux des commits que j'avais tués dans le rebasement ont été poussés, et les nouveaux commits commis localement ne sont pas là.
Je pense que "master/origin" ; est détaché de HEAD, mais je ne suis pas sûr à 100% de ce que cela signifie, comment le visualiser avec les outils de ligne de commande, et comment le corriger.
Tout d'abord, clarifions [ce qu'est la HEAD][1] et ce qu'elle signifie lorsqu'elle est détachée.
HEAD est le nom symbolique du commit actuellement extrait. Lorsque HEAD n'est pas détaché (la situation "normale"1 : vous avez une branche extraite), HEAD pointe en fait vers la "ref" d'une branche et la branche pointe vers le commit. HEAD est donc "attaché" à une branche. Lorsque vous faites un nouveau commit, la branche vers laquelle HEAD pointe est mise à jour pour pointer vers le nouveau commit. HEAD suit automatiquement puisqu'il pointe simplement vers la branche.
git symbolic-ref HEAD
donne refs/heads/master
La branche nommée "master" est extraite.git rev-parse refs/heads/master
donne 17a02998078923f2d62811326d130de991d1a95a
Ce commit est l'extrémité actuelle ou "tête" de la branche master.git rev-parse HEAD
donne aussi 17a02998078923f2d62811326d130de991d1a95a
C'est ce que signifie une "référence symbolique". Elle pointe vers un objet à travers une autre référence.Nous avons HEAD
→ refs/heads/master
→ 17a02998078923f2d62811326d130de991d1a95a
Lorsque HEAD est détaché, il pointe directement vers un commit - au lieu de pointer indirectement vers un commit via une branche. Vous pouvez penser à un HEAD détaché comme étant sur une branche non nommée.
git symbolic-ref HEAD
échoue avec fatal : ref HEAD is not a symbolic ref
.git rev-parse HEAD
donne 17a02998078923f2d62811326d130de991d1a95a
Puisque ce n'est pas une référence symbolique, elle doit pointer directement vers le commit lui-même.Nous avons HEAD
→ 17a02998078923f2d62811326d130de991d1a95a
La chose importante à retenir avec un HEAD détaché est que si le commit vers lequel il pointe n'est pas référencé (aucun autre ref ne peut l'atteindre), alors il deviendra "dangling" quand vous checkouterez un autre commit. Finalement, de tels commits dangling seront élagués par le processus de garbage collection (par défaut, ils sont conservés pendant au moins 2 semaines et peuvent être conservés plus longtemps s'ils sont référencés par le reflog de HEAD).
1 Il est tout à fait possible d'effectuer un travail "normal" avec un HEAD détaché, il suffit de garder une trace de ce que l'on fait pour éviter de devoir repêcher l'historique déposé dans le reflog.
Les étapes intermédiaires d'un rebasement interactif sont effectuées avec un HEAD détaché (en partie pour éviter de polluer le reflog de la branche active). Si vous terminez l'opération de rebasement complet, il mettra à jour votre branche d'origine avec le résultat cumulé de l'opération de rebasement et rattachera HEAD à la branche d'origine. Je suppose que vous n'avez jamais terminé le processus de rebasement complet ; cela vous laissera avec un HEAD détaché pointant sur le commit qui a été traité le plus récemment par l'opération de rebasement.
Pour récupérer votre situation, vous devriez créer une branche qui pointe vers le commit actuellement pointé par votre HEAD détaché :
git branch temp
git checkout temp
(ces deux commandes peuvent être abrégées en git checkout -b temp
)
Ceci rattache votre HEAD à la nouvelle branche temp
.
Ensuite, vous devez comparer le commit actuel (et son historique) avec la branche normale sur laquelle vous pensiez travailler :
git log --graph --decorate --pretty=oneline --abbrev-commit master origin/master temp
git diff master temp
git diff origin/master temp
(Vous voudrez probablement expérimenter avec les options du journal : ajouter -p
, laisser de côté --pretty=...
pour voir le message complet du journal, etc.)
Si votre nouvelle branche temp
semble bonne, vous pouvez mettre à jour (par exemple) master
pour qu'il pointe vers elle :
git branch -f master temp
git checkout master
(ces deux commandes peuvent être abrégées en git checkout -B master temp
)
Vous pouvez ensuite supprimer la branche temporaire :
git branch -d temp
Enfin, vous voudrez probablement pousser l'historique rétabli :
git push origin master
Vous pouvez avoir besoin d'ajouter --force
à la fin de cette commande pour pousser si la branche distante ne peut pas être "fast-forwardée" vers le nouveau commit (c'est-à-dire que vous avez abandonné, ou réécrit un commit existant, ou autrement réécrit un peu d'historique).
Si vous étiez au milieu d'une opération de rebasement, vous devriez probablement la nettoyer. Vous pouvez vérifier si un rebasement était en cours en cherchant le répertoire .git/rebase-merge/
. Vous pouvez nettoyer manuellement le rebasement en cours en supprimant simplement ce répertoire (par exemple, si vous ne vous souvenez plus du but et du contexte de l'opération de rebasement active). Habituellement, vous utiliseriez git rebase --abort
, mais cela fait des réinitialisations supplémentaires que vous voulez probablement éviter (cela déplace HEAD vers la branche d'origine et le réinitialise au commit d'origine, ce qui annule une partie du travail que nous avons fait ci-dessus).
[1] : https://git-scm.com/book/en/v2/Git-Internals-Git-References
Regardez ici pour une explication de base de la tête détachée :
http://git-scm.com/docs/git-checkout
Ligne de commande pour le visualiser :
git branch
ou
git branch -a
vous obtiendrez le résultat suivant :
* (no branch)
master
branch1
Le * (no branch)
montre que vous êtes en tête détachée.
Vous auriez pu arriver à cet état en faisant un git checkout somecommit
etc. et il vous aurait averti avec le message suivant :
Vous êtes dans l'état 'detached HEAD' ;. Vous pouvez regarder autour de vous, faire des expérimentales et les commiter, et vous pouvez abandonner tout commit effectué dans cet état état sans impacter les branches en effectuant un autre checkout.
Si vous souhaitez créer une nouvelle branche pour conserver les commits que vous créez, vous pouvez le faire ainsi (maintenant ou plus tard) en utilisant -b avec la commande commande checkout à nouveau. Exemple :
git checkout -b nouveau_nom_de_branche
Maintenant, pour les mettre sur master:
Faites un git reflog
ou même simplement un git log
et notez vos commits. Maintenant, git checkout master
et git merge
les commits.
git merge HEAD@{1}
Éditez :
Pour ajouter, utilisez git rebase -i
non seulement pour effacer / tuer les commits dont vous n'avez pas besoin, mais aussi pour les modifier. Mentionnez simplement "edit" ; dans la liste des commits et vous pourrez modifier votre commit et ensuite émettre un git rebase --continue
pour continuer. Cela vous aurait permis de ne jamais arriver sur un HEAD détaché.