En la lección anterior quedó claro qué es un remoto: un nombre corto para una URL. Ahora toca crearlo. Y toca hacerlo con el caso que llevamos tres módulos aplazando: Ana va a publicar gestor-tareas.

Hasta hoy, el proyecto ha vivido íntegramente en su portátil Ubuntu. Tiene un historial respetable —cuatro commits de base, dos fusiones, un squash, un conflicto resuelto— y main está en c2a8f1e. En la lección 02-02 vimos a Bruno clonar el proyecto y comprobamos que el clon dejaba registrado un remoto llamado origin sin que nadie se lo pidiera. Aquella lección explicaba la mitad del camino: cómo se hereda un remoto. Esta explica la otra mitad: cómo se crea uno desde cero, que es lo que Ana necesita ahora porque su repositorio nació con git init y no ha hablado con nadie en su vida.

Además, abriremos .git/config y desmenuzaremos la línea fetch = +refs/heads/*:refs/remotes/origin/*. Se llama refspec, aparece en todos los repositorios del mundo y casi nadie sabe qué dice. Entenderla es lo que convierte fetch y push de comandos mágicos en comandos predecibles.

Contenido

  1. El punto de partida: un repositorio sin remotos
  2. git remote add: registrar el primero
  3. git remote -v: qué remotos tengo
  4. git remote show: la radiografía completa
  5. Cómo queda en .git/config
  6. El refspec, explicado con calma
  7. Renombrar, eliminar y cambiar la URL
  8. set-url --push: leer de un sitio y escribir en otro
  9. Trabajar con varios remotos
  10. init frente a clone: dos formas de tener un remoto

  1. El punto de partida: un repositorio sin remotos

Ana está en su proyecto, tal como lo dejamos al final del módulo 3:

cd ~/proyectos/gestor-tareas
git log --oneline -3
c2a8f1e (HEAD -> main) Fusionar el mensaje de lista vacía
6d3f8b2 Mostrar un mensaje cuando el listado está vacío
3b9e7d1 Añadir exportación del listado de tareas a CSV
git branch
  correccion/foco-tras-borrar
  documentacion/actualizar-notas
* main

Y la pregunta del día:

git remote
(sin salida)

Silencio. No hay ningún remoto configurado, que es exactamente lo esperable en un repositorio creado con git init. Podemos confirmarlo mirando el disco:

cat .git/config
[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true

Cuatro líneas de configuración básica y ninguna sección [remote …]. Comparado con el .git/config de Bruno que vimos en la lección 02-02, aquí faltan tanto la sección del remoto como la de seguimiento de la rama.

Antes de continuar, alguien tiene que haber creado el repositorio de destino. En un caso real, Ana entraría en la interfaz web de GitHub, GitLab o el Gitea de la empresa y pulsaría "Nuevo repositorio", obteniendo una URL. Para practicar sin depender de nada, montamos el equivalente exacto con lo que aprendimos en la lección anterior:

mkdir -p /tmp/servidor
git init --bare /tmp/servidor/gestor-tareas.git
Initialized empty Git repository in /tmp/servidor/gestor-tareas.git/

Importante: el repositorio de destino está vacío. Ni un commit, ni una rama. Esto es lo normal y lo deseable: publicar un proyecto consiste en volcar tu historia en un repositorio recién creado.

En el relato usaremos la URL real del equipo, https://git.ejemplo.es/equipo/gestor-tareas.git; en los comandos que puedas reproducir, la ruta local. Son intercambiables: para Git, ambas son URL válidas.

  1. git remote add: registrar el primero

La sintaxis es tan simple como el concepto:

git remote add <nombre> <url>

Ana lo ejecuta:

git remote add origin https://git.ejemplo.es/equipo/gestor-tareas.git
(sin salida)

Silencio absoluto, que en Git significa éxito. Y con razón: no ha ocurrido nada más que escribir tres líneas en un fichero de texto. No se ha conectado a ningún sitio, no ha comprobado que la URL exista, no ha descargado nada y no ha pedido credenciales.

Comprobémoslo de la forma más contundente posible:

git remote add inexistente https://este.servidor.no.existe/nada.git
(sin salida)

Git lo acepta encantado. git remote add es una operación puramente local y offline. Solo cuando ejecutes fetch o push contra ese nombre descubrirá Git que la URL no lleva a ninguna parte. Este detalle explica muchos desconciertos: la gente cree que "el remoto está bien porque git remote add no dio error", cuando ese comando nunca da error por una URL mala.

Limpiamos el experimento:

git remote remove inexistente

Reglas del nombre

  • Debe ser único dentro del repositorio: si ya existe origin, git remote add origin … falla con error: remote origin already exists.
  • Admite letras, números, guiones y guiones bajos. Evita espacios, barras y acentos.
  • No hay ninguna palabra reservada. origin es convención pura, como vimos en 04-01.

La opción -f

git remote add -f origin https://git.ejemplo.es/equipo/gestor-tareas.git

Con -f (fetch), Git registra el remoto y acto seguido ejecuta un git fetch contra él. Es un atajo cómodo cuando añades un remoto que ya tiene contenido y quieres traértelo de inmediato. En el caso de Ana no aporta nada, porque el repositorio de destino está vacío.

  1. git remote -v: qué remotos tengo

Los dos comandos de consulta básicos:

git remote
origin

Solo los nombres. Y la versión útil:

git remote -v
origin	https://git.ejemplo.es/equipo/gestor-tareas.git (fetch)
origin	https://git.ejemplo.es/equipo/gestor-tareas.git (push)

¿Por qué aparece dos veces el mismo remoto? Porque Git guarda por separado la URL de lectura (fetch) y la de escritura (push). Normalmente son idénticas y por eso la línea se repite, pero pueden ser distintas: lo veremos en el apartado 8, y ahí la salida de -v deja de ser redundante y pasa a ser informativa.

git remote -v debería ser tu primer reflejo al llegar a un repositorio ajeno, junto con git status y git log --oneline -5. En tres segundos te dice con quién habla ese repositorio.

  1. git remote show: la radiografía completa

Este comando es de otra categoría, y conviene entender por qué:

git remote show origin

Este sí se conecta a la red. A diferencia de git remote -v, que solo lee tu .git/config, git remote show consulta el servidor para preguntarle qué ramas tiene ahora mismo. Si no hay conexión o las credenciales fallan, este comando falla.

Como el repositorio de Ana aún está vacío, la salida es escueta. Veamos en cambio la que obtiene Bruno más adelante, cuando ya hay contenido, porque es la que enseña algo:

git remote show origin
* remote origin
  Fetch URL: https://git.ejemplo.es/equipo/gestor-tareas.git
  Push  URL: https://git.ejemplo.es/equipo/gestor-tareas.git
  HEAD branch: main
  Remote branches:
    correccion/foco-tras-borrar     tracked
    documentacion/actualizar-notas  tracked
    main                            tracked
    experimento/pwa                 new (next fetch will store in remotes/origin)
    funcionalidad/vieja             stale (use 'git remote prune' to remove)
  Local branches configured for 'git pull':
    main                            merges with remote main
  Local refs configured for 'git push':
    main                            pushes to main            (up to date)

Vale la pena leerla línea por línea, porque contiene cinco informaciones distintas:

Sección Qué significa
Fetch URL / Push URL Las dos URL configuradas. Si difieren, aquí se ve
HEAD branch La rama por defecto del servidor. Es lo que determina en qué rama te deja un git clone
Remote branches Las ramas que existen ahora mismo en el servidor, con su estado
Local branches configured for 'git pull' Qué rama local se integra con cuál remota (lección 04-06)
Local refs configured for 'git push' Qué rama local va a dónde al enviar, y si está al día

Los tres estados posibles de una rama remota merecen explicación:

  • tracked: existe en el servidor y tú tienes su referencia local. Situación normal.
  • new: existe en el servidor pero tú aún no la tienes. Alguien la ha creado desde tu último fetch.
  • stale: tú tienes la referencia pero ya no existe en el servidor. Alguien la borró allí. Se limpia con git remote prune, que veremos en la lección 04-04.

Si solo quieres la información sin salir a la red:

git remote show -n origin

Con -n (no query), Git muestra únicamente lo que sabe por su configuración local. Es rápido y funciona sin conexión, pero la lista de ramas remotas será la de tu última sincronización, no la real.

  1. Cómo queda en .git/config

Volvamos al fichero, que es donde de verdad se entiende lo que ha pasado:

cat .git/config
[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true
[remote "origin"]
	url = https://git.ejemplo.es/equipo/gestor-tareas.git
	fetch = +refs/heads/*:refs/remotes/origin/*

Eso es todo lo que ha hecho git remote add. Una sección nueva con dos claves. Puedes editarla a mano con cualquier editor y funcionará igual; los comandos git remote no son más que una interfaz cómoda para escribir en este fichero.

Las mismas claves se pueden consultar y modificar con git config, porque son configuración normal y corriente:

git config remote.origin.url
https://git.ejemplo.es/equipo/gestor-tareas.git
git config remote.origin.fetch
+refs/heads/*:refs/remotes/origin/*

Recuerda de la lección 01-05 que estas claves están en el nivel local (.git/config), el más específico de los tres. Un remoto pertenece a un repositorio concreto y no tendría ningún sentido configurarlo en --global.

Y ahora, esa segunda línea.

  1. El refspec, explicado con calma

fetch = +refs/heads/*:refs/remotes/origin/*

Esta línea aparece en todos los repositorios Git del planeta y casi nadie sabría explicarla. Es una lástima, porque es sencilla y porque entenderla ilumina todo el módulo. Se llama refspec: una especificación de cómo se traducen las referencias del otro repositorio a referencias del tuyo.

La estructura

Un refspec tiene tres partes:

+refs/heads/*:refs/remotes/origin/*
│└─────────┬┘ └────────────┬─────┘
│      ORIGEN            DESTINO
│   (allí, en el         (aquí, en mi
│    repositorio          repositorio)
│    remoto)
│
└── modificador opcional
Parte Valor Significado
+ Modificador Permite actualizaciones no fast-forward: escribe el nuevo valor aunque no sea un avance limpio
refs/heads/* Origen Todas las ramas del repositorio remoto
: Separador "se traduce a"
refs/remotes/origin/* Destino Se guardan aquí, bajo el espacio de nombres del remoto

Leída en castellano llano:

"Coge todas las ramas del remoto y guárdalas en mi disco bajo refs/remotes/origin/, sobrescribiendo lo que haya."

Ejemplos concretos de la traducción que hace el asterisco:

Rama en el servidor Se guarda en tu disco como Nombre corto que usas
refs/heads/main refs/remotes/origin/main origin/main
refs/heads/documentacion/actualizar-notas refs/remotes/origin/documentacion/actualizar-notas origin/documentacion/actualizar-notas
refs/heads/correccion/foco-tras-borrar refs/remotes/origin/correccion/foco-tras-borrar origin/correccion/foco-tras-borrar

Aquí está el origen de la barra de origin/main, esa pregunta que quedó abierta al final del módulo 3. No es un separador arbitrario ni una sintaxis especial: es la ruta real del fichero dentro de .git/refs/. origin/main se llama así porque está en refs/remotes/origin/main.

flowchart LR
    subgraph REMOTO["Servidor: refs/heads/"]
        R1["main"]
        R2["documentacion/<br/>actualizar-notas"]
        R3["correccion/<br/>foco-tras-borrar"]
    end
    subgraph LOCAL["Tu disco: refs/remotes/origin/"]
        L1["origin/main"]
        L2["origin/documentacion/<br/>actualizar-notas"]
        L3["origin/correccion/<br/>foco-tras-borrar"]
    end
    R1 -->|refspec| L1
    R2 -->|refspec| L2
    R3 -->|refspec| L3

Por qué existe esa traducción

Podría parecer un rodeo innecesario. ¿No sería más simple que las ramas del servidor se copiaran tal cual a tus ramas locales?

No, y por una razón fundamental: serían las mismas referencias y se pisarían. Si refs/heads/main del servidor se copiara sobre tu refs/heads/main, cada fetch destruiría tu trabajo local no enviado. El espacio de nombres separado es lo que permite que convivan tres cosas a la vez:

  • Tu main, que tú controlas y donde confirmas.
  • Tu foto de su main (origin/main), que Git actualiza.
  • Su main, que está en otra máquina.

Y es también lo que hace posible que Git te diga "vas 2 commits por delante": tiene las dos referencias por separado y puede compararlas.

El + del principio

El modificador + significa "actualiza esta referencia aunque el cambio no sea un fast-forward".

Recuerda del módulo 3 que un fast-forward es un avance limpio: el valor nuevo es descendiente del anterior. Sin el +, Git se negaría a actualizar origin/main si en el servidor alguien hubiera reescrito la historia (con un push --force, por ejemplo), porque el commit nuevo no sería descendiente del que tú tenías registrado.

Para las referencias remotas queremos el +: su trabajo es reflejar fielmente el estado del servidor, sea cual sea. Si el servidor cambió de forma extraña, tu foto debe mostrar ese estado extraño; ya decidirás tú qué hacer con tus ramas locales. La protección contra reescrituras se aplica en el otro sentido, al enviar, y es el tema de la lección 04-05.

Refspecs a medida

El refspec por defecto trae todas las ramas, pero se puede afinar. Si un repositorio tiene cientos de ramas y solo te interesa main:

git config remote.origin.fetch '+refs/heads/main:refs/remotes/origin/main'

Sin comodín: solo esa rama. Es, de hecho, lo que hace por dentro git clone --single-branch, mencionado en la lección 02-02.

Se pueden acumular varias líneas para traer varios grupos:

git config --add remote.origin.fetch '+refs/heads/main:refs/remotes/origin/main'
git config --add remote.origin.fetch '+refs/heads/release/*:refs/remotes/origin/release/*'
[remote "origin"]
	url = https://git.ejemplo.es/equipo/gestor-tareas.git
	fetch = +refs/heads/main:refs/remotes/origin/main
	fetch = +refs/heads/release/*:refs/remotes/origin/release/*

Ahora git fetch origin traerá main y todas las ramas de publicación, e ignorará el resto. En un repositorio corporativo con setecientas ramas abiertas, esto es la diferencia entre un fetch de dos segundos y uno de treinta.

El mismo concepto reaparece en el envío. Cuando en la lección 04-05 escribas git push origin main:main, estarás dando un refspec a mano, en la misma sintaxis origen:destino. La coherencia es total: los dos lados de : significan siempre lo mismo, "de aquí" y "a aquí".

  1. Renombrar, eliminar y cambiar la URL

Tres operaciones de mantenimiento, todas locales, todas instantáneas y ninguna peligrosa.

Renombrar

git remote rename <viejo> <nuevo>
git remote rename origin central
git remote -v
central	https://git.ejemplo.es/equipo/gestor-tareas.git (fetch)
central	https://git.ejemplo.es/equipo/gestor-tareas.git (push)

Y esto no es solo un cambio de etiqueta. Renombrar un remoto obliga a Git a reescribir todo lo que dependía del nombre:

  • Las referencias remotas se mueven de refs/remotes/origin/* a refs/remotes/central/*. Tu origin/main pasa a llamarse central/main.
  • El refspec se actualiza para apuntar al espacio de nombres nuevo.
  • La configuración de seguimiento de las ramas (branch.main.remote) se reescribe para que main siga apuntando al remoto correcto.
git config --get-regexp '^(remote|branch)\.'
remote.central.url https://git.ejemplo.es/equipo/gestor-tareas.git
remote.central.fetch +refs/heads/*:refs/remotes/central/*
branch.main.remote central
branch.main.merge refs/heads/main

Todo coherente. Git ha hecho el trabajo completo. Volvamos al nombre convencional:

git remote rename central origin

Eliminar

git remote remove <nombre>     # forma recomendada
git remote rm <nombre>         # alias equivalente

Eliminar un remoto borra su sección de .git/config, todas sus referencias remotas (refs/remotes/<nombre>/*) y la configuración de seguimiento de las ramas que lo usaban.

Lo que no borra: ni un solo commit. Los objetos siguen en .git/objects/. Si eliminas un remoto por error, basta con volver a añadirlo y hacer fetch; lo único que pierdes es tiempo.

Cambiar la URL

Este es el caso más frecuente en la vida real: la empresa migra de servidor, o pasas de HTTPS a SSH tras configurar tus claves.

git remote set-url origin [email protected]:equipo/gestor-tareas.git
git remote -v
origin	[email protected]:equipo/gestor-tareas.git (fetch)
origin	[email protected]:equipo/gestor-tareas.git (push)

Mismo repositorio, mismo historial, mismas referencias: solo cambia el camino para llegar. Nada más se ve afectado, porque la identidad de los commits está en su hash, no en la URL desde la que llegaron.

Aquí se hace tangible aquella idea de la lección 04-01: el repositorio "oficial" lo es por convención. Migrar un equipo entero a otro servidor es un set-url por persona.

Comando Qué toca Requiere red
git remote add Crea la sección en .git/config No
git remote -v Solo lee .git/config No
git remote show <n> Lee config y pregunta al servidor
git remote rename Config + referencias remotas + seguimiento No
git remote remove Borra config + referencias remotas + seguimiento No
git remote set-url Solo la URL No
git remote prune <n> Borra referencias de ramas ya inexistentes

  1. set-url --push: leer de un sitio y escribir en otro

Una vuelta de tuerca que explica por qué git remote -v distingue (fetch) de (push):

git remote set-url --push origin [email protected]:equipo/gestor-tareas.git
git remote -v
origin	https://git.ejemplo.es/equipo/gestor-tareas.git (fetch)
origin	[email protected]:equipo/gestor-tareas.git (push)

Ahora Ana lee por HTTPS y escribe por SSH. Los dos accesos van al mismo repositorio, pero por caminos distintos.

¿Para qué sirve esto en la práctica?

  • Leer sin credenciales, escribir con ellas. Un repositorio público se clona por HTTPS sin autenticarse; para enviar hace falta identidad, y SSH la resuelve sin teclear nada (lección 04-03).
  • Cortafuegos corporativos. En algunas redes solo está abierto el 443 de HTTPS para salir, pero el envío pasa por otro camino.
  • Espejos de solo lectura. Leer de una réplica rápida y cercana, escribir siempre en el original.
  • Impedir envíos por error. Existe un truco conocido: apuntar la URL de push a algo inválido para que ningún envío accidental prospere.
# Convertir un remoto en estrictamente de solo lectura
git remote set-url --push origin NO_ENVIAR
git push origin main
fatal: 'NO_ENVIAR' does not appear to be a git repository
fatal: Could not read from remote repository.

Un buen seguro cuando clonas un proyecto ajeno solo para leerlo.

Para deshacer cualquiera de estos ajustes:

git remote set-url --delete --push origin NO_ENVIAR

Y las opciones relacionadas, para completar el cuadro:

# Añadir una URL adicional (envía a las dos: útil para espejos)
git remote set-url --add --push origin [email protected]:equipo/gestor-tareas.git

# Ver todas las URL de todos los remotos, incluidas las múltiples
git remote -v

Con dos URL de envío, un solo git push envía a los dos sitios. Es la forma más simple de mantener un espejo sincronizado sin herramientas externas.

  1. Trabajar con varios remotos

Un repositorio puede tener tantos remotos como quieras, y no es una rareza: es lo normal en cuanto un proyecto crece un poco.

Supongamos que Bruno mantiene además una copia personal del proyecto y que el equipo despliega enviando a un servidor de preproducción:

git remote add personal [email protected]:bruno/gestor-tareas.git
git remote add preproduccion [email protected]:apps/gestor-tareas.git
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)
preproduccion	[email protected]:apps/gestor-tareas.git (fetch)
preproduccion	[email protected]:apps/gestor-tareas.git (push)

Cada remoto tiene su propio espacio de referencias, completamente separado:

git fetch --all
git branch -r
  origin/HEAD -> origin/main
  origin/main
  origin/documentacion/actualizar-notas
  personal/main
  personal/experimento/pwa
  preproduccion/main

Ahora origin/main, personal/main y preproduccion/main son tres referencias distintas que pueden apuntar a tres commits distintos. Y las puedes comparar como cualquier otra referencia:

# ¿Qué tiene mi copia personal que no esté en el repositorio del equipo?
git log --oneline origin/main..personal/main

# ¿Va preproducción por detrás del equipo?
git log --oneline preproduccion/main..origin/main

# Traer una rama de mi copia personal
git switch -c experimento/pwa personal/experimento/pwa

Para qué sirve tener varios

Situación Configuración típica
Contribuir a un proyecto ajeno origin = tu copia personal; upstream = el proyecto original (módulo 7)
Desplegar por push origin = código; produccion = servidor de despliegue (módulo 10)
Espejo o copia de seguridad origin = principal; espejo = réplica
Migración de servidor origin = el viejo; nuevo = el destino, mientras dura la transición
Intercambio directo entre compañeros origin = servidor; bruno = su portátil, para trabajo puntual a cuatro manos

Ese último caso ilustra lo que decíamos en 04-01 sobre la naturaleza distribuida de Git. Si Ana y Bruno están sentados en la misma sala trabajando juntos:

# Ana registra el repositorio de Bruno, accesible por SSH en la red local
git remote add bruno [email protected]:/home/bruno/proyectos/gestor-tareas
git fetch bruno
git log --oneline bruno/funcionalidad/nueva-idea

Sin pasar por el servidor, sin publicar nada. Es Git haciendo exactamente aquello para lo que se diseñó.

Y un comando útil cuando hay varios:

# Traer de todos los remotos de una vez
git fetch --all
Fetching origin
Fetching personal
Fetching preproduccion

  1. init frente a clone: dos formas de tener un remoto

Cerramos con la comparación que da sentido a toda la lección, porque es la diferencia entre el camino de Ana y el de Bruno.

Repositorio con git init (Ana) Repositorio con git clone (Bruno)
Remoto al empezar Ninguno origin, automático
Hay que ejecutar remote add No
Referencias remotas al empezar Ninguna Todas las del servidor
Rama local de partida La que crees tú La rama por defecto del servidor
Seguimiento configurado No: hay que establecerlo Sí, automático
Primer envío git push -u origin main git push a secas
Historia de partida La tuya La del servidor
flowchart TB
    Q{"¿El proyecto ya<br/>existe en un servidor?"}
    Q -->|"No: yo lo empiezo"| I["git init<br/>trabajar, confirmar<br/><b>git remote add origin URL</b><br/>git push -u origin main"]
    Q -->|"Sí: me incorporo"| C["git clone URL<br/><b>origin ya registrado</b><br/>seguimiento ya configurado<br/>a trabajar"]
    I --> F["Repositorio con remoto<br/>y seguimiento"]
    C --> F

Los dos caminos llegan al mismo sitio. clone no es más que init + remote add + fetch + switch, empaquetado en un solo comando, tal como desglosamos en la lección 02-02. Ahora tienes las piezas sueltas y puedes montarlas a mano:

# Un clon "manual", paso a paso, equivalente a git clone
mkdir gestor-tareas && cd gestor-tareas
git init
git remote add origin https://git.ejemplo.es/equipo/gestor-tareas.git
git fetch origin
git switch main

Ese último git switch main merece una nota: aunque no exista todavía una rama local llamada main, Git ve que existe origin/main, deduce lo que quieres y crea la rama local con su seguimiento ya configurado. Se llama DWIM (Do What I Mean) y es el tema de la lección 04-06.

Ana está lista

El repositorio de Ana ya tiene su remoto:

git remote -v
origin	https://git.ejemplo.es/equipo/gestor-tareas.git (fetch)
origin	https://git.ejemplo.es/equipo/gestor-tareas.git (push)

Pero aún no ha enviado nada. Y si lo intentara ahora mismo contra un servidor real por HTTPS, se encontraría con esto:

Username for 'https://git.ejemplo.es':

Una pregunta que no sabe responder, porque su contraseña de la interfaz web probablemente no valga. Antes de enviar hay que resolver la autenticación, y ese es exactamente el tema de la lección siguiente.

Errores Comunes y Consejos

Error 1: creer que git remote add valida la URL. No lo hace: solo escribe en un fichero. Puedes registrar https://esto.no.existe/nada.git sin ningún aviso. La primera comprobación real llega con git fetch, git push o git ls-remote.

Error 2: error: remote origin already exists. Ocurre al intentar añadir un remoto que ya está, muy típico en repositorios clonados donde origin ya viene puesto. Si lo que quieres es cambiar su dirección, el comando es git remote set-url origin <url>, no add.

Error 3: confundir git remote -v con git remote show. El primero lee tu configuración y funciona sin conexión; el segundo pregunta al servidor y puede fallar por red o credenciales. Para diagnosticar "¿qué URL tengo configurada?" usa -v; para "¿qué hay ahora mismo en el servidor?", show.

Error 4: no entender el refspec y por tanto no entender por qué origin/main no es una rama. La línea +refs/heads/*:refs/remotes/origin/* dice literalmente dónde se guardan las referencias del remoto: en un espacio de nombres separado del de tus ramas. Sin esa separación, cada fetch te machacaría el trabajo.

Error 5: intentar git push a un remoto no bare. Si has montado tú el servidor con git init en vez de git init --bare, el envío será rechazado con branch is currently checked out. Recuerda la lección 04-01: los repositorios que reciben deben ser bare.

Error 6: dejar remotos muertos acumulados. Tras una migración de servidor, mucha gente añade el nuevo y deja el viejo. Meses después, git fetch --all tarda una eternidad esperando el tiempo de espera de un servidor apagado. Limpia con git remote remove.

Consejo 1: comprueba un remoto recién añadido con git ls-remote. Es la forma más barata de verificar que la URL, la red y las credenciales funcionan, sin descargar objetos:

git ls-remote origin

Si devuelve la lista de referencias del servidor, todo está en orden. Si falla, tienes el error concreto antes de haber invertido nada.

Consejo 2: usa siempre origin para el repositorio principal. La convención está tan asentada que cualquier documentación, script o compañero la da por supuesta. Reserva la creatividad para los remotos secundarios.

Consejo 3: guarda git remote -v en tu rutina de llegada a un repositorio. Ese comando y git log --oneline --graph -10 te dan en cinco segundos el mapa completo de dónde estás.

Ejercicios

Ejercicio 1: publicar un proyecto existente

Simula el caso de Ana de principio a fin, en local:

  1. Crea un repositorio normal con dos o tres commits y una rama adicional.
  2. Comprueba que no tiene ningún remoto.
  3. Crea un repositorio bare que hará de servidor.
  4. Regístralo como origin.
  5. Muestra el contenido de .git/config antes y después, señalando exactamente qué líneas ha añadido el comando.
  6. Verifica con git ls-remote que el remoto responde, y explica qué devuelve estando el servidor vacío.

Ejercicio 2: descifrar refspecs

Traduce al castellano cada uno de estos refspecs y di qué haría un git fetch con cada uno:

  1. +refs/heads/*:refs/remotes/origin/*
  2. +refs/heads/main:refs/remotes/origin/main
  3. refs/heads/main:refs/remotes/origin/main (sin el +)
  4. +refs/heads/release/*:refs/remotes/origin/release/*
  5. +refs/tags/*:refs/tags/*

Después, configura el número 4 en un repositorio de prueba y demuestra con comandos que un fetch ya no trae las ramas que no empiezan por release/.

Ejercicio 3: dos remotos y una comparación

Monta esta situación y resuélvela con comandos:

  1. Un bare equipo.git y otro bare personal.git.
  2. Un repositorio de trabajo con ambos registrados como origin y personal.
  3. Envía un commit distinto a cada uno, de modo que sus historias diverjan.
  4. Responde: ¿qué commits tiene personal que no tenga origin? ¿Y al revés?
  5. Configura el repositorio para que lea de origin pero envíe a personal, y demuestra que funciona.

Soluciones

Solución 1:

mkdir -p /tmp/ej1 && cd /tmp/ej1

# 1. Repositorio con historia
git init -b main proyecto
cd proyecto
echo "<h1>Gestor de tareas</h1>" > index.html
git add . && git commit -m "Estructura inicial del gestor de tareas"
echo "body { font-family: sans-serif; }" > estilos.css
git add . && git commit -m "Añadir estilos base del listado"
git switch -c documentacion/actualizar-notas
echo "# Gestor de tareas" > README.md
git add . && git commit -m "Añadir el README del proyecto"
git switch main
# 2. Sin remotos
git remote -v
(sin salida)
# 5a. La configuración antes
cat .git/config
[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true
# 3. El servidor
git init --bare /tmp/ej1/servidor.git

# 4. Registrarlo
git remote add origin /tmp/ej1/servidor.git
# 5b. La configuración después
cat .git/config
[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true
[remote "origin"]
	url = /tmp/ej1/servidor.git
	fetch = +refs/heads/*:refs/remotes/origin/*

Las dos líneas nuevas son la sección [remote "origin"] con su url y su fetch. Nada más. Ni referencias, ni objetos, ni conexión.

# 6. Comprobar que responde
git ls-remote origin
(sin salida, y código de salida 0)
echo $?
0

La ausencia de salida es la respuesta correcta: el servidor ha contestado y su lista de referencias está vacía, porque el repositorio bare aún no tiene ni un commit. Lo importante es que el código de salida sea 0: significa que Git ha llegado hasta allí y ha podido hablar. Compáralo con una URL mala:

git ls-remote /tmp/ej1/no-existe.git
fatal: '/tmp/ej1/no-existe.git' does not appear to be a git repository

Solución 2:

  1. +refs/heads/*:refs/remotes/origin/* — "Trae todas las ramas del remoto y guárdalas bajo refs/remotes/origin/, forzando la actualización aunque no sea un avance limpio." Es el comportamiento por defecto de cualquier clon.

  2. +refs/heads/main:refs/remotes/origin/main — "Trae solo la rama main y guárdala como origin/main." El resto de ramas del servidor se ignoran por completo; ni siquiera aparecerán en git branch -r. Equivale a --single-branch.

  3. refs/heads/main:refs/remotes/origin/main — Lo mismo, pero sin el +: si en el servidor se reescribiera la historia de main, el fetch fallaría en lugar de actualizar la referencia, porque el valor nuevo no sería descendiente del anterior. Rara vez se quiere esto en una referencia remota, cuyo trabajo es reflejar el servidor tal cual.

  4. +refs/heads/release/*:refs/remotes/origin/release/* — "Trae solo las ramas cuyo nombre empiece por release/ y consérvalas con la misma estructura bajo origin/release/." Útil en repositorios enormes donde solo te interesan las ramas de publicación.

  5. +refs/tags/*:refs/tags/* — "Trae todas las etiquetas del remoto y guárdalas como etiquetas locales, con el mismo nombre." Fíjate en que aquí no hay traducción de espacio de nombres: origen y destino son iguales. Las etiquetas no se separan por remoto como las ramas, y por eso una etiqueta traída es indistinguible de una creada por ti. Las etiquetas son el tema de la lección 05-05.

Comprobación del caso 4:

cd /tmp/ej1/proyecto

# Preparar el servidor con varias ramas
git push origin main
git switch -c release/1.0 && git commit --allow-empty -m "Versión 1.0" && git push origin release/1.0
git switch -c release/1.1 && git commit --allow-empty -m "Versión 1.1" && git push origin release/1.1
git switch main
git push origin documentacion/actualizar-notas

# Un clon nuevo con refspec restringido
cd /tmp/ej1
git clone servidor.git restringido
cd restringido
git config remote.origin.fetch '+refs/heads/release/*:refs/remotes/origin/release/*'

# Borrar las referencias que ya tenía, para partir de cero
git branch -r | grep -v HEAD | sed 's/^ *//' | xargs -r -n1 git branch -rd

