A lo largo de este módulo han ido apareciendo mensajes que hemos ido dejando para después:

Your branch is ahead of 'origin/main' by 2 commits.
branch 'main' set up to track 'origin/main'.
  main  b4d7e93 [origin/main: behind 3] Ajustar el estilo del pie de página

Y también comportamientos que Git ha adivinado por su cuenta: git push a secas sabiendo a dónde ir, git pull sin argumentos, y ese git switch documentacion/actualizar-notas que creó una rama local que no existía.

Todo eso apunta al mismo concepto, y es el que cierra el módulo: las ramas de seguimiento. Un mecanismo sencillo —dos líneas de configuración por rama— del que dependen la mitad de las comodidades de Git y la mayoría de sus mensajes de estado.

De paso vamos a aclarar de una vez por todas la confusión que arrastramos desde la lección 04-01: main, origin/main y el main del servidor son tres cosas distintas que se llaman parecido y que la gente mezcla constantemente. Cuando termines esta lección, esa distinción será automática, y con ella el módulo entero encajará.

Contenido

  1. Qué es una rama de seguimiento
  2. Dónde vive: branch.<rama>.remote y branch.<rama>.merge
  3. Las tres cosas que se llaman main
  4. De dónde sale "ahead of 'origin/main' by 2 commits"
  5. git branch -vv: el estado de todas de un vistazo
  6. Establecer, cambiar y quitar el upstream
  7. Crear una rama local a partir de una remota: el DWIM de Git
  8. El caso ambiguo de varios remotos
  9. @{upstream} y @{push}: referirse al seguimiento
  10. Cierre del módulo

  1. Qué es una rama de seguimiento

La definición:

Una rama de seguimiento (tracking branch) es una rama local que tiene registrada una asociación con una referencia remota concreta. A esa referencia asociada se la llama su upstream ("aguas arriba").

Dicho en llano: tu rama main "sabe" que su pareja en el servidor es origin/main. Y con ese dato, Git puede:

  • Enviar sin argumentos: git push sabe a qué remoto y a qué rama.
  • Recibir sin argumentos: git pull también.
  • Informarte del desfase: "vas 2 commits por delante y 3 por detrás".
  • Ofrecer atajos: @{upstream} para referirte a la pareja sin escribir su nombre.

Sin esa asociación, todo funciona igual, pero hay que escribirlo todo:

# Sin seguimiento
git push origin funcionalidad/orden-alfabetico
git pull origin funcionalidad/orden-alfabetico
git log funcionalidad/orden-alfabetico..origin/funcionalidad/orden-alfabetico

# Con seguimiento
git push
git pull
git log @{u}..

Un aviso terminológico importante, porque la documentación de Git no siempre es consistente. Hay dos usos de "rama de seguimiento" que conviene no mezclar:

Expresión A qué se refiere
Rama de seguimiento (tracking branch) Una rama local que tiene un upstream configurado. Por ejemplo, tu main
Rama de seguimiento remota (remote-tracking branch) La referencia remota en sí: origin/main. No es una rama tuya

Y recuerda además el otro sentido de la palabra upstream, el de la lección 04-01: el nombre convencional del remoto que apunta al proyecto original del que hiciste un fork (módulo 7). Tres usos de dos palabras. Aquí, siempre que digamos upstream, nos referimos a la referencia remota asociada a una rama local.

  1. Dónde vive: branch.<rama>.remote y branch.<rama>.merge

Como todo en Git, esto no es magia: son dos líneas en un fichero de texto.

cat .git/config
[remote "origin"]
	url = https://git.ejemplo.es/equipo/gestor-tareas.git
	fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
	remote = origin
	merge = refs/heads/main
[branch "correccion/foco-tras-borrar"]
	remote = origin
	merge = refs/heads/correccion/foco-tras-borrar

Cada rama con seguimiento tiene su propia sección:

Clave Valor Qué significa
branch.main.remote origin Con qué remoto habla esta rama
branch.main.merge refs/heads/main Qué rama de ese remoto es su pareja

Un detalle que despista al leerlo por primera vez: merge = refs/heads/main es el nombre de la rama en el servidor, no refs/remotes/origin/main. Tiene su lógica: la configuración dice "mi pareja es la rama main del repositorio remoto"; ya se encarga el refspec de saber que esa rama se guarda localmente en refs/remotes/origin/main.

Se consultan como cualquier otra configuración:

git config branch.main.remote
git config branch.main.merge
origin
refs/heads/main

Y también existe una clave opcional que quizá encuentres:

git config branch.main.rebase

Si vale true, git pull en esa rama usará --rebase en lugar de fusionar (lección 05-01).

Interioriza esto: el seguimiento es configuración local de tu repositorio. No viaja con el push, no la ve nadie más y cada persona del equipo tiene la suya. Bruno puede tener su main siguiendo a origin/main mientras Carla la tiene siguiendo a espejo/main, y ninguno de los dos se entera.

  1. Las tres cosas que se llaman main

Este es el apartado clave de la lección, y merece toda tu atención. Estas tres cosas se llaman parecido y son distintas:

main origin/main main del servidor
Qué es Tu rama local Tu foto del servidor La rama real del repositorio remoto
Dónde está .git/refs/heads/main, en tu disco .git/refs/remotes/origin/main, en tu disco En otra máquina
Quién la mueve , al confirmar o fusionar Git, al hacer fetch/pull/push Cualquiera del equipo, al enviar
Puedes confirmar en ella No No directamente
Puede quedarse anticuada Es tuya: está donde la dejaste Sí, constantemente Es la verdad del momento
Se ve con git branch git branch -r git ls-remote origin
flowchart TB
    subgraph TU["TU DISCO"]
        M["<b>main</b><br/>refs/heads/main<br/>La mueves tú al confirmar"]
        OM["<b>origin/main</b><br/>refs/remotes/origin/main<br/>Foto del servidor.<br/>La mueve fetch/pull/push"]
    end
    subgraph SRV["OTRA MÁQUINA"]
        SM["<b>main</b><br/>refs/heads/main<br/>La mueve quien envíe"]
    end

    M -.->|"seguimiento configurado:<br/>branch.main.remote = origin<br/>branch.main.merge = refs/heads/main"| OM
    OM <-->|"fetch: actualiza la foto<br/>push: actualiza el servidor"| SM

    style M fill:#fff0e8
    style OM fill:#e8f0ff
    style SM fill:#e8f4e8

