web-dev-qa-db-fra.com

Git Rebase Conflict: Qui est HEAD?

J'ai ce projet où le dépôt distant a la branche principale de développement, et j'ai une fourchette contenant la branche expérimentale. Je dois rebase passer de la branche de développement à ma branche expérimentale avant de pousser vers ma fourchette. Donc ça va comme:

git checkout experimentalbranch
git fetch remoterepo
git rebase remoterepo/developmentbranch

À ce moment-là, j'ai frappé des conflits. Cependant, je ne suis au courant d'aucune de ces modifications (je rebase des semaines de modifications, car elles n'ont pas fusionné mes modifications immédiatement). De plus, c'est ma première fois que je fais rebase. Je suis plus habitué à merge.

En fusion, c'est généralement comme <<LOCAL||REMOTE>> pour merge, ce qui semble très intuitif. Mais dans rebase, c'est <<HEAD||COMMIT MESSAGE>>. Qui est HEAD? Est-ce le HEAD de la branche de développement? Est-ce le dernier code dans la branche de développement ou ailleurs?

51
Joseph

TL; DR (ajouté en mai 2018)

Le tout est fondamentalement au moins un peu déroutant, car Git laisse transparaître son fonctionnement interne.

Notez que les cas qui nous concernent ici se produisent lorsque vous exécutez:

git checkout somebranch; git rebase Origin/their-branch

ou similaire. Le rebase s'est arrêté temporairement pour vous forcer à résoudre un conflit de fusion, après quoi vous êtes censé git add le conflit résolu et exécutez git rebase --continue. (Si vous utilisez un outil de fusion avec git mergetool, ou une interface graphique sophistiquée, cette interface peut faire tout ou partie de cela pour vous d'une autre manière, mais en dessous, c'est git adding les fichiers résolus et exécutant git rebase --continue.)

Au tout début, le HEAD commit est leur branche , de sorte que si vous utilisez git checkout --ours ou git checkout --theirs, --ours signifie le leur - la validation finale de Origin/their-branch-tandis que --theirs signifie le vôtre , le premier commit que vous rebasiez. Il s'agit de la confusion quotidienne de Git (voir Quelle est la signification précise de "nôtre" et "leur" dans git? ) et ce n'est pas ce qui a conduit à la question d'origine.

Plus tard, cependant, le HEAD commit est en fait une sorte de mélange . C'est le résultat de la copie d'un certain nombre de vos validations au sommet de leur dernière validation . Vous obtenez maintenant un conflit entre votre propre série de validations partiellement construite et votre propre version originale valide. La source de ce conflit est généralement quelque chose "qu'ils" ont fait (quelque chose qui a changé en cours de route dans Origin/their-branch). Vous devez encore résoudre ce conflit. Lorsque vous le faites, vous pouvez voir le même conflit se reproduire dans les validations ultérieures.

Encore une fois, HEAD ou local ou --ours est un commit que rebase a construit en combinant vos changements et leurs changements , et l'autre commit (remote ou >>>>>>> ou --theirs) est votre propre commit, que rebase essaie de copier sur HEAD.

Plus long

Lors de la fusion (y compris le rebasage, qui est un cas particulier de "fusion" répétée en interne), deux "têtes" (deux embouts spécifiques) sont impliquées. Appelons ces your-branch et Origin/their-branch:

              G - H --------      <-- HEAD=your-branch
            /               \
... - E - F                   M   <-- desired merge commit [requires manual merge]
            \               /
              I - J - K - L       <-- Origin/their-branch

Ce point est souvent (et sans surprise) déroutant, bien que lorsqu'il est étiqueté comme cela, il soit assez clair.

Pour aggraver les choses, cependant, git utilise --ours et --theirs pour faire référence aux deux validations de tête lors d'une fusion, "la nôtre" étant celle sur laquelle vous étiez (commit H) lorsque vous avez exécuté git merge, et "leur" étant, eh bien, le leur (commit L). Mais lorsque vous effectuez un rebase, les deux têtes sont inversées, de sorte que "la nôtre" est la tête sur laquelle vous rebasez, c'est-à-dire leur code mis à jour, et "la leur" est le commit que vous rebasez actuellement, c'est-à-dire votre propre code.

En effet, rebase utilise en fait une série d'opérations de sélection. Vous commencez avec à peu près la même image:

              G - H           <-- HEAD=your-branch
            /
... - E - F
            \
              I - J - K - L   <-- Origin/their-branch

Ce que git doit faire ici est de copier l'effet des validations G et H, c'est-à-dire git cherry-pick commit G, puis recommencez avec commit H. Mais pour ce faire, git doit basculer pour valider L d'abord, en interne (en utilisant le mode "HEAD détaché"):

              G - H           <-- your-branch
            /
... - E - F
            \
              I - J - K - L   <-- HEAD, Origin/their-branch

Maintenant, il peut démarrer l'opération de rebase en comparant les arbres pour les validations F et G (pour voir ce que vous avez modifié), puis en comparant F vs L ( pour voir si une partie de votre travail est déjà dans L) et en prenant toutes les modifications qui ne sont pas déjà dans L et ajoutez-le. Il s'agit d'une opération de "fusion", en interne.

              G - H           <-- your-branch
            /
... - E - F                   G'   <-- HEAD
            \               /
              I - J - K - L   <-- Origin/their-branch

Si la fusion ne se passe pas bien, HEAD est toujours laissé à commit L (car commit G' n'existe pas encore). Ainsi, oui, HEAD est à la tête de leur branche de développement - du moins, c'est le cas actuellement.

Une fois que la copie de G existe, HEAD se déplace vers G' et git tente de copier les modifications depuis H, de la même manière (diff G vs H, puis diff F vs G', et fusionner les résultats):

              G - H           <-- your-branch
            /