git fetch origin
git branch -r
  origin/release/1.0
  origin/release/1.1

origin/main y origin/documentacion/actualizar-notas no aparecen. El refspec restringido las ha dejado fuera: Git ni siquiera se ha molestado en traerlas.

Solución 3:

mkdir -p /tmp/ej3 && cd /tmp/ej3

# 1. Los dos servidores
git init --bare equipo.git
git init --bare personal.git

# 2. El repositorio de trabajo con los dos remotos
git init -b main trabajo
cd trabajo
echo "<h1>Gestor de tareas</h1>" > index.html
git add . && git commit -m "Estructura inicial del gestor de tareas"

git remote add origin /tmp/ej3/equipo.git
git remote add personal /tmp/ej3/personal.git
git remote -v
origin	/tmp/ej3/equipo.git (fetch)
origin	/tmp/ej3/equipo.git (push)
personal	/tmp/ej3/personal.git (fetch)
personal	/tmp/ej3/personal.git (push)
# 3. Base común y luego divergencia
git push origin main
git push personal main

echo "console.log('equipo');" > app.js
git add . && git commit -m "Añadir el esqueleto de la lógica de tareas"
git push origin main

git reset --hard HEAD~1
echo "console.log('personal');" > experimento.js
git add . && git commit -m "Probar una idea suelta"
git push personal main