La regla mnemotécnica

Cuando algo de remotos te desconcierte, pregúntate: ¿de cuál de las tres estoy hablando?

Ejemplos de aplicación inmediata:

  • "Mi compañero dice que ya está subido pero yo no lo veo." → Estáis hablando del main del servidor; lo que tú miras es tu origin/main, que está anticuado. Solución: git fetch.
  • "He hecho fetch y mis ficheros no han cambiado." → El fetch actualiza origin/main; tus ficheros dependen de main. Correcto.
  • "He hecho git reset --hard origin/main y he perdido mis commits." → Has movido tu main a donde está tu foto del servidor. Los commits siguen en el reflog (lección 09-04), pero la rama ya no los alcanza.
  • "git push dice everything up-to-date pero mi cambio no está." → Tu main está donde tu origin/main; el cambio nunca llegó a confirmarse.

Un experimento que fija la idea mejor que cualquier explicación:

# 1. Confirmar algo: se mueve SOLO main
git commit --allow-empty -m "Prueba"
git rev-parse main origin/main
d5e9b2f8a3c1e6b4d7f2a9c5e8b1d4f6a3c7e9b2   ← main ha avanzado
b4d7e935f1c8a2e6d4b7f9a3c1e5b8d2f4a6c9e7   ← origin/main sigue igual
# 2. Enviar: ahora se mueven las dos (y la del servidor)
git push
git rev-parse main origin/main
d5e9b2f8a3c1e6b4d7f2a9c5e8b1d4f6a3c7e9b2
d5e9b2f8a3c1e6b4d7f2a9c5e8b1d4f6a3c7e9b2

Un push con éxito actualiza también tu origin/main, y es lógico: Git acaba de hablar con el servidor y sabe con certeza dónde ha quedado su referencia.

  1. De dónde sale "ahead of 'origin/main' by 2 commits"

git status
On branch main
Your branch is ahead of 'origin/main' by 2 commits.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean

Ese mensaje no sale de ningún sitio mágico: Git compara tu rama con su upstream y cuenta commits a cada lado. Solo puede hacerlo porque el seguimiento está configurado; sin él, git status no diría nada del servidor.

El comando que hace el cálculo

git rev-list --left-right --count main...origin/main
2	0

Desglosemos, porque cada pieza importa:

  • main...origin/maintres puntos, no dos. Es la diferencia simétrica: todos los commits alcanzables desde una de las dos referencias pero no desde ambas. Es decir, lo exclusivo de cada lado.
  • --left-right — marca cada commit según de qué lado viene: < para el izquierdo (main), > para el derecho (origin/main).
  • --count — en lugar de listar los commits, cuenta cuántos hay de cada lado.

El resultado se lee así:

2	0
│   └── commits que tiene origin/main y no main   → "behind" (por detrás)
└────── commits que tiene main y no origin/main   → "ahead"  (por delante)

Dos puntos frente a tres puntos, que es de lo que más se confunde:

Sintaxis Significado Uso típico
main..origin/main Lo que tiene origin/main y no main "¿Qué me falta por traer?"
origin/main..main Lo que tiene main y no origin/main "¿Qué me falta por enviar?"
main...origin/main Lo exclusivo de cada lado "¿Cuánto hemos divergido?"

Sin --count, se ve commit a commit:

git rev-list --left-right --oneline main...origin/main
<d5e9b2f Devolver el foco al campo tras borrar una tarea
<8a3f7c1 Corregir el foco tras borrar una tarea
>b4d7e93 Ajustar el estilo del pie de página

Dos míos (<) y uno suyo (>): hemos divergido, y git status lo dirá así:

Your branch and 'origin/main' have diverged,
and have 2 and 1 different commits each, respectively.

Los cuatro estados posibles

rev-list devuelve Situación Qué dice git status Qué hacer
0 0 Sincronizados "up to date with 'origin/main'" Nada
N 0 Por delante "ahead of 'origin/main' by N commits" git push
0 N Por detrás "behind 'origin/main' by N commits, and can be fast-forwarded" git pull
N M Divergidos "have diverged" Integrar (ver 09-03)
flowchart TB
    B["Base común"]
    B --> A1["A"] --> A2["B<br/><b>main</b>"]
    B --> C1["C"] --> C2["D<br/><b>origin/main</b>"]

    N["main...origin/main → 2 y 2<br/>HAN DIVERGIDO"]

    style A2 fill:#fff0e8
    style C2 fill:#e8f0ff

Un aviso fundamental sobre estos números

El desfase se calcula contra tu origin/main, que es una foto. No contra el servidor.

Si llevas dos días sin hacer fetch, ese "por delante 2 commits" puede ser completamente falso: el servidor puede haber avanzado quince veces. git status nunca consulta la red.

Por eso el primer comando del día es git fetch: sin él, todos estos números describen un pasado.

Y por eso existe esta opción, que hace las dos cosas de golpe:

git fetch && git status

Hay una opción de git status que promete hacerlo sola:

git status -uno --ahead-behind

Pero --ahead-behind solo controla si se calcula o no el desfase; no trae nada del servidor. La única forma de actualizar la foto sigue siendo fetch.

  1. git branch -vv: el estado de todas de un vistazo

En la lección 03-06 mencionamos -vv y dijimos que cobraría sentido con remotos. Ha llegado el momento.

git branch -v
  correccion/foco-tras-borrar     d5e9b2f Devolver el foco al campo tras borrar una tarea
  documentacion/actualizar-notas  7f3c9a2 Actualizar las notas internas con el nuevo flujo
* main                            b4d7e93 Ajustar el estilo del pie de página
  experimento/local               3a7e9c2 Probar una idea sin publicar

Con la doble v:

git branch -vv
  correccion/foco-tras-borrar     d5e9b2f [origin/correccion/foco-tras-borrar] Devolver el foco al campo tras borrar una tarea
  documentacion/actualizar-notas  7f3c9a2 [origin/documentacion/actualizar-notas: ahead 1] Actualizar las notas internas con el nuevo flujo
