Quelle est la différence entre HEAD ^ et HEAD ~ dans Git?

756

Lorsque je spécifie un objet de validation d'ancêtre dans Git, je suis confus entre HEAD^et HEAD~.

Les deux ont une version "numérotée" comme HEAD^3et HEAD~2.

Ils me semblent très similaires ou identiques, mais y a-t-il des différences entre le tilde et le caret?

TK.
la source
64
il est mauvais de coller des liens, je sais mais c'est la meilleure explication que j'ai trouvée et il y a une image dedans. paulboxley.com/blog/2011/06/git-caret-and-tilde
igor
4
Les liens sont particulièrement mauvais lorsqu'ils sont rompus. C'est la raison pour laquelle il est plus sûr de répondre à la question qui permet d'éviter cela en raison de la possibilité de copier-coller quelques explications :)
Samuel

Réponses:

763

Règles de base

  • Utilisez la ~plupart du temps - pour remonter un certain nombre de générations, généralement ce que vous voulez
  • Utilisation ^lors des validations de fusion - parce qu'ils ont deux parents (immédiats) ou plus

Mnémotechnique:

  • Tilde ~est presque linéaire en apparence et veut reculer en ligne droite
  • Caret ^suggère un segment intéressant d'un arbre ou d'une fourche sur la route

Tilde

La section «Spécification des révisions» de la git rev-parsedocumentation définit ~comme

<rev>~<n>, par exemplemaster~3
Un suffixe ~<n>pour un paramètre de révision signifie l'objet commit qui est l' ancêtre de n ième génération de l'objet commit nommé, ne suivant que les premiers parents. Par exemple, <rev>~3est équivalent à <rev>^^^ce qui équivaut à <rev>^1^1^1

Vous pouvez contacter les parents de n'importe quel engagement, pas seulement HEAD. Vous pouvez également revenir en arrière à travers les générations: par exemple, master~2signifie le grand-parent de la pointe de la branche principale, favorisant le premier parent lors des validations de fusion.

Caret

L'histoire de Git est non linéaire: un graphique acyclique dirigé (DAG) ou un arbre. Pour un commit avec un seul parent, rev~et rev^signifie la même chose. Le sélecteur de curseur devient utile avec les validations de fusion, car chacun est l'enfant de deux parents ou plus - et le langage des souches emprunté à la biologie.

HEAD^signifie le premier parent immédiat de la pointe de la branche actuelle. HEAD^est l'abréviation de HEAD^1, et vous pouvez également adresser HEAD^2et ainsi de suite, le cas échéant. La même section de la git rev-parsedocumentation le définit comme

<rev>^, par exemple HEAD^ ,v1.5.1^0
un suffixe ^pour un paramètre de révision signifie le premier parent de cet objet de validation. ^<n>signifie le n ème parent ([ par exemple ] <rev>^est équivalent à <rev>^1). En règle générale, <rev>^0signifie la validation elle-même et est utilisé lorsque <rev>est le nom d'objet d'un objet balise qui fait référence à un objet de validation.

Exemples

Ces spécificateurs ou sélecteurs peuvent être chaînés arbitrairement, par exemple , topic~3^2en anglais est le deuxième parent du commit de fusion qui est l'arrière-grand-parent (trois générations en arrière) de la pointe actuelle de la branche topic.

La section susmentionnée de la git rev-parsedocumentation trace de nombreux chemins à travers une histoire git théorique. Le temps s'écoule généralement vers le bas. Les validations D, F, B et A sont des validations de fusion.

Voici une illustration, par Jon Loeliger. Les deux nœuds de validation B et C sont les parents du nœud de validation A. Les validations parent sont ordonnées de gauche à droite.

G   H   I   J
 \ /     \ /
  D   E   F
   \  |  / \
    \ | /   |
     \|/    |
      B     C
       \   /
        \ /
         A