git fetch --all
# 4. Comparar los dos remotos
git log --oneline origin/main..personal/main
9c4e2b7 Probar una idea suelta
git log --oneline personal/main..origin/main
5a1d8f3 Añadir el esqueleto de la lógica de tareas

Cada uno tiene un commit que el otro no tiene: han divergido desde el commit inicial común. Se puede ver de golpe:

git rev-list --left-right --count origin/main...personal/main
1	1

Un commit exclusivo a cada lado. Esta sintaxis de tres puntos y su lectura son el tema de la lección 04-06.

# 5. Leer de origin, enviar a personal
git remote set-url --push origin /tmp/ej3/personal.git
git remote -v
origin	/tmp/ej3/equipo.git (fetch)
origin	/tmp/ej3/personal.git (push)
personal	/tmp/ej3/personal.git (fetch)
personal	/tmp/ej3/personal.git (push)
# Demostración: un fetch de origin trae lo del equipo…
git fetch origin
git log --oneline -1 origin/main
5a1d8f3 Añadir el esqueleto de la lógica de tareas
# …pero un push a origin escribe en personal
git commit --allow-empty -m "Comprobación de destino de envío"
git push origin main
git --git-dir=/tmp/ej3/personal.git log --oneline -1 main
e7b3f2a Comprobación de destino de envío
# Y el repositorio del equipo NO lo ha recibido
git --git-dir=/tmp/ej3/equipo.git log --oneline -1 main
5a1d8f3 Añadir el esqueleto de la lógica de tareas