* main                            b4d7e93 [origin/main: behind 3] Ajustar el estilo del pie de página
  experimento/local               3a7e9c2 Probar una idea sin publicar

Los corchetes son la información nueva, y cada línea cuenta algo distinto:

Rama Corchetes Lectura
correccion/foco-tras-borrar [origin/correccion/…] Tiene upstream y está sincronizada
documentacion/actualizar-notas [origin/…: ahead 1] Un commit local sin enviar
main [origin/main: behind 3] Tres commits en el servidor sin traer
experimento/local (nada) No tiene upstream: es puramente local

Ese último caso es el que hay que saber reconocer. Una rama sin corchetes no existe en ningún servidor: si pierdes el disco, pierdes ese trabajo. Es perfectamente legítimo tener ramas así (experimentos, pruebas), pero conviene saber cuáles son.

Y también puede aparecer este:

  funcionalidad/antigua           9d1e4b7 [origin/funcionalidad/antigua: gone] Marcar tareas como completadas

gone significa que la rama tenía upstream pero ese upstream ya no existe: alguien borró la rama en el servidor y tú has hecho fetch --prune. Es la señal habitual de "esta rama ya se integró y se limpió; puedes borrar la tuya".

Un comando muy útil para la limpieza periódica:

# Listar las ramas cuyo upstream ha desaparecido
git branch -vv | grep ': gone]'
  funcionalidad/antigua           9d1e4b7 [origin/funcionalidad/antigua: gone] Marcar tareas como completadas
  funcionalidad/exportar-csv      e9a2c5f [origin/funcionalidad/exportar-csv: gone] Ahora sí

Estas son las candidatas naturales a git branch -d, y complementa perfectamente el --merged de la lección 03-06 (que fallaba con las ramas integradas mediante squash: si la plataforma la borró tras integrarla, aquí aparecerá aunque --merged no la detecte).

Otras formas de consultar el seguimiento:

# Solo el nombre del upstream de la rama actual
git rev-parse --abbrev-ref @{upstream}
origin/main
# Con formato a medida
git branch --format='%(refname:short) → %(upstream:short) [%(upstream:track)]'
correccion/foco-tras-borrar → origin/correccion/foco-tras-borrar []
documentacion/actualizar-notas → origin/documentacion/actualizar-notas [ahead 1]
main → origin/main [behind 3]
experimento/local →  []

Los marcadores %(upstream:short) y %(upstream:track) son los mismos de git for-each-ref que usamos en la lección 03-06 para el listado bonito de ramas.

  1. Establecer, cambiar y quitar el upstream

Hay cuatro caminos para configurar el seguimiento, y conviene conocerlos todos porque cada uno aparece en un momento distinto.

  1. git push -u (el más habitual)

git push -u origin funcionalidad/orden-alfabetico

Envía y configura el seguimiento en un solo paso. Es el camino natural cuando publicas una rama nueva, como vimos en la lección 04-05.

  1. Automáticamente, con push.autoSetupRemote

git config --global push.autoSetupRemote true

Ya lo tenemos configurado desde la lección 01-06. Con él, un git push a secas sobre una rama sin upstream la crea en el servidor y configura el seguimiento, sin -u:

git switch -c funcionalidad/orden-alfabetico
git commit -am "Mostrar las tareas ordenadas alfabéticamente"
git push
 * [new branch]      funcionalidad/orden-alfabetico -> funcionalidad/orden-alfabetico
branch 'funcionalidad/orden-alfabetico' set up to track 'origin/funcionalidad/orden-alfabetico'.

Sin ese ajuste, ese git push habría fallado con el clásico:

fatal: The current branch funcionalidad/orden-alfabetico has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin funcionalidad/orden-alfabetico

Un mensaje que has visto mil veces si has usado Git sin esa configuración.

  1. git branch --set-upstream-to (a posteriori)

Para una rama que ya existe en los dos sitios pero no está emparejada:

git branch --set-upstream-to=origin/main main
branch 'main' set up to track 'origin/main'.

Hay una forma corta con -u (que aquí significa lo mismo que en push):

git branch -u origin/main

Sin nombre de rama, se aplica a la actual. Es el comando que necesitas cuando has creado el repositorio con init y has añadido el remoto después, o cuando la configuración se ha perdido por algún motivo.

Se puede apuntar a una rama con otro nombre, que es útil en casos de migración:

git branch --set-upstream-to=origin/desarrollo main

Ahora tu main local sigue a origin/desarrollo. Es raro, pero perfectamente válido: la asociación no exige que los nombres coincidan.

  1. Al crear la rama

# Explícito
git switch -c mi-copia --track origin/main

# O directamente desde la referencia remota (el DWIM del apartado 7)
git switch main

Quitar el seguimiento

git branch --unset-upstream
git status
On branch main
nothing to commit, working tree clean

Fíjate en lo que ha desaparecido: la línea del desfase. Sin upstream, Git no tiene con qué comparar y git status se queda mudo respecto al servidor. Es la mejor demostración de que ese mensaje depende enteramente de esta configuración.

Volvemos a ponerlo:

git branch -u origin/main

Resumen

Comando Cuándo
git push -u origin <rama> Al publicar una rama por primera vez
git push (con push.autoSetupRemote) Igual, pero automático
git branch -u origin/<rama> La rama ya existe en ambos lados, sin emparejar
git switch -c <local> --track origin/<rama> Al crear una local desde una remota, con nombre distinto
git switch <rama> (DWIM) Al crear una local desde una remota, con el mismo nombre
git branch --unset-upstream Para desvincularla

  1. Crear una rama local a partir de una remota: el DWIM de Git

Carla acaba de clonar y quiere trabajar en la rama de documentación de Ana. Solo tiene una rama local:

git branch
* main
git branch -r
  origin/HEAD -> origin/main
  origin/correccion/foco-tras-borrar
  origin/documentacion/actualizar-notas
  origin/main

Las referencias remotas no son ramas suyas, como sabemos desde la lección 04-01: son de solo lectura y no puede confirmar en ellas. Necesita una rama local.

El camino explícito sería este:

git switch -c documentacion/actualizar-notas --track origin/documentacion/actualizar-notas

Largo y redundante. Por eso Git tiene un atajo:

git switch documentacion/actualizar-notas
branch 'documentacion/actualizar-notas' set up to track 'origin/documentacion/actualizar-notas'.
Switched to a new branch 'documentacion/actualizar-notas'

Esto es el DWIM de Git (Do What I Mean, "haz lo que quiero decir"). El razonamiento interno es:

  1. ¿Existe una rama local llamada documentacion/actualizar-notas? No.
  2. ¿Existe exactamente una referencia remota con ese nombre? , origin/documentacion/actualizar-notas.
  3. Entonces la persona quiere trabajar en esa rama: creo la local a partir de ella y configuro el seguimiento.

Y el resultado es exactamente el mismo que la forma larga:

git branch -vv
* documentacion/actualizar-notas 7f3c9a2 [origin/documentacion/actualizar-notas] Actualizar las notas internas con el nuevo flujo
  main                           b4d7e93 [origin/main] Ajustar el estilo del pie de página

Lo mismo funciona con checkout por compatibilidad, aunque como sabemos desde la lección 03-02 es preferible switch:

git checkout documentacion/actualizar-notas    # equivalente, pero prefiere switch

Los límites del DWIM

No funciona si el nombre local ya existe:

git switch main

Si ya tienes main local, esto simplemente cambia a tu rama existente. No crea nada ni reconfigura nada.

No funciona si no has hecho fetch:

git switch funcionalidad/recien-creada
fatal: invalid reference: funcionalidad/recien-creada

Si un compañero acaba de crear esa rama y tú no has traído las referencias, Git no la conoce. Solución: git fetch primero. Es un error muy común al incorporarse a un trabajo en curso.

No funciona con varios remotos ambiguos: es el apartado siguiente.

Desactivarlo

Hay una configuración para exigir siempre la forma explícita:

git config --global checkout.guess false

Con ella, git switch <rama> solo funciona con ramas locales existentes. La mayoría de la gente prefiere el DWIM activado, pero conviene saber que existe si el comportamiento automático te incomoda.

  1. El caso ambiguo de varios remotos

Cuando hay más de un remoto con una rama del mismo nombre, el DWIM no puede adivinar y se rinde.

Supongamos que Bruno tiene dos remotos, como en la lección 04-02:

git remote -v
origin		https://git.ejemplo.es/equipo/gestor-tareas.git (fetch)
origin		https://git.ejemplo.es/equipo/gestor-tareas.git (push)
personal	[email protected]:bruno/gestor-tareas.git (fetch)
personal	[email protected]:bruno/gestor-tareas.git (push)
git branch -r
  origin/main
  origin/funcionalidad/orden-alfabetico
  personal/main
  personal/funcionalidad/orden-alfabetico

Y ahora:

git switch funcionalidad/orden-alfabetico
fatal: 'funcionalidad/orden-alfabetico' matched multiple (2) remote tracking branches

Git se niega, y hace bien: elegir por ti significaría configurar un seguimiento hacia un repositorio que quizá no es el que quieres, con el riesgo de acabar enviando trabajo al sitio equivocado.

Las tres formas de resolverlo

1. Ser explícito con la referencia remota (la más clara):

git switch -c funcionalidad/orden-alfabetico origin/funcionalidad/orden-alfabetico

O con --track, que además deja constancia de la intención:

git switch -c funcionalidad/orden-alfabetico --track origin/funcionalidad/orden-alfabetico

2. Usar la sintaxis de desambiguación:

git switch --track origin/funcionalidad/orden-alfabetico

Con --track y sin -c, Git crea una rama local con el mismo nombre corto que la remota.

3. Configurar un remoto preferente:

git config checkout.defaultRemote origin

A partir de ahí, ante la ambigüedad Git elegirá siempre origin sin preguntar:

git switch funcionalidad/orden-alfabetico
branch 'funcionalidad/orden-alfabetico' set up to track 'origin/funcionalidad/orden-alfabetico'.
Switched to a new branch 'funcionalidad/orden-alfabetico'

Es la solución recomendada si trabajas habitualmente con varios remotos, por ejemplo en el flujo de forks del módulo 7, donde tener origin y upstream a la vez es lo normal.

Nombres locales distintos

Con varios remotos, a veces conviene desacoplar los nombres para no perderse:

git switch -c orden-equipo    origin/funcionalidad/orden-alfabetico
git switch -c orden-personal  personal/funcionalidad/orden-alfabetico
git branch -vv
  orden-equipo    a1e5c93 [origin/funcionalidad/orden-alfabetico] Mostrar las tareas ordenadas alfabéticamente
* orden-personal  4f8c2a1 [personal/funcionalidad/orden-alfabetico] Probar otra forma de ordenar

Dos ramas locales con nombres claros, cada una siguiendo a un remoto distinto. Recuerda que el seguimiento no exige que los nombres coincidan.

  1. @{upstream} y @{push}: referirse al seguimiento

Git ofrece una sintaxis para referirse al upstream de una rama sin escribir su nombre. Es un ahorro pequeño pero muy cómodo, y aparece constantemente en alias y scripts.

# Las tres formas de decir lo mismo
git log origin/main..main
git log @{upstream}..
git log @{u}..

@{upstream} (abreviado @{u}) resuelve al upstream de la rama actual:

git rev-parse --abbrev-ref @{u}
origin/main

Y se puede aplicar a otra rama:

git rev-parse --abbrev-ref documentacion/actualizar-notas@{u}
origin/documentacion/actualizar-notas

Los usos más prácticos, y todos independientes del nombre de la rama en la que estés:

# ¿Qué tengo sin enviar?
git log --oneline @{u}..

# ¿Qué me falta por traer?
git log --oneline ..@{u}

# ¿Cuánto hemos divergido?
git rev-list --left-right --count @{u}...

# ¿Qué cambia respecto al servidor?
git diff @{u}

Fíjate en la elegancia de @{u}..: al omitir el lado derecho, Git usa HEAD. Y ..@{u} omite el izquierdo, con el mismo efecto. Son los rangos del módulo 2 aprovechando los valores por defecto.

@{push}, el hermano menos conocido

Existe una segunda referencia, @{push}, que apunta a dónde iría un git push. Normalmente coincide con @{upstream}, pero no siempre: si trabajas con remote.pushDefault configurado o con refspecs de envío distintos de los de descarga, pueden diferir.

git rev-parse --abbrev-ref @{push}