A =      = A^0
B = A^   = A^1     = A~1
C = A^2
D = A^^  = A^1^1   = A~2
E = B^2  = A^^2
F = B^3  = A^^3
G = A^^^ = A^1^1^1 = A~3
H = D^2  = B^^2    = A^^^2  = A~2^2
I = F^   = B^3^    = A^^3^
J = F^2  = B^3^2   = A^^3^2

Exécutez le code ci-dessous pour créer un référentiel git dont l'historique correspond à l'illustration citée.

#! /usr/bin/env perl

use strict;
use warnings;
use subs qw/ postorder /;
use File::Temp qw/ mkdtemp /;

my %sha1;
my %parents = (
  A => [ qw/ B C /               ],
  B => [ qw/     D E F /         ],
  C => [ qw/         F /         ],
  D => [ qw/           G H /     ],
  F => [ qw/               I J / ],
);

sub postorder {
  my($root,$hash) = @_;
  my @parents = @{ $parents{$root} || [] };
  postorder($_, $hash) for @parents;
  return if $sha1{$root};
  @parents = map "-p $sha1{$_}", @parents;
  chomp($sha1{$root} = `git commit-tree @parents -m "$root" $hash`);
  die "$0: git commit-tree failed" if $?;
  system("git tag -a -m '$sha1{$root}' '$root' '$sha1{$root}'") == 0 or die "$0: git tag failed";
}

$0 =~ s!^.*/!!;  # / fix Stack Overflow highlighting
my $repo = mkdtemp "repoXXXXXXXX";
chdir $repo or die "$0: chdir: $!";
system("git init") == 0               or die "$0: git init failed";
chomp(my $tree = `git write-tree`);      die "$0: git write-tree failed" if $?;

postorder 'A', $tree;
system "git update-ref HEAD   $sha1{A}"; die "$0: git update-ref failed" if $?;
system "git update-ref master $sha1{A}"; die "$0: git update-ref failed" if $?;

# for browsing history - http://blog.kfish.org/2010/04/git-lola.html
system "git config alias.lol  'log --graph --decorate --pretty=oneline --abbrev-commit'";
system "git config alias.lola 'log --graph --decorate --pretty=oneline --abbrev-commit --all'";

Il ajoute des alias dans le nouveau référentiel jetable uniquement pour git loletgit lola vous pouvez donc afficher l'historique comme dans

$ git lol
*   29392c8 (HEAD -> master, tag: A) A
|\
| * a1ef6fd (tag: C) C
| |
|  \
*-. \   8ae20e9 (tag: B) B
|\ \ \
| | |/
| | *   03160db (tag: F) F
| | |\
| | | * 9df28cb (tag: J) J
| | * 2afd329 (tag: I) I
| * a77cb1f (tag: E) E
*   cd75703 (tag: D) D
|\
| * 3043d25 (tag: H) H
* 4ab0473 (tag: G) G

Notez que sur votre machine, les noms des objets SHA-1 seront différents de ceux ci-dessus, mais les balises vous permettent d'adresser les commits par nom et de vérifier votre compréhension.

$ git log -1 --format=%f $(git rev-parse A^)
B
$ git log -1 --format=%f $(git rev-parse A~^3~)
I
$ git log -1 --format=%f $(git rev-parse A^2~)
F

La «Spécification des révisions» dans la git rev-parsedocumentation regorge d'informations utiles et mérite une lecture approfondie. Voir aussi Git Tools - Revision Selection dans le livre Pro Git .

Ordre des engagements des parents

Le commit 89e4fcb0dd de la propre histoire de git est un commit de fusion, comme l' git show 89e4fcb0ddindique la ligne d'en-tête Merge qui affiche les noms des objets des ancêtres immédiats.

commit 89e4fcb0dd01b42e82b8f27f9a575111a26844df
Merge: c670b1f876 649bf3a42f b67d40adbb
Author: Junio C Hamano <[email protected]>
Date:   Mon Oct 29 10:15:31 2018 +0900

    Merge branches 'bp/reset-quiet' and 'js/mingw-http-ssl' into nd/config-split […]