Confirmado: origin lee de equipo.git y escribe en personal.git. Es el mecanismo que usan quienes clonan un proyecto ajeno de solo lectura y envían sus cambios a su propia copia.

Conclusión

En esta lección Ana ha dado el paso que llevaba tres módulos pendiente: conectar su repositorio con el mundo. Lo aprendido:

  • git remote add <nombre> <url> registra un remoto, y es una operación puramente local: escribe tres líneas en .git/config, no valida la URL y no toca la red. La primera comprobación real llega con fetch, push o git ls-remote.
  • git remote -v lista los remotos con sus URL de lectura y escritura, sin conexión. git remote show <nombre> sí consulta el servidor y da la radiografía completa: rama por defecto, ramas remotas con su estado (tracked, new, stale) y la configuración de pull y push.
  • Todo vive en .git/config, en una sección [remote "origin"] con dos claves: url y fetch.
  • El refspec +refs/heads/*:refs/remotes/origin/* dice: "trae todas las ramas del remoto y guárdalas bajo refs/remotes/origin/, forzando la actualización". El + permite actualizaciones no fast-forward; el : separa origen y destino. De ahí sale la barra de origin/main: es la ruta real del fichero. Y esa separación de espacios de nombres es lo que impide que un fetch te machaque las ramas locales.
  • Renombrar, eliminar y cambiar la URL son operaciones locales e inofensivas. Renombrar mueve además las referencias remotas y reescribe la configuración de seguimiento; eliminar no borra ningún commit.
  • set-url --push permite leer de un sitio y escribir en otro, o bloquear los envíos apuntando a una URL inválida.
  • Varios remotos conviven sin problema, cada uno con su espacio de referencias, y se comparan entre sí como cualquier otro nombre de commit.
  • init frente a clone: el primero exige registrar el remoto a mano; el segundo lo hereda. Los dos llegan al mismo sitio, porque clone es init + remote add + fetch + switch.

Lo que viene

El remoto de Ana está configurado, pero si intentara enviar ahora mismo se toparía con una pregunta incómoda: Username for 'https://git.ejemplo.es':. Registrar una URL no da permiso para escribir en ella.

En la lección 04-03: Autenticación con Repositorios Remotos resolvemos ese muro. Veremos por qué el clone de un repositorio público no pide nada y el push sí, qué es un token personal de acceso y por qué sustituyó a la contraseña, cómo generar y usar una clave SSH con ssh-keygen -t ed25519, y cómo cada sistema operativo guarda las credenciales para no tener que teclearlas cuarenta veces al día. Es especialmente relevante para Carla, que va a incorporarse desde Windows 11 y necesita dejar esto resuelto antes de escribir su primera línea de código.

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