Es la referencia correcta para responder a "¿qué commits saldrían si hiciera push ahora?":

git log --oneline @{push}..

En el flujo de forks del módulo 7 —donde lees de upstream y escribes en origin— esa distinción se vuelve muy útil.

Y un par de alias que valen su peso en oro:

git config --global alias.sinenviar "log --oneline @{u}.."
git config --global alias.pendiente "log --oneline ..@{u}"
git config --global alias.desfase "rev-list --left-right --count @{u}..."
git sinenviar
d5e9b2f Devolver el foco al campo tras borrar una tarea
8a3f7c1 Corregir el foco tras borrar una tarea

Los alias, a fondo, en la lección 06-04.

  1. Cierre del módulo

Recapitulemos lo que hemos construido en estas seis lecciones, porque el módulo 4 es un salto conceptual y conviene verlo entero.

Empezamos deshaciendo el mito: un remoto no es un servidor mágico, es un nombre corto para una URL guardado en .git/config. Vimos que en un sistema distribuido no hay servidor central técnicamente: todos los repositorios son iguales y el "oficial" lo es por acuerdo del equipo. Entendimos por fin qué es un repositorio bare —el contenido de .git/ sin directorio de trabajo— y por qué es la forma correcta de montar un repositorio que recibe envíos. Y descubrimos que las referencias remotas viven en .git/refs/remotes/, en tu disco, y que origin/main es una foto con fecha, no una ventana en directo.