Nous pouvons confirmer la commande en demandant git rev-parsede montrer les parents immédiats de 89e4fcb0dd dans l'ordre.

$ git rev-parse 89e4fcb0dd^1 89e4fcb0dd^2 89e4fcb0dd^3
c670b1f876521c9f7cd40184bf7ed05aad843433
649bf3a42f344e71b1b5a7f562576f911a1f7423
b67d40adbbaf4f5c4898001bf062a9fd67e43368

L'interrogation du quatrième parent inexistant entraîne une erreur.

$ git rev-parse 89e4fcb0dd^4
89e4fcb0dd^4
fatal: ambiguous argument '89e4fcb0dd^4': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'

Si vous souhaitez extraire uniquement les parents, utilisez un joli format %P pour les hachages complets

$ git log -1 --pretty=%P 89e4fcb0dd
c670b1f876521c9f7cd40184bf7ed05aad843433 649bf3a42f344e71b1b5a7f562576f911a1f7423 b67d40adbbaf4f5c4898001bf062a9fd67e43368

ou %ppour les parents abrégés.

$ git log -1 --pretty=%p 89e4fcb0dd
c670b1f876 649bf3a42f b67d40adbb
Greg Bacon
la source
il semble que ^ puisse gérer tous les cas et on peut se demander pourquoi ~ est apparu en premier lieu. Pourquoi ne pas vous rappeler seulement comment fonctionne ^?
Sbu
c'est encore très déroutant ... en supposant que G est HEAD, donc si je fais un HEAD ^ ce serait D ... non?
Patoshi パ ト シ
12
@duckx, le graphique va de haut en bas, donc A est le commit le plus récent et G est l'un des plus anciens. Le chemin de G à D est en avant, pas en arrière, d'après ce que je peux dire.
goldenratio
@SimonBudin Je suppose que ce n'est pas très pratique à utiliser à la ^^^^^^^place de ~7, n'est-ce pas? C'est pourquoi ~est utile
YakovL
1
@AdityaVikasDevarapalli Ce serait bien sa propre question.
Greg Bacon
340

La différence entre HEAD^et HEAD~est bien décrite par l'illustration (par Jon Loeliger) trouvée sur http://www.kernel.org/pub/software/scm/git/docs/git-rev-parse.html .

Cette documentation peut être un peu obscure pour les débutants, j'ai donc reproduit cette illustration ci-dessous:

G   H   I   J
 \ /     \ /
  D   E   F
   \  |  / \
    \ | /   |
     \|/    |
      B     C
       \   /
        \ /
         A
A =      = A^0
B = A^   = A^1     = A~1
C = A^2
D = A^^  = A^1^1   = A~2
E = B^2  = A^^2
F = B^3  = A^^3
G = A^^^ = A^1^1^1 = A~3
H = D^2  = B^^2    = A^^^2  = A~2^2
I = F^   = B^3^    = A^^3^
J = F^2  = B^3^2   = A^^3^2
g_fred
la source
12
Juste une question. Comment un engagement peut-il avoir plus de deux parents? (Voir B - ses parents sont D, E et F) J'imagine que la seule façon dont un commit peut avoir deux parents est quand c'est un commit de fusion ... mais comment pouvez-vous fusionner 3 commits en même temps?
tsikov
Si je ne me trompe pas, cela peut être évident, mais je pense qu'il convient de préciser que HEAD ~ suit la branche actuelle (comme Diego Dias mentionné ci-dessous).
fibono
2
De plus, F = A^2^.
Mateen Ulhaq
2
Ainsi, ^ == ^1 == LEFTMOST PARENT, ^2 == SECOND LEFTMOST PARENTet ainsi de suite. Et ~ == ~1 == LEFTMOST PARENT, ~2 == LEFTMOST PARENTS LEFTMOST PARENT == LEFTMOST GRANDPARENT. Par extension,~2^2 == LEFTMOST GRANDPARENTS SECOND LEFTMOST PARENT
Alexander Torstling
1
@AlexanderTorstling, cela m'a été très utile. Mais que signifient gauche et droite ici?
polynomial_donut
287