... - E - F                   G' - H'   <-- HEAD
            \               /
              I - J - K - L   <-- Origin/their-branch

Encore une fois, si la fusion échoue et a besoin d'aide, vous vous retrouvez avec HEAD pointant vers G' au lieu de H' comme H' n'existe pas encore.

Une fois la fusion réussie et validée G' et H' existe-t-il , git supprime l'étiquette your-branch à partir de la validation H, et indique que la validation H' au lieu:

              G - H
            /
... - E - F                   G' - H'   <-- HEAD=your-branch
            \               /
              I - J - K - L   <-- Origin/their-branch

et vous êtes maintenant rebasé et HEAD est à nouveau ce que vous attendez. Mais lors du rebase, HEAD est soit leur branch-tip (commit L), soit l'un des nouveaux commits copiés et ajoutés après leur branch-tip; et --ours signifie que la branche est agrandie à la fin de L tandis que --theirs signifie que le commit est copié depuis (G ou H ci-dessus).

(Il s'agit essentiellement de git exposant le mécanisme brut de comment il fait ce qu'il fait, ce qui se produit assez souvent dans git.)

78
torek

Définitions

Dans cette section, nous allons voir les déficiences qui nous sont demandées en réponse:

Qui est HEAD?

HEAD: le commit actuel de votre dépôt est activé. La plupart du temps HEAD pointe vers le dernier commit de votre branche, mais cela n'a pas à être le cas. HEAD signifie vraiment "ce que mon repo pointe actuellement à".

Dans le cas où la validation HEAD ne fait référence à la pointe d'aucune branche, cela s'appelle un "detached head ".

Est-ce le HEAD de la branche de développement?

Au moment où une fusion ou un rebase est effectué, le HEADpasse immédiatement pour pointer vers le commit créé ou refactorisé et va donc pointer vers la branche développement .

Dans git bash nous pouvons voir la situation HEAD, listant commit:

# Normal commit list
git log
# List of commit in a single line
git log --oneline 
# All commits graphically-linear (Recommended as alias)
git log --all --graph --decorate --oneline

Entraine toi

Dans cette section, nous verrons le _how_ fonctionne quelques actions effectuées par l'utilisateur

Lorsque l'utilisateur procède à:

# Command to change from the branch to the current one to experimentalbranch
git checkout experimentalbranch
# Command that traverses the typical workflow to synchronize its local repository with the main branch of the central repository (remoterepo)
git fetch remoterepo
# git fetch Origin
# git fetch Origin branch:branch
# With the command git rebase, you can take all the changes confirmed in one branch (remoterepo), and reapply them over another developmentbranch
git rebase remoterepo/developmentbranch

À ce moment-là, j'ai frappé des conflits. Cependant, je ne connais aucun de ces changements (je rebase des semaines de changements, car ils n'ont pas fusionné mes changements immédiatement). De plus, c'est ma première fois que je rebase. Je suis plus habitué à fusionner.

L'union des succursales se fait de deux manières:

  • git merge

  • git rebase.

Remarque :

Pour les exemples, nous utiliserons l'arbre suivant :

* a122f6d (HEAD -> remoterepo) Commit END
* 9667bfb Commit MASTER
| * b9bcaf0 (Origin/experimentalbranch, experimentalbranch) Commit 3
| * 110b2fb Commit 2
| * e597c60 Commit 1
|/
* 0e834f4 (Origin/remoterepo) First commit

git merge

La forme la plus connue est git merge, qui effectue une fusion vers trois bandes entre les deux derniers instantanés de chaque branche et l'ancêtre commun aux deux, créant un nouveau commit avec des changements mixtes.

Par exemple :

git checkout remoterepo
git merge experimentalbranch

Cela nous produirait:

*   003e576 (HEAD -> remoterepo) Merge branch 'experimentalbranch' in remoterepo
|\
| * b9bcaf0 (Origin/experimentalbranch, experimentalbranch) Commit 3
| * 110b2fb Commit 2
| * e597c60 Commit 1
* | a122f6d Commit END
* | 9667bfb Commit MASTER
|/
* 0e834f4 (Origin/remoterepo) First commit

git rebase

git rebase essentiellement ce qu'il fait c'est de collecter un par un les changements confirmés dans une branche, et de les réappliquer sur une autre.

L'utilisation de rebase peut nous aider éviter les conflits chaque fois qu'elle est appliquée à des validations locales et qui n'ont été téléchargées vers aucun référentiel distant. Si vous ne faites pas attention à ce dernier et qu'un partenaire utilise les modifications affectées, vous aurez sûrement des problèmes puisque ces types de conflits sont généralement difficiles à réparer.

Par exemple :

git checkout remoterepo
git rebase experimentalbranch


* f8a74be (HEAD -> remoterepo) Commit END
* 4293e9d Commit MASTER
* b9bcaf0 (Origin/experimentalbranch, experimentalbranch) Commit 3
* 110b2fb Commit 2
* e597c60 Commit 1
* 0e834f4 (Origin/remoterepo) First commit

Quelle est l'origine?

Origin: le nom par défaut que git donne à votre dépôt distant principal. Votre boîte a son propre référentiel, et vous pousserez probablement vers un référentiel distant vers lequel vous et tous vos collègues pousserez. Ce dépôt à distance est presque toujours appelé Origin, mais ce n'est pas obligatoire.