Notes préliminaires
L'observation ici est que, après avoir commencé à travailler dans branch1
(en oubliant ou en ne réalisant pas qu'il serait bon de passer d'abord à une autre branche branch2
), vous exécutez:
git checkout branch2
Parfois Git dit "OK, tu es sur branch2 maintenant!" Parfois, Git dit "Je ne peux pas faire ça, je perdrais certains de vos changements."
Si Git ne vous laisse pas le faire, vous devez valider vos modifications, pour les enregistrer quelque part de façon permanente. Vous voudrez peut-être utiliser git stash
pour les enregistrer; c'est l'une des choses pour lesquelles il est conçu. Notez que git stash save
ou signifiegit stash push
en fait "Validez toutes les modifications, mais sur aucune branche du tout, puis supprimez-les de l'endroit où je suis maintenant." Cela permet de basculer: vous n'avez désormais plus de modifications en cours. Vous pouvez ensuite git stash apply
les après avoir changé.
Barre latérale: git stash save
c'est l'ancienne syntaxe; git stash push
a été introduit dans Git version 2.13, pour résoudre certains problèmes avec les arguments git stash
et permettre de nouvelles options. Les deux font la même chose, lorsqu'ils sont utilisés de manière simple.
Vous pouvez arrêter de lire ici, si vous le souhaitez!
Si Git ne vous laisse pas changer, vous avez déjà un remède: utilisez git stash
ou git commit
; ou, si vos modifications sont faciles à recréer, utilisez-les git checkout -f
pour la forcer. Cette réponse concerne le moment où Git vous autorisera git checkout branch2
même si vous avez commencé à apporter des modifications. Pourquoi ça marche parfois , et pas d' autres fois?
La règle ici est simple dans un sens, et compliquée / difficile à expliquer dans un autre:
Vous pouvez changer de branche avec des modifications non validées dans l'arborescence de travail si et seulement si ladite commutation ne nécessite pas d'altérer ces modifications.
C'est-à-dire et veuillez noter que cela est encore simplifié; il y a des cas d'angle extra-difficiles avec des git add
s, git rm
s et autres par étapes - supposons que vous soyez allumé branch1
. A git checkout branch2
devrait faire ceci:
- Pour chaque fichier qui se trouve dans
branch1
et non dans branch2
, 1 supprimez ce fichier.
- Pour chaque fichier qui est dedans
branch2
et pas dedans branch1
, créez ce fichier (avec le contenu approprié).
- Pour chaque fichier qui se trouve dans les deux branches, si la version de
branch2
est différente, mettez à jour la version de l'arborescence de travail.
Chacune de ces étapes pourrait encombrer quelque chose dans votre arbre de travail:
- La suppression d'un fichier est "sûre" si la version dans l'arborescence est la même que la version validée dans
branch1
; c'est "dangereux" si vous avez apporté des modifications.
- Créer un fichier tel qu'il apparaît
branch2
est "sûr" s'il n'existe pas maintenant. 2 Il est «dangereux» s'il existe maintenant mais a un «mauvais» contenu.
- Et bien sûr, remplacer la version d'arbre de travail d'un fichier par une version différente est "sûr" si la version d'arbre de travail est déjà validée
branch1
.
La création d'une nouvelle branche ( git checkout -b newbranch
) est toujours considérée comme "sûre": aucun fichier ne sera ajouté, supprimé ou modifié dans l'arborescence de travail dans le cadre de ce processus, et l'index / zone de transfert est également intact. (Attention: il est sûr de créer une nouvelle branche sans changer le point de départ de la nouvelle branche; mais si vous ajoutez un autre argument, par exemple git checkout -b newbranch different-start-point
, cela pourrait devoir changer les choses, pour passer à different-start-point
. Git appliquera alors les règles de sécurité de la caisse comme d'habitude .)
1 Cela nécessite que nous définissions ce que signifie pour un fichier d'être dans une branche, ce qui à son tour nécessite de définir correctement le mot branche . (Voir aussi Qu'entendons-nous exactement par "branche"? ) Ici, ce que je veux vraiment dire, c'est le commit auquel le nom de branche se résout: un fichier dont le chemin est dans if produit un hachage. Ce fichier n'est pas dans si vous obtenez un message d'erreur à la place. L'existence d'un chemin dans votre index ou votre arbre de travail n'est pas pertinente pour répondre à cette question particulière. Ainsi, le secret ici est d'examiner le résultat de chaqueP
branch1
git rev-parse branch1:P
branch1
P
git rev-parse
branch-name:path
. Cela échoue parce que le fichier est "dans" au plus une branche, ou nous donne deux ID de hachage. Si les deux ID de hachage sont identiques , le fichier est le même dans les deux branches. Aucun changement n'est requis. Si les ID de hachage diffèrent, le fichier est différent dans les deux branches et doit être modifié pour changer de branche.
L'idée clé ici est que les fichiers dans les validations sont gelés pour toujours. Les fichiers que vous éditez ne sont évidemment pas figés. Nous ne nous penchons, au moins initialement, que sur les décalages entre deux commits gelés. Malheureusement, nous - ou Git - devons également traiter les fichiers qui ne figurent pas dans la validation à partir de laquelle vous allez basculer et qui se trouvent dans la validation vers laquelle vous allez basculer. Cela conduit aux complications restantes, car les fichiers peuvent également exister dans l'index et / ou dans l'arborescence de travail, sans avoir à exister ces deux validations gelées particulières avec lesquelles nous travaillons.
2 Il peut être considéré comme "sorte de coffre-fort" s'il existe déjà avec le "bon contenu", de sorte que Git n'a pas à le créer après tout. Je me souviens au moins de certaines versions de Git autorisant cela, mais les tests montrent maintenant qu'il est considéré comme "dangereux" dans Git 1.8.5.4. Le même argument s'appliquerait à un fichier modifié qui se trouve être modifié pour correspondre à la branche to-be-switch-to. Encore une fois, le 1.8.5.4 dit simplement "serait écrasé", cependant. Voir également la fin des notes techniques: ma mémoire peut être défectueuse car je ne pense pas que les règles de l'arborescence de lecture ont changé depuis que j'ai commencé à utiliser Git dans la version 1.5.
Est-il important que les modifications soient échelonnées ou non?
Oui, à certains égards. En particulier, vous pouvez mettre en place une modification, puis "dé-modifier" le fichier d'arborescence de travail. Voici un fichier en deux branches, différent dans branch1
et branch2
:
$ git show branch1:inboth
this file is in both branches
$ git show branch2:inboth
this file is in both branches
but it has more stuff in branch2 now
$ git checkout branch1
Switched to branch 'branch1'
$ echo 'but it has more stuff in branch2 now' >> inboth
À ce stade, le fichier d'arbre de travail inboth
correspond à celui de branch2
, même si nous sommes allumés branch1
. Cette modification n'est pas mise en scène pour la validation, ce qui est git status --short
illustré ici:
$ git status --short
M inboth
L'espace-alors-M signifie "modifié mais pas par étapes" (ou plus précisément, la copie d'arbre de travail diffère de la copie par étapes / index).
$ git checkout branch2
error: Your local changes ...
OK, passons maintenant à la copie de l'arborescence de travail, qui, nous le savons déjà, correspond également à la copie branch2
.
$ git add inboth
$ git status --short
M inboth
$ git checkout branch2
Switched to branch 'branch2'
Ici, les copies mises en scène et de travail correspondaient toutes les deux à ce qui se trouvait branch2
, donc le paiement a été autorisé.
Essayons une autre étape:
$ git checkout branch1
Switched to branch 'branch1'
$ cat inboth
this file is in both branches
La modification que j'ai apportée est maintenant perdue dans la zone de transit (car le paiement est écrit dans la zone de transit). Ceci est un peu un cas d'angle. Le changement n'est pas parti, mais le fait que je l'avais mis en scène, est parti.
Préparons une troisième variante du fichier, différente de chaque copie de branche, puis définissons la copie de travail pour qu'elle corresponde à la version actuelle de la branche:
$ echo 'staged version different from all' > inboth
$ git add inboth
$ git show branch1:inboth > inboth
$ git status --short
MM inboth
Les deux M
s ici signifient: le fichier intermédiaire diffère du HEAD
fichier et le fichier d'arborescence de travail diffère du fichier intermédiaire. La version de l'arbre de travail correspond à la version branch1
(aka HEAD
):
$ git diff HEAD
$
Mais git checkout
ne permettra pas le paiement:
$ git checkout branch2
error: Your local changes ...
Définissons la branch2
version comme version de travail:
$ git show branch2:inboth > inboth
$ git status --short
MM inboth
$ git diff HEAD
diff --git a/inboth b/inboth
index ecb07f7..aee20fb 100644
--- a/inboth
+++ b/inboth
@@ -1 +1,2 @@
this file is in both branches
+but it has more stuff in branch2 now
$ git diff branch2 -- inboth
$ git checkout branch2
error: Your local changes ...
Même si la copie de travail actuelle correspond à celle de branch2
, le fichier intermédiaire ne le fait pas, donc a git checkout
perdrait cette copie et git checkout
est rejeté.
Notes techniques - uniquement pour les fous curieux :-)
L' indice d' implémentation sous-jacent de tout cela est l' indice de Git . L'index, également appelé "zone de transit", est l'endroit où vous générez le prochain commit: il commence par correspondre au commit actuel, c'est-à-dire, tout ce que vous avez extrait maintenant, puis à chaque fois que vous git add
un fichier, vous remplacez la version d'index avec tout ce que vous avez dans votre arbre de travail.
N'oubliez pas que l' arborescence est l'endroit où vous travaillez sur vos fichiers. Ici, ils ont leur forme normale, plutôt qu'une forme spéciale uniquement utile à Git comme ils le font dans les commits et dans l'index. Vous extrayez donc un fichier d' une validation, via l'index, puis dans l'arborescence de travail. Après l'avoir changé, vous git add
le mettez à l'index. Il y a donc en fait trois emplacements pour chaque fichier: la validation actuelle, l'index et l'arbre de travail.
Lorsque vous exécutez git checkout branch2
, ce que Git fait sous les couvertures, c'est de comparer le commit de tipbranch2
à ce qui se trouve à la fois dans le commit actuel et dans l'index maintenant. Tout fichier qui correspond à ce qui existe maintenant, Git peut laisser seul. Tout est intact. N'importe quel fichier identique dans les deux validations , Git peut également rester seul - et ce sont ceux qui vous permettent de changer de branche.
Une grande partie de Git, y compris la commutation de validation, est relativement rapide en raison de cet index. Ce qui est réellement dans l'index n'est pas chaque fichier lui-même, mais plutôt le hachage de chaque fichier . La copie du fichier lui-même est stockée en tant que ce que Git appelle un objet blob , dans le référentiel. Cela est similaire à la façon dont les fichiers sont également stockés dans les validations: les validations ne contiennent pas réellement les fichiers , elles conduisent simplement Git à l'ID de hachage de chaque fichier. Git peut donc comparer les ID de hachage - actuellement des chaînes de 160 bits - pour décider si les validations X et Y ont le même fichier ou non. Il peut ensuite comparer ces ID de hachage à l'ID de hachage dans l'index également.
C'est ce qui mène à tous les cas de coin excentriques ci-dessus. Nous avons des commits X et Y qui ont tous deux un fichier path/to/name.txt
, et nous avons une entrée d'index pour path/to/name.txt
. Peut-être que les trois hachages correspondent. Peut-être que deux d'entre eux correspondent et un ne correspond pas. Peut-être que les trois sont différents. Et, nous pourrions aussi avoir another/file.txt
ce n'est qu'en X ou seulement en Y et est ou n'est pas dans l'index maintenant. Chacun de ces différents cas nécessite sa propre considération: Git doit-il copier le fichier de la validation vers l'index, ou le supprimer de l'index, pour passer de X à Y ? Si oui, il doit égalementcopiez le fichier dans l'arborescence ou supprimez-le de l'arborescence. Et si tel est le cas, les versions d'index et d'arborescence de travail devraient mieux correspondre à au moins une des versions validées; sinon Git va encombrer certaines données.
(Les règles complètes pour tout cela sont décrites dans, non pas la git checkout
documentation comme vous pouvez vous y attendre, mais plutôt la git read-tree
documentation, dans la section intitulée "Two Tree Merge" .)
git checkout -m
, qui fusionne votre arbre de travail et les modifications d'index dans la nouvelle caisse.