Skip to main content

Modelo mental de git

Por fin conseguí hacerme un modelo mental de git que me permite hacer de todo sin necesidad de google ni esquemas ni Stack Overflow ;P. Y ésta es mi chuleta / "volcado de memoria". Parece que no hay muchas cosas así en español; a ver si le sirve a alguien.

La idea era mantenerlo muy corto y al grano — como una chuleta, vaya. Asumo que sabes de qué va en general git y qué son cosas como un puntero y una lista simplemente enlazada. Si hay interés lo expandiré en algo más "tutorial".




Contexto básico: la historia es un árbol de nodos (commits) simplemente enlazados de hijos a padres. Un hijo contiene un puntero al padre; los padres no saben nada de los hijos. Los nodos son accesibles mediante sus SHA o mediante punteros: HEAD, o branch heads, o tags. Esta estructura tiene 2 implicaciones importantes que explican el comportamiento de git en algunos casos que podrían parecer extraños:
  • El SHA de un nodo ha sido calculado con el nodo apuntando a un padre; así que incluso cambiar el padre de un nodo implica renovar el nodo y todos sus hijos. (es automático, pero puede sorprender)
  • Un nodo que no es referenciado por ningún puntero queda "inaccesible" y será eventualmente borrado (garbage collected) por git. (pero el reflog puede ser una red de seguridad)

Una branch head es un puntero a un nodo de la historia. Una rama (branch) es la cadena de nodos desde la branch head hasta la raíz del árbol (simplemente enlazada, de hijos a padres!). Coloquialmente se suele llamar branch a la branch head, aunque a veces la distinción es importante.

HEAD es como el pulgar para marcar la página en el libro de la historia. Es generalmente usado para tener un nodo por defecto cuando no se da explícitamente un nodo a algún comando. Cuando HEAD apunta a una branch head (es lo normal), esa branch está activa. Cuando HEAD apunta directamente a un nodo, tenemos una DETACHED HEAD.

Una DETACHED HEAD es "peligrosa" porque cualquier nuevo commit creado en ella quedará inaccesible al mover HEAD a otro sitio. La solución para no perder nuevos commits es crear una branch – con lo cual HEAD también deja de estar DETACHED.

Index, o staging, o cache: es una instantánea del directorio de trabajo: implementa toda la historia desde el principio hasta un commit dado.

Y ya podemos explicar claramente lo que pasa al hacer un commit:

  • cogemos la diferencia entre staging y HEAD
  • creamos un nuevo nodo para la historia conteniendo esa diferencia
  • el nuevo nodo será el hijo de HEAD, así que hacemos que ese nodo apunte a HEAD
  • hacemos que HEAD apunte al nuevo nodo
En otras palabras: HEAD es un puntero a la cola de una lista enlazada (branch), y hacer un commit es añadir un nuevo nodo a la cola de la lista enlazada, con el movimiento de punteros que ello conlleva. (Ojo, todo esto es la simplificación para el caso de una detached head; si HEAD apunta a una branch head, el proceso es el mismo, pero la branch head se mueve junto con HEAD).



git checkout <commit> 
Mueve HEAD (y NO la rama posiblemente apuntada por HEAD) a ese commit, y actualiza el index y el directorio de trabajo.
Si <commit> es una branch head, entonces HEAD apuntará a esa branch head, con lo que la rama será activa. Ésta es la otra cura para una DETACHED HEAD.

git reset <commit>
Mueve la branch head apuntada por HEAD a ese commit, y (por defecto (--mixed)) deja staging tal como era en ese commit, pero no toca el directorio de trabajo (para eso usar --hard).
EJEMPLO: git reset HEAD~1 es el primer paso para “enmendar el último commit” (lo que hacen varios comandos al añadir --amend). Simplemente añade tus cambios al staging y haz commit. Enmendado.

git branch sólo trata con branch heads; no toca HEAD, ni nada más.

Comments