Git refs: An overview
Aprender los conceptos básicos de Git con este tutorial centrado en el espacio.
Si conoces las diversas formas de hacer referencia a una confirmación, aprovecharás toda la utilidad de estos comandos. En este capítulo, profundizaremos en el funcionamiento interno de comandos comunes como git checkout
, git branch
y git push
al explorar los muchos métodos que existen para hacer referencia a una confirmación.
También aprenderemos a revivir confirmaciones aparentemente “perdidas” accediendo a ellas a través del mecanismo de registro de referencias de Git.
Hashes
La forma más directa de hacer referencia a una confirmación es a través de su hash SHA-1, que funciona como el identificador único de cada confirmación. Puedes consultar el hash de todas tus confirmaciones con git log
.
commit 0c708fdec272bc4446c6cabea4f0022c2b616eba Author: Mary Johnson Date: Wed Jul 9 16:37:42 2014 -0500 Some commit message
Para pasar la confirmación a otros comandos de Git, solo necesitas introducir suficientes caracteres para identificar la confirmación de forma única. Por ejemplo, puedes consultar la confirmación anterior con git show
, ejecutando el siguiente comando:
git show 0c708f
A veces es necesario resolver una rama, etiqueta u otra referencia indirecta en el hash de confirmación correspondiente. Para hacerlo, puedes usar el comando git rev-parse
. El siguiente código devuelve el hash de la confirmación a la que apunta la rama main
:
Material relacionado
Cómo mover un repositorio de Git completo
VER LA SOLUCIÓN
Aprende a usar Git con Bitbucket Cloud
git rev-parse main
Este comando es particularmente útil cuando se escriben scripts personalizados que aceptan una referencia de confirmación. En lugar de analizar la referencia de confirmación manualmente, puedes dejar que git rev-parse
normalice la entrada por ti.
Referencias
Una referencia (o “ref”) es una forma indirecta de referirse a una confirmación. Puedes considerarla como un alias fácil de usar para un hash de confirmación. Este es el mecanismo interno de Git para representar ramas y etiquetas.
Las referencias se almacenan como archivos de texto normales en el directorio .git/refs
, donde el archivo que lleva por nombre .git
se suele llamar .git
. Para consultar las referencias en uno de tus repositorios, ve a .git/refs
. Deberías ver la siguiente estructura, pero contendrá diferentes archivos según las ramas, etiquetas y remotos que tengas en el repositorio:
.git/refs/ heads/ main some-feature remotes/ origin/ main tags/ v0.9
El directorio heads
define todas las ramas locales de tu repositorio. Cada nombre de archivo coincide con el nombre de la rama correspondiente, y dentro del archivo encontrarás un hash de confirmación. Este hash de confirmación es la ubicación del extremo de la rama. Para comprobarlo, prueba a ejecutar los dos comandos siguientes desde la raíz del repositorio de Git:
# Output the contents of `refs/heads/main` file: cat .git/refs/heads/main # Inspect the commit at the tip of the `main` branch: git log -1 main
El hash de confirmación que devuelva el comando cat
debe coincidir con el ID de confirmación que muestra git log
.
Para cambiar la ubicación de la rama main
, todo lo que tiene que hacer Git es cambiar el contenido del archivo refs/heads/main
. Del mismo modo, crear una nueva rama es simplemente cuestión de escribir un hash de confirmación en un nuevo archivo. Esto explica, en parte, que las ramas de Git sean tan livianas en comparación con SVN.
El directorio tags
funciona exactamente de la misma manera, pero contiene etiquetas en lugar de ramas. El directorio remotes
enumera todos los repositorios remotos que creaste con git remote
como subdirectorios separados. Dentro de cada uno, encontrarás todas las ramas remotas que se han recuperado en tu repositorio.
Specifying refs
Al pasar una referencia a un comando de Git, puedes definir el nombre completo de la referencia o usar un nombre corto y dejar que Git busque una referencia que coincida. Seguro que ya conoces los nombres cortos de referencias, ya que son lo que utilizas cada vez que haces referencia a una rama por su nombre.
git show some-feature
El argumento some-feature
en el comando anterior es en realidad un nombre corto para la rama. Git resuelve esto en refs/heads/some-feature
antes de usarlo. También puedes especificar la referencia completa en la línea de comandos de esta forma:
git show refs/heads/some-feature
De esta forma, evitas cualquier ambigüedad con respecto a la ubicación de la referencia. Esto es necesario, por ejemplo, si tienes tanto una etiqueta como una rama con el nombre some-feature
. Sin embargo, si usas convenciones de nomenclatura adecuadas, la ambigüedad entre etiquetas y ramas no debería ser un problema.
Veremos más nombres de referencia completos en la sección sobre especificaciones de referencia.
Packed refs
En repositorios grandes, Git realizará periódicamente una recolección de basura para eliminar objetos innecesarios y comprimir las referencias en un solo archivo a fin de mejorar el rendimiento. Puedes forzar esta compresión con el comando de recolección de basura:
git gc
Esto mueve todos los archivos de ramas y etiquetas individuales de la carpeta refs
a un único archivo llamado packed-refs
ubicado en la parte superior del directorio .git
. Si abres este archivo, verás un mapeo de hashes de confirmación a refs:
00f54250cf4e549fdfcafe2cf9a2c90bc3800285 refs/heads/feature 0e25143693cfe9d5c2e83944bbaf6d3c4505eb17 refs/heads/main bb883e4c91c870b5fed88fd36696e752fb6cf8e6 refs/tags/v0.9
En apariencia, el funcionamiento normal de Git no se verá afectado de ninguna manera. Pero si te preguntas por qué tu carpeta .git/refs
está vacía, es porque las referencias fueron a packed-refs.
Special refs
Aparte del directorio refs
, hay algunas referencias especiales que se almacenan en el directorio .git
de nivel superior . Son estas:
HEAD
: la confirmación/rama actualmente extraída con checkout.FETCH_HEAD
: la rama que se ha recuperado más recientemente de un repositorio remoto.ORIG_HEAD
: una referencia a la versión deHEAD
antes de realizar cambios drásticos en ella.MERGE_HEAD
: las confirmaciones que quieres fusionar en la rama actual congit merge
.CHERRY_PICK_HEAD
: la confirmación que quieres seleccionar con cherry-pick.
Git crea y actualiza todas estas referencias cuando es necesario. Por ejemplo, el comando git pull
primero ejecuta git fetch
, que actualiza la referencia FETCH_HEAD
. Luego, ejecuta git merge FETCH_HEAD
para terminar de incorporar las ramas obtenidas al repositorio. Por supuesto, puedes usar todas estas referencias del mismo modo que cualquier otra referencia, como seguro que has hecho con HEAD
.
Estos archivos incluyen contenido diferente según su tipo y el estado de tu repositorio. La referencia HEAD
puede contener un hash de confirmación o una referencia simbólica, que es simplemente una referencia a otra referencia en lugar de un hash de confirmación. Por ejemplo, echa un vistazo al contenido de HEAD
cuando estás en la rama main
:
git checkout main cat .git/HEAD
Esto generará ref: refs/heads/main
, lo que significa que HEAD
apunta a la referencia refs/heads/main
. Así es como Git sabe que la rama main
está extraída actualmente. Si cambiaras a otra rama, el contenido de HEAD
se actualizaría para reflejar la nueva rama. Pero si extrajeras una confirmación en lugar de una rama, HEAD
contendría un hash de confirmación en lugar de una referencia simbólica. Así es como Git sabe que está en un estado HEAD desasociado.
Por lo general, HEAD
será la única referencia que utilizarás directamente. Las demás solo suelen ser útiles cuando se escriben scripts de nivel inferior que necesitan enlazar mediante hook al funcionamiento interno de Git.
Referencias de especificaciones
Una especificación de referencia (o “refspec”) asigna una rama del repositorio local a una rama en un repositorio remoto. Esto permite gestionar ramas remotas mediante comandos de Git locales y configurar algunos comportamientos avanzados de git push
y git fetch
.
Una especificación de referencia se indica así: [+]
<src>
:
<dst>
. El parámetro <src>
es la rama de origen en el repositorio local y el parámetro <dst>
es la rama de destino en el repositorio remoto. El signo +
opcional sirve para forzar al repositorio remoto a realizar una actualización sin avance rápido.
Las especificaciones de referencia se pueden usar con el comando git push
para dar un nombre diferente a la rama remota. Por ejemplo, el siguiente comando envía la rama main
al repositorio remoto origin
como un git push
normal, pero usa qa-main
como el nombre de la rama en el repositorio origin
. Esto resulta útil para los equipos de control de calidad que necesitan enviar sus propias ramas a un repositorio remoto.
git push origin main:refs/heads/qa-main
You can also use refspecs for deleting remote branches. This is a common situation for feature-branch workflows that push the feature branches to a remote repo (e.g., for backup purposes). The remote feature branches still reside in the remote repo after they are deleted from the local repo, so you get a build-up of dead feature branches as your project progresses. You can delete them by pushing a refspec that has an empty parameter, like so:
git push origin :some-feature
Esto es muy práctico, ya que no necesitas iniciar sesión en tu repositorio remoto para eliminar manualmente la rama remota. Ten en cuenta que a partir de Git v1.7.0 puedes usar la marca --delete
en lugar del método anterior. Lo siguiente tendrá el mismo efecto que el comando anterior:
git push origin --delete some-feature
Al añadir algunas líneas al archivo de configuración de Git, puedes usar especificaciones de referencia para alterar el comportamiento de git fetch
. De forma predeterminada, git fetch
recupera todas las ramas del repositorio remoto. Esto sucede así debido a la sección del archivo .git/config
que aparece a continuación:
[remote "origin"] url = https://git@github.com:mary/example-repo.git fetch = +refs/heads/*:refs/remotes/origin/*
La línea de fetch
le dice a git fetch
que descargue todas las ramas del repositorio origin
. No obstante, algunos flujos de trabajo no necesitan todas las ramas. Por ejemplo, muchos flujos de trabajo de integración continua solo se preocupan por la rama main
. Para recuperar solo la rama main
, cambia la línea de fetch
de la siguiente manera:
[remote "origin"] url = https://git@github.com:mary/example-repo.git fetch = +refs/heads/main:refs/remotes/origin/main
También puedes configurar git push
de manera similar. Por ejemplo, si quieres enviar siempre la rama main
a qa-main
del repositorio remoto origin
(como hicimos anteriormente), cambiarías el archivo de configuración a:
[remote "origin"] url = https://git@github.com:mary/example-repo.git fetch = +refs/heads/main:refs/remotes/origin/main push = refs/heads/main:refs/heads/qa-main
Las especificaciones de referencia te dan un control completo sobre cómo transfieren ramas entre repositorios los comandos de Git. Te permiten cambiar el nombre de las ramas de tu repositorio local y eliminarlas, recuperar (fetch) o enviar (push) ramas con nombres diferentes y configurar git push
y git fetch
para que funcionen solo con las ramas que quieras.
Relative refs
También puedes hacer referencia a confirmaciones relacionadas con otra confirmación. El carácter ~
te permite llegar a confirmaciones anteriores. Por ejemplo, el siguiente código muestra la confirmación antepenúltima (la más reciente es HEAD
):
git show HEAD~2
Pero cuando se trabaja con confirmaciones de fusión, las cosas se complican un poco más. Como las confirmaciones de fusión tienen más de una confirmación inmediatamente anterior, hay más de una ruta que puedes seguir. En las fusiones de 3 vías, la primera confirmación anterior proviene de la rama en la que estabas cuando realizaste la fusión y la segunda proviene de la rama que pasaste al comando git merge
.
El carácter ~
siempre seguirá a la primera confirmación anterior de una confirmación de fusión. Si quieres seguir otra confirmación anterior, debes especificar cuál con el carácter ^
. Por ejemplo, si HEAD
es una confirmación de fusión, el siguiente código devuelve la segunda confirmación anterior de HEAD
.
git show HEAD^2
Puedes usar más de un carácter ^
para ir a otras generaciones de confirmaciones anteriores. Por ejemplo, este código muestra la confirmación antepenúltima (suponiendo que HEAD
es una confirmación de fusión), que es anterior a la segunda confirmación anterior.
git show HEAD^2^1
Para aclarar cómo funcionan ~
y ^
, la siguiente imagen muestra cómo llegar a cualquier confirmación de A
usando referencias relativas. En algunos casos, hay varias formas de llegar a una confirmación.
Las referencias relativas se pueden usar con los mismos comandos que para una referencia normal. Por ejemplo, todos los comandos siguientes usan una referencia relativa:
# Only list commits that are parent of the second parent of a merge commit git log HEAD^2 # Remove the last 3 commits from the current branch git reset HEAD~3 # Interactively rebase the last 3 commits on the current branch git rebase -i HEAD~3
The reflog
El registro de referencias (“reflog”) es como la red de seguridad de Git. Registra casi todos los cambios que haces en tu repositorio, independientemente de si confirmaste una instantánea o no. Es como un historial cronológico de todo lo que has hecho en tu repositorio local. Para ver el registro de referencias, ejecuta el comando git reflog
. Debería mostrar algo parecido a lo siguiente:
400e4b7 HEAD@{0}: checkout: moving from main to HEAD~2 0e25143 HEAD@{1}: commit (amend): Integrate some awesome feature into `main` 00f5425 HEAD@{2}: commit (merge): Merge branch ';feature'; ad8621a HEAD@{3}: commit: Finish the feature
Esto se puede entender de la siguiente manera:
- Acabas de extraer mediante checkout
HEAD~2
- Antes de eso, modificaste un mensaje de confirmación
- Antes de eso, fusionaste la rama
feature
enmain
- Antes de eso, confirmaste una instantánea
La sintaxis HEAD{
te permite hacer referencia a confirmaciones almacenadas en el registro de referencias. Funciona de forma muy parecida a las referencias HEAD~
de la sección anterior, pero
hace referencia a una entrada del registro de referencias en lugar del historial de confirmaciones.
Puedes usarlo para volver a un estado que de otro modo se perdería. Por ejemplo, digamos que acabas de descartar una nueva función con git reset
. El registro de referencias sería parecido a esto:
ad8621a HEAD@{0}: reset: moving to HEAD~3 298eb9f HEAD@{1}: commit: Some other commit message bbe9012 HEAD@{2}: commit: Continue the feature 9cb79fa HEAD@{3}: commit: Start a new feature
Las tres confirmaciones anteriores al git reset
ahora están huérfanas, lo que significa que no hay forma de hacer referencia a ellas, excepto a través del registro de referencias. Ahora, pongamos que te das cuenta de que no deberías haber desperdiciado todo tu trabajo. Todo lo que tienes que hacer es extraer con checkout la confirmación HEAD@{1}
para volver al estado de tu repositorio antes de ejecutar git reset
.
git checkout HEAD@{1}
Esta acción dejará el repositorio en un estado de HEAD
desasociado. Desde aquí, puedes crear una nueva rama y seguir trabajando en tu función.
Resumen
Ahora debería resultarte fácil hacer referencia a confirmaciones de un repositorio de Git. Hemos aprendido cómo se almacenan las ramas y las etiquetas como referencias en el subdirectorio .git
, cómo leer un archivo de packed-refs
, cómo se representa HEAD
, cómo usar las especificaciones de referencia para enviar y recuperar de forma avanzada, y cómo usar los operadores relativos ~
y ^
para recorrer una jerarquía de ramas.
También hemos echado un vistazo al registro de referencias, que es una forma de hacer referencia a confirmaciones que no están disponibles a través de ningún otro medio. Esta es una manera estupenda para recuperarse de esas situaciones en las que piensas “Vaya, no debería haber hecho eso”.
El objetivo de todo esto era saber elegir exactamente la confirmación que necesitas en cualquier situación de desarrollo. Es muy fácil aplicar los conocimientos que has adquirido en este artículo junto con tus otros conocimientos sobre Git, ya que algunos de los comandos más comunes aceptan referencias como argumentos; por ejemplo, git log
, git show
, git checkout
, git reset
, git revert
, git rebase
y muchos otros.
Compartir este artículo
Tema siguiente
Lecturas recomendadas
Consulta estos recursos para conocer los tipos de equipos de DevOps o para estar al tanto de las novedades sobre DevOps en Atlassian.