HEAD~N y HEAD^Una rama (branch) en Git no es una copia de archivos ni una estructura compleja: es, literalmente, un archivo de texto de 41 bytes que contiene el hash de un commit. Vive en .git/refs/heads/nombre-de-la-rama.
Cuando se hace un nuevo commit estando en una rama, Git:
Eso es todo. Por eso crear una rama en Git es prácticamente instantáneo: no se copia nada, solo se crea un archivo de 41 bytes apuntando al commit actual.
HEAD es una referencia especial que indica en qué rama (o en
qué commit, si no hay rama activa) está el directorio de trabajo en
este momento. Normalmente HEAD no apunta directamente a un
commit, sino a una rama, y esa rama apunta al commit:
HEAD -> refs/heads/main -> a1b2c3d (commit)
Cuando HEAD apunta directamente a un commit en vez de a una
rama, se dice que el repositorio está en estado de HEAD
separado (detached HEAD). Esto ocurre, por ejemplo, al hacer
git checkout sobre un commit específico en vez de sobre una
rama. No es un error ni un estado peligroso, pero conviene saber que
los commits hechos en ese estado pueden perderse si no se les asigna
una rama antes de moverse a otro lado, porque ninguna rama los
retiene.
Como cada commit conoce a su padre (o padres), el historial completo forma un grafo dirigido acíclico: dirigido porque cada enlace va de un commit a su padre, y acíclico porque nunca hay ciclos —un commit no puede ser antepasado de sí mismo—. Las ramas son simplemente distintos puntos de entrada a ese mismo grafo.
A---B---C (main)
\
D---E (feature)
Aquí main y feature comparten los commits A y
B; a partir de ahí, cada rama tiene su propio camino.
HEAD~N y HEAD^ ¶Git ofrece una notación compacta para referirse a commits relativos a uno dado, en vez de escribir hashes completos.
HEAD~1El padre del commit actual. Equivalente a HEAD^.
HEAD~2El abuelo del commit actual (el padre del padre). Equivalente a
HEAD^^.
HEAD~NEl antepasado que está N generaciones atrás, siguiendo siempre el primer padre en cada commit de fusión.
HEAD^El primer padre del commit actual.
HEAD^2El segundo padre del commit actual. Solo tiene sentido en un
commit de fusión, que tiene más de un padre. HEAD^ y
HEAD^1 son equivalentes; HEAD^2 es distinto y se
refiere a la otra rama que se fusionó.
La diferencia entre ~ y ^ importa solo cuando hay
commits de fusión de por medio: ~N siempre sigue el camino
"principal" N pasos, mientras que ^N elige entre padres en un
único paso. Se pueden combinar: HEAD~2^2 es el segundo padre
del commit que está dos generaciones atrás.
Esta misma notación relativa funciona con cualquier referencia, no
solo con HEAD: por ejemplo, main~3 o
a1b2c3d~1.
Para referirse a un conjunto de commits, en vez de a uno solo, Git usa dos operadores:
A..BTodos los commits alcanzables desde B pero no desde
A. Es el rango más común, y responde a la pregunta "¿qué
commits tiene B que A no tiene?".
A...BLa diferencia simétrica: los commits alcanzables desde
A o desde B, pero no desde ambos. Es decir, todo lo
que diverge entre las dos ramas, en cualquier dirección.
Por ejemplo, git log main..feature muestra los commits que
existen en feature y todavía no llegaron a main: útil
para revisar qué se está a punto de fusionar.
ORIG_HEADGuarda automáticamente el valor anterior de HEAD antes de
operaciones que lo mueven de forma drástica, como reset o
merge. Sirve de red de seguridad: git reset ORIG_HEAD
deshace el movimiento anterior.
@{-1}Se refiere a la rama en la que se estaba justo antes de la última vez que se cambió de rama. Equivalente informal a "cd -" en la línea de comandos, pero para ramas.
Entender esta notación es lo que permite escribir comandos como
git reset --hard HEAD~2 o git branch nueva-rama
HEAD~3 sin tener que copiar hashes de commits a mano.