Les deux ~et ^par eux-mêmes se réfèrent au parent de la validation ( ~~et les ^^deux se réfèrent à la validation des grands-parents, etc.) Mais ils ont une signification différente lorsqu'ils sont utilisés avec des nombres:

  • ~2signifie monter de deux niveaux dans la hiérarchie , via le premier parent si un commit a plus d'un parent

  • ^2signifie le deuxième parent où un commit a plus d'un parent (c'est-à-dire parce que c'est une fusion)

Ceux-ci peuvent être combinés, donc HEAD~2^3signifie HEADle troisième commit parent du commit grand-parent.

Matthew Strawbridge
la source
2
La lecture de ceci suivie de l'image de stackoverflow.com/questions/2221658/… était parfaitement logique.
kunigami
23
Cela devrait être la réponse acceptée, beaucoup plus succincte et utile que les autres.
RichVel
3
Cette réponse m'a fait distinguer le caret / tilde sans numéro et avec numéro! Je pensais que ^^c'était la même chose ^2mais ce n'est pas le cas.
Alexander Derck
278

Mes deux centimes...

entrez la description de l'image ici

Alex Janzik
la source
Et comment H=A~2^2pas H=A~2^1?
Mohammad Faisal
3
Si je l' avais deviné correctement, les commits A, B, D, Gsont sur la même branche et la commettras Dest une fusion de Get H, ayant donc deux parents. Ainsi, le commit ( H) d'une autre branche est référencé par ^2.
Mohammad Faisal
62

Voici une très bonne explication reprise textuellement de http://www.paulboxley.com/blog/2011/06/git-caret-and-tilde :

ref~est un raccourci pour ref~1et signifie le premier parent du commit. ref~2signifie le premier parent du premier parent de la validation. ref~3signifie le premier parent du premier parent du premier parent de la validation. Etc.

ref^est un raccourci pour ref^1et signifie le premier parent du commit. Mais là où les deux diffèrent, cela ref^2signifie que le deuxième parent du commit (rappelez-vous, les commits peuvent avoir deux parents quand ils sont une fusion).

Les opérateurs ^et ~peuvent être combinés.

entrez la description de l'image ici

dr_
la source
5
Merci d'avoir expliqué les différences plutôt que de publier une foule d'exemples.
Kirk Broadhurst
32

Le ^<n>format vous permet de sélectionner le nième parent du commit (pertinent dans les fusions). Le ~<n>format vous permet de sélectionner le nième commit ancêtre, toujours en suivant le premier parent. Voir la documentation de git-rev-parse pour quelques exemples.

jamessan
la source
21

Il convient de noter que git a également une syntaxe pour suivre "d'où-vous-êtes venu" / "voulez-vous-revenir-maintenant" - par exemple, HEAD@{1}fera référence à l'endroit d'où vous avez sauté vers le nouvel emplacement de validation.

Fondamentalement, les HEAD@{}variables capturent l'historique du mouvement HEAD, et vous pouvez décider d'utiliser une tête particulière en consultant les reflogs de git à l'aide de la commande git reflog.

Exemple:

