Una referencia visual de Git

Otros Idiomas:

Esta página da una referencia breve y visual para los comandos más comunes en git. Una vez que conozcas un poco sobre la forma de trabajo de git, este sitio puede fortalecer tu entendimiento. Si estás interesado en cómo se creó este sitio, mirá mi repositorio de GitHub.

Contenidos

  1. Uso Básico
  2. Convenciones
  3. Comandos en Detalle
    1. Diff
    2. Commit
    3. Checkout
    4. Commiteando con un HEAD Detachado
    5. Reset
    6. Merge
    7. Cherry Pick
    8. Rebase
  4. Notas Técnicas

Uso Básico

Los cuatro comandos de arriba copian archivos entre el directorio de trabajo, el stage (también llamado index), y la historia (en la forma de commits).

Podés usar git reset -p, git checkout -p, o git add -p en lugar de (o en combinación con) especificar archivos particulares para elegir interactivamente qué partes copiar.

También es posible, además, saltarse el stage y hacer check-out de los archivos directamente desde los archivos de commit sin hacer primero el staging.

Convenciones

En el resto del documento, usaremos gráficos de la siguiente forma.

Los commits se muestran en verde como identificadores de 5 caracteres, y apuntan a sus padres. Los branch se muestran en naranja, y apuntan a un commit en particular. El branch actual se identifica por medio de una referencia especial HEAD, que es "adjuntada" al branch. En esta imagen, se muestran los cinco últimos commits, con ed489 siendo el más reciente. main (el branch actual) apunta a este commit, mientras que stable (otro branch) apunta a un antecesor del commit de main.

Comandos en Detalle

Diff

Hay varias formas de ver las diferencias entre commits. Debajo están algunos ejemplos comunes. Cualquiera de esos comandos pueden tomar opcionalmente como argumento nombres de archivos que limiten las diferencias a los archivos nombrados.

Commit

Cuando commiteás, git crea un nuevo objeto de commit utilizando los archivos del stage y establece el padre al commit actual. Entonces apunta el presente branch a este nuevo commit. En la imagen debajo, el branch actual es main. Antes de que se ejecutase el comando, main apuntaba a ed489. Luego, un nuevo commit, f0cec, se crea, con el padre ed489, y luego main se mueve al nuevo commit.

Este mismo proceso sucede aún cuando el branch actual es un ancestro de otro. Debajo, un commit ocurre en el branch stable, el cual fue un ancestro de main, resultando en 1800b. Luego, stable no es más un ancestro de main. Para reunir las dos historias, será necesario un merge (o rebase).

A veces pueden cometerse errores en un commit, pero son fáciles de corregir con git commit --amend. Cuando usás este comando, git crea un nuevo commit con el mismo padre que el commit actual. (El commit anterior será descartado si nada más lo referencia.)

Un cuarto caso es commitear con un HEAD detachado, como explicaremos después.

Checkout

El comando checkout se usa para copiar archivos de los commits (o desde stage) al directorio de trabajo, y para opcionalmente intercambiar branches.

Cuando un nombre de archivo (y/o -p) se proporciona, git copia esos archivos desde el commit dado hacia stage y el directorio de trabajo. Por ejemplo, git checkout HEAD~ foo.c copia el archivo foo.c desde el commit llamado HEAD~ (el padre del commit actual) al directorio de trabajo, y además lo pasa a stage. (Si no se proporciona nombre de commit, los archivos se copiarán desde stage.) Notá que el branch actual no se cambia.

Cuando not se proporciona un nombre de archivo, pero la referencia es un branch (local), HEAD se mueve a ese branch (esto es, nosotros "cambiamos" ese branch), y entonces el stage y el directorio de trabajo se establecen para coincidir con los contenidos de ese commit. Cualquier archivo que existe en el nuevo commit (a47c3 debajo) se copia; cualquier archivo que exista en el anterior commit (ed489) pero no en el que está borrado; y cualquier archivo que exista será ignorado.

Cuando not se proporciona un nombre de archivo y la referencia no es un branch (local) — digamos, es un tag, un branch remoto, un identificador SHA-1, o algo como main~3 — obtenemos un branch anónimo, llamado un HEAD detachado. Esto es útil para saltar a través de la historia. Digamos que querés compilar la versión 1.6.6.1 de git. Podés hacer git checkout v1.6.6.1 (que es un tag, no un branch), compilar, instalar, y luego cambiar de nuevo a otro branch, digamos git checkout main. Sin embargo, commitear trabajos levemente diferentes con un HEAD detachado; esto es cubierto debajo.

Commiteando con un HEAD detachado

