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
- El punto de partida: un repositorio sin remotos
git remote add: registrar el primerogit remote -v: qué remotos tengogit remote show: la radiografía completa- Cómo queda en
.git/config - El refspec, explicado con calma
- Renombrar, eliminar y cambiar la URL
set-url --push: leer de un sitio y escribir en otro- Trabajar con varios remotos
initfrente aclone: dos formas de tener un remoto
- El punto de partida: un repositorio sin remotos
Ana está en su proyecto, tal como lo dejamos al final del módulo 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
Y la pregunta del día:
Silencio. No hay ningún remoto configurado, que es exactamente lo esperable en un repositorio creado con git init. Podemos confirmarlo mirando el disco:
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:
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.
git remote add: registrar el primero
git remote add: registrar el primeroLa sintaxis es tan simple como el concepto:
Ana lo ejecuta:
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 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:
Reglas del nombre
- Debe ser único dentro del repositorio: si ya existe
origin,git remote add origin …falla conerror: remote origin already exists. - Admite letras, números, guiones y guiones bajos. Evita espacios, barras y acentos.
- No hay ninguna palabra reservada.
origines convención pura, como vimos en 04-01.
La opción -f
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.
git remote -v: qué remotos tengo
git remote -v: qué remotos tengoLos dos comandos de consulta básicos:
Solo los nombres. Y la versión útil:
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.
git remote show: la radiografía completa
git remote show: la radiografía completaEste comando es de otra categoría, y conviene entender por qué:
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:
* 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 últimofetch.stale: tú tienes la referencia pero ya no existe en el servidor. Alguien la borró allí. Se limpia congit remote prune, que veremos en la lección 04-04.
Si solo quieres la información sin salir a la red:
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.
- Cómo queda en
.git/config
.git/configVolvamos al fichero, que es donde de verdad se entiende lo que ha pasado:
[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:
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.
- El refspec, explicado con calma
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:
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í".
- Renombrar, eliminar y cambiar la URL
Tres operaciones de mantenimiento, todas locales, todas instantáneas y ninguna peligrosa.
Renombrar
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/*arefs/remotes/central/*. Tuorigin/mainpasa a llamarsecentral/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 quemainsiga apuntando al remoto correcto.
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:
Eliminar
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 -vorigin [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 | Sí |
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 | Sí |
set-url --push: leer de un sitio y escribir en otro
set-url --push: leer de un sitio y escribir en otroUna 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 -vorigin 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
pusha algo inválido para que ningún envío accidental prospere.
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:
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 -vCon 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.
- 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 -vorigin 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:
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/pwaPara 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-ideaSin pasar por el servidor, sin publicar nada. Es Git haciendo exactamente aquello para lo que se diseñó.
Y un comando útil cuando hay varios:
init frente a clone: dos formas de tener un remoto
init frente a clone: dos formas de tener un remotoCerramos 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 |
Sí | 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 mainEse ú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:
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:
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:
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:
- Crea un repositorio normal con dos o tres commits y una rama adicional.
- Comprueba que no tiene ningún remoto.
- Crea un repositorio bare que hará de servidor.
- Regístralo como
origin. - Muestra el contenido de
.git/configantes y después, señalando exactamente qué líneas ha añadido el comando. - Verifica con
git ls-remoteque 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:
+refs/heads/*:refs/remotes/origin/*+refs/heads/main:refs/remotes/origin/mainrefs/heads/main:refs/remotes/origin/main(sin el+)+refs/heads/release/*:refs/remotes/origin/release/*+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:
- Un bare
equipo.gity otro barepersonal.git. - Un repositorio de trabajo con ambos registrados como
originypersonal. - Envía un commit distinto a cada uno, de modo que sus historias diverjan.
- Responde: ¿qué commits tiene
personalque no tengaorigin? ¿Y al revés? - Configura el repositorio para que lea de
originpero envíe apersonal, 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# 3. El servidor
git init --bare /tmp/ej1/servidor.git
# 4. Registrarlo
git remote add origin /tmp/ej1/servidor.git[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.
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:
Solución 2:
-
+refs/heads/*:refs/remotes/origin/*— "Trae todas las ramas del remoto y guárdalas bajorefs/remotes/origin/, forzando la actualización aunque no sea un avance limpio." Es el comportamiento por defecto de cualquier clon. -
+refs/heads/main:refs/remotes/origin/main— "Trae solo la ramamainy guárdala comoorigin/main." El resto de ramas del servidor se ignoran por completo; ni siquiera aparecerán engit branch -r. Equivale a--single-branch. -
refs/heads/main:refs/remotes/origin/main— Lo mismo, pero sin el+: si en el servidor se reescribiera la historia demain, elfetchfallarí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. -
+refs/heads/release/*:refs/remotes/origin/release/*— "Trae solo las ramas cuyo nombre empiece porrelease/y consérvalas con la misma estructura bajoorigin/release/." Útil en repositorios enormes donde solo te interesan las ramas de publicación. -
+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 -rorigin/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 -vorigin /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 --allCada uno tiene un commit que el otro no tiene: han divergido desde el commit inicial común. Se puede ver de golpe:
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 -vorigin /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# …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# Y el repositorio del equipo NO lo ha recibido
git --git-dir=/tmp/ej3/equipo.git log --oneline -1 mainConfirmado: 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 confetch,pushogit ls-remote.git remote -vlista 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 depullypush.- Todo vive en
.git/config, en una sección[remote "origin"]con dos claves:urlyfetch. - El refspec
+refs/heads/*:refs/remotes/origin/*dice: "trae todas las ramas del remoto y guárdalas bajorefs/remotes/origin/, forzando la actualización". El+permite actualizaciones no fast-forward; el:separa origen y destino. De ahí sale la barra deorigin/main: es la ruta real del fichero. Y esa separación de espacios de nombres es lo que impide que unfetchte 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 --pushpermite 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.
initfrente aclone: el primero exige registrar el remoto a mano; el segundo lo hereda. Los dos llegan al mismo sitio, porquecloneesinit+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
- ¿Qué es Git?
- Instalando Git
- Terminología Básica de Git
- El Modelo de Datos de Git
- Configurando Git
- Configuración Inicial
Módulo 2: Operaciones Básicas de Git
- Creando un Repositorio
- Clonando un Repositorio
- Flujo de Trabajo Básico de Git
- Preparando y Confirmando Cambios
- Inspeccionando Cambios con git diff
- Visualizando el Historial de Confirmaciones
Módulo 3: Ramas y Fusión
- Entendiendo las Ramas
- Creando y Cambiando Ramas
- Fusionando Ramas
- Estrategias de Fusión
- Resolviendo Conflictos de Fusión
- Gestión de Ramas
Módulo 4: Trabajando con Repositorios Remotos
- Entendiendo los Repositorios Remotos
- Agregando un Repositorio Remoto
- Autenticación con Repositorios Remotos
- Obteniendo y Extrayendo Cambios
- Enviando Cambios
- Rastreando Ramas
Módulo 5: Operaciones Avanzadas de Git
- Rebase
- Rebase Interactivo
- Cherry-Picking de Confirmaciones
- Guardando Cambios Temporales
- Etiquetando Confirmaciones
- Revirtiendo Confirmaciones
Módulo 6: Herramientas y Técnicas de Git
- Usando Git Hooks
- Git Bisect
- Git Blame
- Git Log y Alias
- Submódulos de Git
- Múltiples Copias de Trabajo con git worktree
Módulo 7: Estrategias de Colaboración y Flujo de Trabajo
- Forks y Pull Requests
- Revisiones de Código con Git
- Flujo de Trabajo Git Flow
- GitHub Flow
- Trunk Based Development
- Integración Continua con Git
Módulo 8: Mejores Prácticas y Consejos de Git
- Escribiendo Buenos Mensajes de Confirmación
- Manteniendo un Historial Limpio
- Ignorando Archivos con .gitignore
- Atributos de Fichero con .gitattributes
- Mejores Prácticas de Seguridad
- Consejos de Rendimiento
Módulo 9: Solución de Problemas y Depuración
- Problemas Comunes de Git
- Deshaciendo Cambios
- Resolviendo Divergencias con el Remoto
- Recuperando Confirmaciones Perdidas
- Tratando con Repositorios Corruptos
- Técnicas Avanzadas de Depuración