0aee51f HEAD@{0}: reset: moving to HEAD@{5}
290e035 HEAD@{1}: reset: moving to HEAD@{7}
0aee51f HEAD@{2}: reset: moving to HEAD@{3}
290e035 HEAD@{3}: reset: moving to HEAD@{3}
9e77426 HEAD@{4}: reset: moving to HEAD@{3}
290e035 HEAD@{5}: reset: moving to HEAD@{3}
0aee51f HEAD@{6}: reset: moving to HEAD@{3}
290e035 HEAD@{7}: reset: moving to HEAD@{3}
9e77426 HEAD@{8}: reset: moving to HEAD@{3}
290e035 HEAD@{9}: reset: moving to HEAD@{1}
0aee51f HEAD@{10}: reset: moving to HEAD@{4}
290e035 HEAD@{11}: reset: moving to HEAD^
9e77426 HEAD@{12}: reset: moving to HEAD^
eb48179 HEAD@{13}: reset: moving to HEAD~
f916d93 HEAD@{14}: reset: moving to HEAD~
0aee51f HEAD@{15}: reset: moving to HEAD@{5}
f19fd9b HEAD@{16}: reset: moving to HEAD~1
290e035 HEAD@{17}: reset: moving to HEAD~2
eb48179 HEAD@{18}: reset: moving to HEAD~2
0aee51f HEAD@{19}: reset: moving to HEAD@{5}
eb48179 HEAD@{20}: reset: moving to HEAD~2
0aee51f HEAD@{21}: reset: moving to HEAD@{1}
f916d93 HEAD@{22}: reset: moving to HEAD@{1}
0aee51f HEAD@{23}: reset: moving to HEAD@{1}
f916d93 HEAD@{24}: reset: moving to HEAD^
0aee51f HEAD@{25}: commit (amend): 3rd commmit
35a7332 HEAD@{26}: checkout: moving from temp2_new_br to temp2_new_br
35a7332 HEAD@{27}: commit (amend): 3rd commmit
72c0be8 HEAD@{28}: commit (amend): 3rd commmit

Un exemple pourrait être que j'ai fait des commits locaux a-> b-> c-> d puis que je suis retourné en rejetant 2 commits pour vérifier mon code - git reset HEAD~2- et ensuite que je veux ramener mon HEAD sur d - git reset HEAD@{1}.

cendré
la source
17

Simplement :

  • ~ spécifie les ancêtres
  • ^ précise les parents

Vous pouvez spécifier une ou plusieurs branches lors de la fusion. Ensuite, un commit a deux parents ou plus et ^est ensuite utile pour indiquer les parents.

Supposons que vous êtes sur la branche A et vous avez deux branches: B et C .

Sur chaque branche, les trois derniers commits sont:

  • A : A1 , A2 , A3
  • B : B1 , B2 , B3
  • C : C1 , C3 , C3

Si maintenant sur la branche A, vous exécutez la commande:

git merge B C

alors vous combinez trois branches ensemble (ici votre commit de fusion a trois parents)

et

~ indique le nième ancêtre dans la première branche, donc

  • HEAD~indique A3
  • HEAD~2indique A2
  • HEAD~3indique A1

^ indique le nième parent, donc

  • HEAD^indique A3
  • HEAD^2indique B3
  • HEAD^3indique C3

La prochaine utilisation de ~ou ^côte à côte se situe dans le contexte de la validation désignée par les caractères précédents.

Remarque 1 :

  • HEAD~3est toujours égal à: HEAD~~~et à: HEAD^^^(chaque indique A1 ),

        et généralement :

  • HEAD~nest toujours égal à: HEAD~...~( n fois ~) et à: HEAD^...^( n fois ^).

Avis 2 :

  • HEAD^3n'est pas le même que HEAD^^^(le premier indique C3 et le second indique A1 ),

        et généralement :

  • HEAD^1est le même que HEAD^,
  • mais pour n > 1: HEAD^nn'est pas toujours le même que HEAD^...^( n fois ~).
simhumileco
la source
15

TLDR

~ est ce que vous voulez la plupart du temps, il fait référence aux validations passées dans la branche actuelle

^ référence les parents (git-merge crée un 2ème parent ou plus)