Cuando el HEAD está detachado, commit trabaja como siempre, excepto que en un branch sin nombre. (Podés pensar en esto como un branch anónimo.)

Una vez que hacés un check-out de algo más, digamos main, el commit (supuestamente) no es referenciado más por nada, y se pierde. Notá que luego de ese comando, no hay nada referenciando 2eecb.

Si, por otro lado, querés guardar este estado, podés crear un nuevo branch con nombre usando git checkout -b nombre.

Reset

El comando reset mueve el branch actual a otra posición, y opcionalmente actualiza el stage y el directorio de trabajo. Además se usa para copiar archivos desde los commits a stage sin tocar el directorio de trabajo.

Si un commit se hace sin especificar nombres de archivos, el branch actual se mueve a ese commit, y luego el stage se actualiza para coincidir con ese commmit. Si se proporciona el parámetro --hard el directorio de trabajo también se actualiza. Si se proporciona el parámetro --soft ninguno se actualiza.

Si no se proporciona un commit, por defecto se refiere a HEAD. En este caso, el branch no se mueve, pero se pasa a stage (y opcionalmente al directorio de trabajo si se especifica el parámetro --hard) y reinicia a los contenidos del último commit.

Si se proporciona un nombre de archivo (y/o -p), entonces el comando trabaja en forma similar al checkout con un nombre de archivo, excepto que solo el stage (y no el directorio de trabajo) se actualice. (Además deberías especificar el commit del cual tomar los archivos, en lugar de HEAD.)

Merge

Un merge crea un nuevo commit que incorpora cambios de otros commit. Antes de mezclar, el stage debe coincidir con el commit actual. El caso trivial es si el otro commit es un ancestro del commit actual, en cuyo caso no se hace nada adicional. El siguiente más simple es si el commit actual es un ancestro del otro commit. Esto resulta en una mezcla fast-forward. La referencia simplemente se mueve, y luego al nuevo commit se le hace check out.

De otro modo, una mezcla "real" debe ocurrir. Podés elegir otras estrategias, pero por defecto se realiza una mezcla "recursive", la cual básicamente toma el commit actual (ed489 debajo), el otro commit (33104), y su ancestro común (b325c), y realiza una mezcla de tres vías. El resultado se guarda al directorio de trabajo y al stage, y luego ocurre un commit, con un padre adicional (33104) para el nuevo commit.

Cherry Pick

El comando cherry-pick "copia" un commit, creando un nuevo commit en el branch actual con el mismo mensaje y patch que otro commit.

Rebase

Un rebase es una alternativa al merge para combinar múltiples branches. Mientras que un merge crea un solo commit con dos padres, dejando una historia no lineal, un rebase reproduce los commits del branch actual en otro, dejando una historia lineal. En esencia, esta es una forma automatizada de realizar muchas cherry-pick de una vez.

El comando de arriba toma todos los commits que existen en topic pero no en main (nombrados 169a6 y 2c33a), los reproduce dentro de main, y luego mueve el branch head al nuevo punto. Notá que los commits viejos serán recogidos por el recolector de basura si no son referenciados.

Para limitar qué tanto va hacia atrás, usá la opción --onto. El siguiente comando reproduce dentro de main el más reciente commit del branch actual desde 169a6 (exclusivo), concretamente 2c33a.

Además existe git rebase --interactive, que permite hacer cosas más complicadas que simplemente reproducir commits, descartar, reordenar, modificar o juntar commits. No hay una imagen obvia para graficar este concepto; ver git-rebase(1) para más detalles.

Notas Técnicas

Los contenidos de los archivos no se almacenan en realidad en el índice (.git/index) o en objetos commit. En su lugar, cada archivo se almacena en el objeto database (.git/objects) como un blob, identificado por su hash SHA-1. El archivo de índice lista los nombres de los archivos junto con el identificador del blob asociado, así como otros datos. Para los commits, hay un tipo de dato adicional, un tree, también identificado por su hash. Los trees se corresponden con directorios del directorio de trabajo, y contienen una lista de trees y blobs que se corresponden a cada nombre de archivo dentro de ese directorio. Cada commit almacena el identificador de su tree de alto nivel, el cual a su vez contiene todos los blobs y otros trees asociados con ese commit.

Si hacés un commit usando un HEAD detachado, el último commit es el que se referencia por algo: el reflog para HEAD. Sin embargo, esto expirará luego de un tiempo, por eso el commit eventualmente será recogido por el garbage collector, de forma similar a los commits descartados con git commit --amend o git rebase.


Copyright © 2010, Mark Lodato. Spanish translation © 2012, Lucas Videla.

Este trabajo está licenciado bajo una Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License.

¿Querés traducirlo a otro idioma?