Después, Ana publicó el proyecto. Registramos el remoto con git remote add —una operación puramente local que ni siquiera valida la URL—, aprendimos a inspeccionarlo, renombrarlo y cambiarle la dirección, y desmenuzamos el refspec +refs/heads/*:refs/remotes/origin/*, esa línea que casi nadie sabe leer y que explica de dónde sale la barra de origin/main.

Resolvimos la autenticación: por qué leer un repositorio público no requiere credenciales y escribir siempre sí, qué es un token personal de acceso y por qué sustituyó a la contraseña, cómo se genera y se usa un par de claves SSH, y cómo cada sistema operativo guarda las credenciales —con Git Credential Manager abriéndole el camino a Carla desde Windows 11.

Atacamos la confusión más extendida de Git: fetch frente a pull. El primero descarga y actualiza referencias remotas sin tocar un solo fichero, y es absolutamente seguro; el segundo añade una integración que sí modifica tu trabajo y puede dar conflictos. Aprendimos a mirar en el hueco entre ambos, a limpiar referencias fantasma con --prune, y Carla clonó por fin un repositorio con toda la historia del módulo 3 dentro.

Le dimos la vuelta al canal con git push: qué envía exactamente, qué hace el -u, cómo se escribe un refspec de envío, por qué el servidor rechaza los envíos non-fast-forward —para no destruir trabajo ajeno— y por qué --force-with-lease es casi siempre la respuesta correcta cuando de verdad hay que forzar.

Y hemos terminado atando los cabos: las ramas de seguimiento, dos líneas de configuración por rama de las que dependen git push sin argumentos, git pull sin argumentos, los mensajes de git status y el DWIM de git switch.

La idea que sostiene todo el módulo, y que conviene llevarse: los remotos no han cambiado nada de lo que ya sabías. Fusionar el trabajo de Bruno es el mismo git merge del módulo 3. Los conflictos se resuelven igual. Las ramas siguen siendo ficheros de 41 bytes. Lo único que se ha añadido es el transporte: cómo consiguen los objetos viajar de un .git/ a otro. Una vez han llegado, todo funciona como ya sabías.

El problema que queda abierto

El equipo ya colabora. Los tres envían, reciben e integran, y el proyecto avanza. Pero si Ana mira el historial de main con ojos críticos, empieza a ver cosas que no le gustan:

git log --oneline --graph -14
*   4f8c2a1 Merge branch 'main' of https://git.ejemplo.es/equipo/gestor-tareas
|\
| * d5e9b2f Devolver el foco al campo tras borrar una tarea
* | 8a3f7c1 Corregir el foco tras borrar una tarea
|/
*   e7b3f2a Merge branch 'main' of https://git.ejemplo.es/equipo/gestor-tareas
|\
| * b4d7e93 Ajustar el estilo del pie de página
* | 3e9c2a5 arreglo
|/
* 9e2f4a7 Añadir el contador de tareas pendientes
* c2a8f1e Fusionar el mensaje de lista vacía

Dos problemas distintos conviviendo:

  • Commits de fusión que no cuentan nada. Esos Merge branch 'main' of https://… no son decisiones de diseño: son el rastro de dos personas que hicieron pull a la vez. Ensucian el grafo y no aportan información.
  • Commits que no deberían existir tal cual. arreglo no describe nada. Y en las ramas de trabajo hay wip, wip 2, ahora sí y algún console.log olvidado que se envió sin querer.

Falta además todo un repertorio de operaciones que el trabajo en equipo hace imprescindibles: llevarse un commit concreto de una rama a otra sin arrastrar el resto; guardar el trabajo a medias para atender una urgencia y recuperarlo después; marcar versiones con etiquetas que se publiquen en el servidor; y deshacer un commit ya enviado sin reescribir el historial que los demás ya tienen.

En el módulo 5: Operaciones Avanzadas de Git entramos en ese terreno. Veremos git rebase —que reescribe commits para producir un historial lineal, y del que hemos hablado tres veces sin desarrollarlo—, el rebase interactivo para reordenar, unir y reescribir commits antes de publicarlos, git cherry-pick para trasplantar commits sueltos, git stash para apartar trabajo temporalmente, las etiquetas para marcar versiones, y git revert para deshacer sin borrar.

Es un módulo de precisión, y llega en el momento adecuado: ahora que el historial es compartido, reescribirlo tiene consecuencias para otras personas. Todo lo que has aprendido aquí sobre push, sobre el rechazo non-fast-forward y sobre --force-with-lease va a ser exactamente lo que te permita distinguir qué se puede reescribir y qué no.

Errores Comunes y Consejos

Error 1: confundir main, origin/main y el main del servidor. Es el error que engloba a casi todos los demás. Ante cualquier duda, pregúntate de cuál de los tres estás hablando. Los dos primeros están en tu disco.

Error 2: fiarse del "ahead/behind" sin haber hecho fetch. git status nunca consulta la red: compara contra tu foto. Si llevas dos días sin fetch, esos números describen un pasado.

Error 3: confundir .. con .... Dos puntos es "lo que tiene B y no A"; tres puntos es "lo exclusivo de cada lado". Para el desfase se usa ... con --left-right --count.

Error 4: fatal: The current branch X has no upstream branch. La rama no tiene seguimiento. Se arregla con git push -u origin X, o de una vez para siempre con push.autoSetupRemote true.

Error 5: que el DWIM falle porque no has hecho fetch. Si un compañero acaba de crear la rama y tú no has traído las referencias, git switch <rama> dará invalid reference. Un git fetch lo resuelve.

Error 6: ignorar las ramas marcadas como gone. Significan que su rama en el servidor ha desaparecido, normalmente porque se integró. Son las candidatas más claras a borrar en local.

Error 7: no darse cuenta de que una rama no tiene upstream. Sin corchetes en git branch -vv, esa rama solo existe en tu disco. Legítimo para un experimento, peligroso para trabajo que importa.

Error 8: creer que el seguimiento se comparte con el equipo. Es configuración local de tu .git/config. Cada persona tiene la suya y nadie ve la de nadie.

Consejo 1: git fetch && git status como rutina de inicio. Dos comandos que te dicen la verdad en lugar de una foto vieja.

Consejo 2: usa git branch -vv para la revisión semanal. Ves de un golpe qué está sin enviar, qué está sin traer, qué es solo local y qué se puede borrar.

Consejo 3: activa push.autoSetupRemote true. Elimina el error de "no upstream branch" para siempre. Ya lo tienes desde la lección 01-06.

Consejo 4: aprende @{u}. git log @{u}.. para ver lo que no has enviado y git log ..@{u} para lo que no has traído funcionan en cualquier rama sin cambiar una letra. Guárdalos como alias.

Consejo 5: con varios remotos, configura checkout.defaultRemote origin. Evita el error de ambigüedad y te ahorra escribir la referencia completa cada vez.

Ejercicios

Ejercicio 1: las tres cosas

Monta un servidor bare y dos clones. Después, y con comandos, demuestra cada afirmación:

  1. Al confirmar en local, solo se mueve main.
  2. Al hacer push, se mueven main, origin/main y la rama del servidor.
  3. Cuando el otro clon envía algo, el origin/main del primero no cambia hasta que hace fetch.
  4. git status calcula el desfase sin consultar la red: provoca una situación en la que el mensaje sea objetivamente falso respecto al servidor y explica por qué.
  5. Reproduce el cálculo de git status a mano con git rev-list.

Ejercicio 2: gestionar el seguimiento

En un repositorio conectado a un bare:

  1. Crea una rama local sin upstream y demuestra con dos comandos distintos que no lo tiene.
  2. Comprueba qué dice git push en esa rama si desactivas push.autoSetupRemote.
  3. Configúrale el upstream de las tres formas posibles (deshaciendo entre una y otra) y verifica el resultado en .git/config cada vez.
  4. Quítale el upstream y observa qué desaparece de git status.
  5. Crea una rama local que siga a una remota con otro nombre y demuestra que funciona.

Ejercicio 3: DWIM y ambigüedad

  1. Monta dos servidores bare y un repositorio de trabajo con ambos registrados.
  2. Crea en los dos una rama con el mismo nombre, con contenido distinto.
  3. Intenta usar el DWIM y captura el error.
  4. Resuélvelo de las tres formas explicadas en la lección.
  5. Demuestra con git branch -vv que en cada caso la rama local sigue al remoto correcto.
  6. Comprueba con git log que el contenido es realmente distinto según el remoto elegido.

Soluciones

Solución 1:

mkdir -p /tmp/ej-track && cd /tmp/ej-track
git init --bare servidor.git

git clone servidor.git ana
cd ana
echo "base" > f.txt && git add . && git commit -m "Estructura inicial del gestor de tareas"
git push -u origin main
cd ..
git clone servidor.git bruno
# 1. Confirmar mueve SOLO main
cd /tmp/ej-track/ana
git rev-parse main origin/main
5a1d8f39c2e7b4a6f1d8c3e9b2a5f7d4c6e8b1a3
5a1d8f39c2e7b4a6f1d8c3e9b2a5f7d4c6e8b1a3
echo "contador" >> f.txt
git commit -am "Añadir el contador de tareas pendientes"
git rev-parse main origin/main
9e2f4a78c3b1d6f4a2e7c5b9d8f1a3c6e4b2d7f9   ← main ha avanzado
5a1d8f39c2e7b4a6f1d8c3e9b2a5f7d4c6e8b1a3   ← origin/main NO

Y en el servidor tampoco ha cambiado nada:

git --git-dir=/tmp/ej-track/servidor.git rev-parse main
5a1d8f39c2e7b4a6f1d8c3e9b2a5f7d4c6e8b1a3
# 2. Push mueve las tres
git push
git rev-parse main origin/main
git --git-dir=/tmp/ej-track/servidor.git rev-parse main
9e2f4a78c3b1d6f4a2e7c5b9d8f1a3c6e4b2d7f9
9e2f4a78c3b1d6f4a2e7c5b9d8f1a3c6e4b2d7f9
9e2f4a78c3b1d6f4a2e7c5b9d8f1a3c6e4b2d7f9

Las tres alineadas. El push actualiza tu origin/main porque Git acaba de hablar con el servidor y sabe con certeza dónde ha quedado.

# 3. Lo que envía Bruno no llega solo a Ana
cd /tmp/ej-track/bruno
git pull
echo "filtro" >> f.txt
git commit -am "Añadir el filtro de tareas pendientes"
git push

# En el repositorio de Ana, SIN hacer fetch:
cd /tmp/ej-track/ana
git rev-parse origin/main
git --git-dir=/tmp/ej-track/servidor.git rev-parse main
9e2f4a78c3b1d6f4a2e7c5b9d8f1a3c6e4b2d7f9   ← la foto de Ana, anticuada
c3b8f5d2a9e4f7b1c6d3a8e5b2f9c4d7a1e6b3f8   ← la realidad del servidor
# 4. git status miente (sin saberlo)
git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

El mensaje es objetivamente falso. El servidor tiene un commit que Ana no tiene, así que su rama está por detrás. Pero git status compara contra origin/main, que es una foto tomada antes del envío de Bruno, y nunca consulta la red. No está mintiendo: está diciendo la verdad sobre una información caducada.

git fetch
git status
On branch main
Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)

Ahora sí. La única forma de que esos números sean ciertos es hacer fetch antes.

# 5. El cálculo a mano
git rev-list --left-right --count main...origin/main
0	1

Cero commits exclusivos de main (nada por enviar), uno exclusivo de origin/main (por traer). Es exactamente "behind by 1 commit". Y con detalle:

git rev-list --left-right --oneline main...origin/main
>c3b8f5d Añadir el filtro de tareas pendientes

El > indica que viene del lado derecho, origin/main.

Solución 2:

mkdir -p /tmp/ej-up && cd /tmp/ej-up
git init --bare servidor.git
git clone servidor.git trabajo
cd trabajo
echo "base" > f.txt && git add . && git commit -m "Base"
git push -u origin main
# 1. Rama local sin upstream
git switch -c funcionalidad/orden-alfabetico
echo "orden" >> f.txt
git commit -am "Mostrar las tareas ordenadas alfabéticamente"

# Prueba a: sin corchetes en branch -vv
git branch -vv
* funcionalidad/orden-alfabetico a1e5c93 Mostrar las tareas ordenadas alfabéticamente
  main                           5f8b2e1 [origin/main] Base
# Prueba b: @{u} no resuelve
git rev-parse --abbrev-ref @{u}
fatal: no upstream configured for branch 'funcionalidad/orden-alfabetico'
# Prueba c: tampoco hay sección en la configuración
git config --get-regexp '^branch\.'
branch.main.remote origin
branch.main.merge refs/heads/main

Solo aparece main. La rama nueva no tiene ninguna entrada.

# 2. Qué dice push sin autoSetupRemote
git config --local push.autoSetupRemote false
git push
fatal: The current branch funcionalidad/orden-alfabetico has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin funcionalidad/orden-alfabetico

Git no adivina: exige que digas a dónde va la rama la primera vez.

# 3a. Forma 1: push -u
git push -u origin funcionalidad/orden-alfabetico
git config --get-regexp '^branch\.funcionalidad'
branch.funcionalidad/orden-alfabetico.remote origin
branch.funcionalidad/orden-alfabetico.merge refs/heads/funcionalidad/orden-alfabetico
# Deshacer para probar la siguiente
git branch --unset-upstream
git config --get-regexp '^branch\.funcionalidad'
(sin salida)
# 3b. Forma 2: branch --set-upstream-to
git branch --set-upstream-to=origin/funcionalidad/orden-alfabetico
branch 'funcionalidad/orden-alfabetico' set up to track 'origin/funcionalidad/orden-alfabetico'.
git branch --unset-upstream

# 3c. Forma 3: automática con autoSetupRemote
git config --local push.autoSetupRemote true
git commit --allow-empty -m "Otro commit"
git push
branch 'funcionalidad/orden-alfabetico' set up to track 'origin/funcionalidad/orden-alfabetico'.

Las tres producen exactamente la misma configuración. Cambia el momento y la comodidad, no el resultado.

# 4. Quitar el upstream
git status
On branch funcionalidad/orden-alfabetico
Your branch is up to date with 'origin/funcionalidad/orden-alfabetico'.

nothing to commit, working tree clean
git branch --unset-upstream
git status
On branch funcionalidad/orden-alfabetico
nothing to commit, working tree clean

Ha desaparecido la línea del desfase. Sin upstream, Git no tiene con qué comparar. Es la prueba de que ese mensaje depende enteramente de esta configuración.

# 5. Rama local con nombre distinto del upstream
git switch main
git switch -c orden-local --track origin/funcionalidad/orden-alfabetico
branch 'orden-local' set up to track 'origin/funcionalidad/orden-alfabetico'.
Switched to a new branch 'orden-local'
git branch -vv
  funcionalidad/orden-alfabetico a7c2e94 Otro commit
  main                           5f8b2e1 [origin/main] Base
* orden-local                    a7c2e94 [origin/funcionalidad/orden-alfabetico] Otro commit
git commit --allow-empty -m "Trabajo desde la rama con otro nombre"
git push
To /tmp/ej-up/servidor.git
   a7c2e94..b3f8d1c  orden-local -> funcionalidad/orden-alfabetico

Fíjate en la última línea: orden-local -> funcionalidad/orden-alfabetico. La rama local se llama de una forma y la del servidor de otra, y el envío funciona sin problema. El seguimiento no exige que los nombres coincidan.

Solución 3:

mkdir -p /tmp/ej-dwim && cd /tmp/ej-dwim

# 1. Dos servidores y un repositorio de trabajo
git init --bare equipo.git
git init --bare personal.git

git init -b main trabajo
cd trabajo
echo "base" > f.txt && git add . && git commit -m "Base"
git remote add origin /tmp/ej-dwim/equipo.git
git remote add personal /tmp/ej-dwim/personal.git
git push origin main
git push personal main
# 2. La misma rama, con contenido distinto, en cada servidor
git switch -c funcionalidad/orden-alfabetico
echo "orden del equipo" >> f.txt
git commit -am "Mostrar las tareas ordenadas alfabéticamente"
git push origin funcionalidad/orden-alfabetico

git reset --hard HEAD~1
echo "otra forma de ordenar" >> f.txt
git commit -am "Probar otra forma de ordenar"
git push personal funcionalidad/orden-alfabetico

# Volver a un estado limpio y borrar la rama local
git switch main
git branch -D funcionalidad/orden-alfabetico
git fetch --all
git branch -r
  origin/funcionalidad/orden-alfabetico
  origin/main
  personal/funcionalidad/orden-alfabetico
  personal/main
# 3. El DWIM falla
git switch funcionalidad/orden-alfabetico
fatal: 'funcionalidad/orden-alfabetico' matched multiple (2) remote tracking branches

Git no puede adivinar cuál quieres, y elegir mal significaría acabar enviando trabajo al repositorio equivocado.

# 4a. Forma 1: referencia remota explícita
git switch -c orden-equipo origin/funcionalidad/orden-alfabetico
branch 'orden-equipo' set up to track 'origin/funcionalidad/orden-alfabetico'.
Switched to a new branch 'orden-equipo'
# 4b. Forma 2: --track sin -c (toma el nombre corto de la remota)
git switch main
git switch --track personal/funcionalidad/orden-alfabetico
branch 'funcionalidad/orden-alfabetico' set up to track 'personal/funcionalidad/orden-alfabetico'.
Switched to a new branch 'funcionalidad/orden-alfabetico'
# 4c. Forma 3: remoto preferente
git switch main
git branch -D funcionalidad/orden-alfabetico
git config checkout.defaultRemote origin
git switch funcionalidad/orden-alfabetico
branch 'funcionalidad/orden-alfabetico' set up to track 'origin/funcionalidad/orden-alfabetico'.
Switched to a new branch 'funcionalidad/orden-alfabetico'

Ya no hay ambigüedad: ante la duda, Git elige origin.

# 5. Cada rama sigue a su remoto
git branch -vv
* funcionalidad/orden-alfabetico a1e5c93 [origin/funcionalidad/orden-alfabetico] Mostrar las tareas ordenadas alfabéticamente
  main                           5f8b2e1 Base
  orden-equipo                   a1e5c93 [origin/funcionalidad/orden-alfabetico] Mostrar las tareas ordenadas alfabéticamente
# 6. El contenido es realmente distinto
git log --oneline -1 origin/funcionalidad/orden-alfabetico
git log --oneline -1 personal/funcionalidad/orden-alfabetico
a1e5c93 Mostrar las tareas ordenadas alfabéticamente
4f8c2a1 Probar otra forma de ordenar
git diff origin/funcionalidad/orden-alfabetico personal/funcionalidad/orden-alfabetico
diff --git a/f.txt b/f.txt
index 8c3d5a1..2e7b9f4 100644
--- a/f.txt
+++ b/f.txt
@@ -1,2 +1,2 @@
 base
-orden del equipo
+otra forma de ordenar

Dos commits distintos con el mismo nombre de rama en dos servidores distintos. Aquí se ve por qué Git prefiere fallar antes que adivinar: elegir mal habría significado trabajar sobre una base equivocada y, peor aún, enviar el resultado al sitio equivocado.

Conclusión

Con esta lección cerramos el módulo 4. Lo aprendido aquí:

  • Una rama de seguimiento es una rama local asociada a una referencia remota, su upstream. Esa asociación es lo que permite git push y git pull sin argumentos, los mensajes de desfase de git status y el atajo @{u}.
  • Vive en dos líneas de .git/config: branch.<rama>.remote (con qué remoto) y branch.<rama>.merge (qué rama de ese remoto). Es configuración local: no viaja con el push y cada persona tiene la suya.
  • main, origin/main y el main del servidor son tres cosas distintas. Las dos primeras están en tu disco: la mueves tú al confirmar, y la segunda la mueve Git al hablar con el servidor. La tercera está en otra máquina y la mueve cualquiera del equipo. Casi todos los desconciertos con remotos se resuelven preguntándose de cuál de las tres se está hablando.
  • El "ahead/behind" se calcula con git rev-list --left-right --count main...origin/main, usando la diferencia simétrica de tres puntos. Y se calcula contra tu foto, no contra el servidor: sin un fetch previo, esos números pueden ser falsos.
  • git branch -vv muestra el upstream y el desfase de todas las ramas entre corchetes. Sin corchetes = sin upstream, la rama es puramente local. Con gone = su rama del servidor ya no existe, normalmente porque se integró: candidata a borrar.
  • El upstream se establece con git push -u, automáticamente con push.autoSetupRemote true, a posteriori con git branch -u origin/<rama>, o al crear la rama con --track. Se quita con --unset-upstream, y entonces git status deja de informar del desfase.
  • El DWIM de git switch <rama> crea una rama local a partir de una remota del mismo nombre y configura el seguimiento, siempre que no exista ya en local y haya exactamente una referencia remota que coincida. Con varios remotos falla: se resuelve siendo explícito, con --track, o configurando checkout.defaultRemote.
  • @{upstream} (o @{u}) se refiere al upstream de la rama actual sin nombrarlo: git log @{u}.. (lo que no has enviado) y git log ..@{u} (lo que no has traído) funcionan en cualquier rama.

Lo que viene

El proyecto ha salido del portátil de Ana y circula entre tres máquinas y tres sistemas operativos. El equipo colabora de verdad: los tres envían, reciben e integran, y gestor-tareas avanza.

Pero el historial empieza a acusarlo. Se llena de commits de fusión que no cuentan nada, de mensajes como arreglo y wip 2, y de trabajo a medias que se publicó sin querer. Y aparecen necesidades nuevas que el trabajo en solitario no planteaba: llevarse un commit concreto de una rama a otra, apartar el trabajo sin terminar para atender una urgencia, marcar una versión publicada o deshacer algo que ya está en el servidor.

En el módulo 5: Operaciones Avanzadas de Git aprenderemos a manipular el historial con precisión: git rebase para reescribir commits y obtener una historia lineal, el rebase interactivo para reordenarlos, unirlos y corregirlos antes de publicarlos, git cherry-pick para trasplantar commits sueltos, git stash para apartar trabajo temporalmente, las etiquetas para marcar versiones, y git revert para deshacer sin borrar.

Y todo lo que has aprendido en este módulo será justamente lo que te permita usarlas con criterio. Porque a partir de ahora el historial es compartido, y reescribir algo que otra persona ya tiene descargado no es una operación local: es una decisión que afecta a todo el equipo.

Dominando Git: De Principiante a Avanzado

Módulo 1: Introducción a Git

Módulo 2: Operaciones Básicas de Git

Módulo 3: Ramas y Fusión

Módulo 4: Trabajando con Repositorios Remotos

Módulo 5: Operaciones Avanzadas de Git

Módulo 6: Herramientas y Técnicas de Git

Módulo 7: Estrategias de Colaboración y Flujo de Trabajo

Módulo 8: Mejores Prácticas y Consejos de Git

Módulo 9: Solución de Problemas y Depuración

Módulo 10: Git en el Mundo Real

© Copyright 2026. Todos los derechos reservados