A ~ est toujours identique à A ^
A ~~ est toujours identique à A ^^, et ainsi de suite
A ~ 2 n'est pas identique à A ^ 2 cependant,
parce que ~ 2 est un raccourci pour ~~
tandis que ^ 2 n'est pas raccourci pour quoi que ce soit, cela signifie le 2e parent

WeakPointer
la source
11

HEAD ^^^ est le même que HEAD ~ 3, en sélectionnant le troisième commit avant HEAD

HEAD ^ 2 spécifie la deuxième tête dans un commit de fusion

knittl
la source
9
  • HEAD ~ spécifie le premier parent d'une "branche"

  • HEAD ^ vous permet de sélectionner un parent spécifique du commit

Un exemple:

Si vous voulez suivre une branche latérale, vous devez spécifier quelque chose comme

master~209^2~15
Diego Dias
la source
5

exemple réel de la différence entre HEAD ~ et HEAD ^

TETE ^ VS TETE ~

Anas Alpure
la source
0

Autrement dit, pour le premier niveau de filiation (ascendance, héritage, lignée, etc.) HEAD ^ et HEAD ~ pointent tous deux vers le même commit, qui est (situé) un parent au-dessus de HEAD (commit).

De plus, HEAD ^ = HEAD ^ 1 = HEAD ~ = HEAD ~ 1. Mais HEAD ^^! = HEAD ^ 2! = HEAD ~ 2. Pourtant HEAD ^^ = HEAD ~ 2. Continuer à lire.

Au-delà du premier niveau de filiation, les choses deviennent plus délicates, surtout si la branche de travail / branche principale a eu des fusions (d'autres branches). Il y a aussi la question de la syntaxe avec le curseur, HEAD ^^ = HEAD ~ 2 (ils sont équivalents) MAIS HEAD ^^! = HEAD ^ 2 (ce sont deux choses complètement différentes).

Chaque / le caret fait référence au premier parent de la HEAD, c'est pourquoi les carets enchaînés sont équivalents aux expressions tilde, car ils se réfèrent aux premiers parents du premier parent (premier parent), etc., etc. basés strictement sur le nombre de carets connectés ou sur le nombre suivant le tilde (de toute façon, ils signifient tous deux la même chose), c'est-à-dire rester avec le premier parent et remonter x générations.

HEAD ~ 2 (ou HEAD ^^) fait référence à la validation qui est deux niveaux d'ascendance au-dessus / au-dessus de la validation actuelle (la HEAD) dans la hiérarchie, ce qui signifie la validation des grands-parents de la HEAD.

HEAD ^ 2, d'autre part, ne fait PAS référence à la validation du deuxième parent du premier parent, mais simplement à la validation du deuxième parent. En effet, le signe d'insertion signifie le parent du commit, et le nombre suivant signifie à quel parent le commit est référencé (le premier parent, dans le cas où le caret n'est pas suivi d'un nombre [car il est un raccourci pour le nombre étant 1, c'est-à-dire le premier parent]). Contrairement au signe d'insertion, le nombre qui suit ensuite n'implique pas un autre niveau de hiérarchie vers le haut, mais il implique plutôt combien de niveaux latéralement, dans la hiérarchie, il faut aller trouver le bon parent (commit). Contrairement au nombre dans une expression tilde, il ne s'agit que d'un parent dans la hiérarchie, quel que soit le nombre (immédiatement) précédant le curseur. Au lieu de vers le haut, le signe d'insertion '

Donc HEAD ^ 3 est égal au troisième parent du commit HEAD (PAS l'arrière-grand-parent, ce qui serait HEAD ^^^ AND HEAD ~ 3 ...).

Sean Tank Garvey
la source
-1

~ cela signifie parent.

^ s'il a des parents de deux ou plus, comme merge commit, nous pouvons sélectionner le deuxième parent ou un autre.

donc si une seule chose comme (HEAD ~ ou HEAD ^), cela a les mêmes résultats.

Margaux
la source