Cerramos el módulo 1 con el portátil de Ana preparado: Git instalado, la identidad configurada, init.defaultBranch valiendo main y el editor listo. Todo el andamiaje estaba montado, pero gestor-tareas seguía siendo lo que era el primer día: una carpeta normal y corriente con cuatro ficheros dentro. Ni historial, ni versiones, ni forma de volver atrás.

En esta lección damos el paso que lo cambia todo. Con un único comando, git init, esa carpeta pasa a ser un repositorio: Git empieza a observarla, aparece el directorio .git/ con la base de datos de objetos que estudiamos en El Modelo de Datos de Git, y a partir de ahí cada cambio puede quedar registrado para siempre.

Veremos qué crea exactamente git init, qué dice git status en un repositorio recién nacido (y por qué habla de "no commits yet"), cómo se crea la primera confirmación del proyecto, en qué se diferencia arrancar de cero de convertir un proyecto que ya existe, y cómo deshacer un git init lanzado por error en el sitio equivocado —algo que le pasa a casi todo el mundo alguna vez.

Contenido

  1. El punto de partida: la carpeta de Ana
  2. git init: el comando que crea el repositorio
  3. Qué crea exactamente dentro de .git/
  4. git status en un repositorio recién creado
  5. Qué significa "No commits yet"
  6. La primera confirmación del proyecto
  7. Convertir un proyecto existente frente a empezar de cero
  8. Repositorios bare: qué son y para qué sirven
  9. Cómo deshacer un git init accidental
  10. Comprobaciones útiles antes de seguir

  1. El punto de partida: la carpeta de Ana

Ana trabaja en Ubuntu y tiene su proyecto en ~/proyectos/gestor-tareas. Lo ha escrito durante un par de tardes y contiene cuatro ficheros:

cd ~/proyectos/gestor-tareas
ls -l
total 16
-rw-rw-r-- 1 ana ana  486 jul 20 18:12 app.js
-rw-rw-r-- 1 ana ana  312 jul 20 17:55 estilos.css
-rw-rw-r-- 1 ana ana  602 jul 20 18:04 index.html
-rw-rw-r-- 1 ana ana  198 jul 20 18:20 README.md

El contenido es sencillo. index.html monta la página:

<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="utf-8">
  <title>Gestor de Tareas</title>
  <link rel="stylesheet" href="estilos.css">
</head>
<body>
  <h1>Gestor de Tareas</h1>
  <form id="form-tarea">
    <input type="text" id="campo-tarea" placeholder="¿Qué hay que hacer?">
    <button type="submit">Añadir</button>
  </form>
  <ul id="lista-tareas"></ul>
  <script src="app.js"></script>
</body>
</html>

Y app.js contiene la lógica mínima para añadir tareas:

// gestor-tareas — lógica principal
const tareas = [];

function anadirTarea(texto) {
  tareas.push({ id: Date.now(), texto: texto, hecha: false });
  pintarLista();
}

function pintarLista() {
  const lista = document.querySelector('#lista-tareas');
  lista.innerHTML = '';
  for (const tarea of tareas) {
    const li = document.createElement('li');
    li.textContent = tarea.texto;
    lista.appendChild(li);
  }
}

document.querySelector('#form-tarea').addEventListener('submit', function (evento) {
  evento.preventDefault();
  const campo = document.querySelector('#campo-tarea');
  if (campo.value.trim() !== '') {
    anadirTarea(campo.value.trim());
    campo.value = '';
  }
});

Estos ficheros nos acompañarán durante todo el curso. Fíjate en un detalle que importará más adelante: no hay ninguna carpeta oculta. Comprobémoslo:

ls -a
.  ..  app.js  estilos.css  index.html  README.md

Solo están . (el directorio actual) y .. (el padre). Ni rastro de .git. Esto es lo que Git llama, con precisión, un directorio que no está bajo control de versiones. Si Ana borrase app.js ahora mismo, no habría nada que hacer.

  1. git init: el comando que crea el repositorio

Ana se sitúa dentro de la carpeta del proyecto y ejecuta:

cd ~/proyectos/gestor-tareas
git init
Initialized empty Git repository in /home/ana/proyectos/gestor-tareas/.git/

Ya está. Eso es todo. Analicemos el mensaje con calma, porque cada palabra dice algo:

  • Initialized: se ha creado la estructura desde cero.
  • empty: el repositorio está vacío en el sentido de que no contiene ninguna confirmación. Ojo: los cuatro ficheros de Ana siguen ahí intactos. "Vacío" se refiere al historial, no a la carpeta.
  • La ruta /home/ana/proyectos/gestor-tareas/.git/: te dice exactamente dónde ha quedado el repositorio. Merece la pena leerla siempre, porque es la forma más rápida de detectar que lo has ejecutado en el sitio equivocado.

Las dos formas de invocarlo

Forma Qué hace Cuándo usarla
git init Convierte el directorio actual en repositorio Ya tienes una carpeta con ficheros (el caso de Ana)
git init <nombre> Crea el directorio <nombre> si no existe y lo inicializa Empiezas un proyecto desde cero

La segunda forma ahorra un paso. Si Ana fuera a empezar un proyecto nuevo llamado notas-equipo:

cd ~/proyectos
git init notas-equipo
Initialized empty Git repository in /home/ana/proyectos/notas-equipo/.git/
ls -a notas-equipo
.  ..  .git

Git ha creado la carpeta y la ha inicializado, pero no ha puesto nada dentro: el proyecto está por escribir.

Opciones que conviene conocer

# Forzar el nombre de la rama inicial en este repositorio concreto
git init --initial-branch=main
git init -b main          # forma abreviada

# Inicializar con salida mínima
git init --quiet

Ana no necesita -b main porque ya fijó init.defaultBranch=main en su configuración global en Configuración Inicial. Pero si trabajas en un equipo donde no todos lo tienen configurado, git init -b main garantiza el mismo punto de partida para todo el mundo.

Nota sobre versiones antiguas. La opción -b / --initial-branch existe desde Git 2.28. En versiones anteriores la rama inicial siempre se llamaba master y no había forma de cambiarla en el momento de crear el repositorio. Si tu git --version está por debajo de 2.28, este es un buen motivo para actualizar.

  1. Qué crea exactamente dentro de .git/

Ahora sí, veamos qué ha aparecido:

ls -a
.  ..  .git  app.js  estilos.css  index.html  README.md

El único cambio visible es la carpeta .git. Miremos dentro:

ls -F .git
branches/  config  description  HEAD  hooks/  info/  objects/  refs/

Esto conecta directamente con lo que vimos en El Modelo de Datos de Git. Repasemos qué es cada pieza en un repositorio recién inicializado:

Elemento Qué es Estado recién creado
HEAD Referencia a la rama actual Apunta a refs/heads/main, que todavía no existe
config Configuración de nivel --local Solo unos pocos valores por defecto
description Nombre del repositorio para GitWeb Texto de relleno; irrelevante hoy
objects/ La base de datos de objetos Vacía: sin blobs, sin trees, sin commits
refs/ Ramas (heads/) y etiquetas (tags/) Ambos subdirectorios vacíos
hooks/ Scripts que se disparan en ciertos eventos Solo ejemplos desactivados (.sample)
info/ Información auxiliar, como exclude Prácticamente vacío
branches/ Directorio heredado, en desuso Vacío; puedes ignorarlo

Fíjate en lo que no está: no hay fichero index. El área de preparación es un fichero binario (.git/index) que Git crea la primera vez que preparas algo. Antes de eso, sencillamente no existe.

Mirando los ficheros clave

HEAD es un fichero de texto de una sola línea:

cat .git/HEAD
ref: refs/heads/main

Dice: "la rama actual es main". Pero ahora mismo esa rama es una promesa, no un hecho:

ls .git/refs/heads
(no muestra nada)

El directorio está vacío. main no existe como fichero de referencia porque una rama es, literalmente, un fichero que contiene el hash de una confirmación, y todavía no hay ninguna confirmación a la que apuntar. Esta situación —HEAD apuntando a una rama inexistente— se llama rama huérfana o unborn branch, y se resuelve sola en cuanto se cree el primer commit.

El config local también es corto:

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

Cuatro valores técnicos y nada más. Recuerda de Configurando Git que este fichero es el nivel --local, el de mayor precedencia: la identidad de Ana no está aquí porque la puso en --global, y desde allí aplica igual.

Y la base de datos de objetos está estrictamente vacía:

find .git/objects -type f
(no muestra nada)
ls .git/objects
info  pack

Dos subdirectorios vacíos esperando el primer blob. Esto ilustra bien la idea de fondo: git init no guarda ninguno de tus ficheros. Solo prepara el almacén. Guardar es cosa de git add y git commit.

  1. git status en un repositorio recién creado

git status es el comando que más veces escribirás en tu vida. Veamos qué dice ahora:

git status
On branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	README.md
	app.js
	estilos.css
	index.html

nothing added to commit but untracked files present (use "git add" to track)

Cuatro bloques de información, y todos importan:

  1. On branch main — en qué rama estás. Coincide con lo que leímos en .git/HEAD.
  2. No commits yet — el historial está vacío. Lo desarrollamos en el apartado siguiente.
  3. Untracked files — los ficheros sin seguimiento. Git los ve en el directorio de trabajo, pero nunca los ha registrado, así que no forman parte del proyecto todavía. Es el estado que presentamos en Terminología Básica de Git.
  4. La última línea — un resumen: no hay nada preparado, pero sí ficheros sin seguimiento.

Observa el detalle de que Git sugiere el comando siguiente entre paréntesis: use "git add <file>...". Git es insistentemente pedagógico en su salida. Leer esas sugerencias en lugar de saltárselas acorta muchísimo la curva de aprendizaje.

Consejo. Si esas sugerencias te resultan ruidosas cuando ya domines el flujo, puedes reducirlas con git config --global advice.statusHints false. Al principio, déjalas.

  1. Qué significa "No commits yet"

Es una frase que confunde al principiante porque parece decir "no has hecho nada", cuando en realidad tiene un significado técnico muy concreto:

HEAD apunta a la rama main, pero main no apunta a ninguna confirmación.

Es un estado transitorio y perfectamente válido, pero tiene consecuencias prácticas. Muchos comandos necesitan una confirmación de referencia y fallan hasta que exista la primera:

git log
fatal: your current branch 'main' does not have any commits yet
git diff HEAD
fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree.

No es un error tuyo: es que HEAD no resuelve a nada. En cuanto haya un commit, todos estos comandos funcionarán con normalidad.

Podemos verlo con la fontanería que aprendimos en el módulo 1:

git rev-parse HEAD
HEAD
fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree.

Este diagrama resume el estado del repositorio de Ana en este momento:

graph LR
    HEAD["HEAD"] -->|ref: refs/heads/main| MAIN["main<br/>(no existe todavía)"]
    MAIN -.->|apuntará a| C["primer commit<br/>(aún sin crear)"]
    OBJ["objects/<br/>vacío"]
    WT["Directorio de trabajo<br/>4 ficheros sin seguimiento"]

    style MAIN stroke-dasharray: 5 5
    style C stroke-dasharray: 5 5

  1. La primera confirmación del proyecto

Vamos a cerrar el círculo. En Flujo de Trabajo Básico de Git y sobre todo en Preparando y Confirmando Cambios veremos estos comandos con todo detalle; aquí los usamos en su forma más simple para que el repositorio deje de estar vacío.

Paso 1: preparar los cuatro ficheros.

git add README.md app.js estilos.css index.html

El comando no imprime nada. En Git, el silencio significa éxito. Comprobemos el efecto:

git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
	new file:   README.md
	new file:   app.js
	new file:   estilos.css
	new file:   index.html

Los cuatro ficheros han pasado de Untracked files a Changes to be committed: están en el área de preparación. Cada uno aparece marcado como new file porque no existían en ninguna confirmación anterior (no hay ninguna).

Y ahora sí existe el índice:

ls .git/index
.git/index

Y la base de datos ya tiene contenido:

find .git/objects -type f | wc -l
4

Cuatro objetos: un blob por cada fichero. Todavía no hay ni árbol ni commit; eso lo crea git commit.

Paso 2: confirmar.

git commit -m "Estructura inicial del gestor de tareas"
[main (root-commit) 1a4c8d6] Estructura inicial del gestor de tareas
 4 files changed, 68 insertions(+)
 create mode 100644 README.md
 create mode 100644 app.js
 create mode 100644 estilos.css
 create mode 100644 index.html

Leamos la primera línea, que es la más informativa:

  • [main — la rama sobre la que se ha confirmado.
  • (root-commit) — esta marca solo aparece en la primera confirmación de un historial: es un commit sin padre. Recuerda del modelo de datos que un commit normal guarda el hash de su padre; este no tiene ninguno.
  • 1a4c8d6 — el hash abreviado del nuevo commit.
  • El mensaje que escribimos.

Las líneas siguientes resumen qué entró: 4 ficheros, 68 líneas añadidas, y el modo 100644 (fichero normal) de cada uno, exactamente la notación que vimos al estudiar los trees.

Paso 3: comprobar el resultado.

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

Han desaparecido tanto No commits yet como la lista de ficheros. working tree clean significa que el directorio de trabajo coincide exactamente con la última confirmación: no hay nada pendiente. Es el mensaje más tranquilizador de Git.

Y la rama ya existe de verdad:

cat .git/refs/heads/main
1a4c8d6f2b9e5a3c7d1f4b8e6a2c9d5f3b7e1a4c

Un fichero con un hash dentro. Eso es una rama en Git: nada más y nada menos.

git log --oneline
1a4c8d6 (HEAD -> main) Estructura inicial del gestor de tareas

HEAD -> main indica que estamos situados en la rama main y que esa rama apunta a este commit. El proyecto de Ana ya tiene historia.

  1. Convertir un proyecto existente frente a empezar de cero

Los dos escenarios usan el mismo comando, pero las precauciones son distintas.

Empezar de cero Convertir proyecto existente
Comando típico git init <nombre> cd proyecto && git init
Estado inicial Carpeta vacía Ficheros ya presentes, sin seguimiento
Riesgo principal Ninguno Confirmar basura: dependencias, secretos, binarios
Paso previo recomendado Crear un README.md Escribir un .gitignore antes del primer git add
Primer commit Casi vacío Todo el proyecto de golpe

El riesgo del segundo caso es real y muy frecuente. Imagina que Ana hubiera instalado dependencias antes de inicializar el repositorio:

git status --short
?? README.md
?? app.js
?? estilos.css
?? index.html
?? node_modules/
?? .env

Si hiciera git add . sin pensar, metería en el historial miles de ficheros de node_modules/ (que se reinstalan con un comando y no aportan nada) y, mucho peor, .env con sus claves. Y como aprendimos en El Modelo de Datos de Git, el historial es inmutable: sacar de ahí un fichero con secretos no es "borrarlo", es reescribir el historial y rotar las claves.

La regla es sencilla: en un proyecto existente, escribe el .gitignore antes del primer git add. Un .gitignore mínimo para el caso de Ana:

node_modules/
.env
*.log
.DS_Store

Con eso, git status vuelve a mostrar solo lo que interesa. Veremos los patrones, las excepciones y los casos difíciles en Ignorando Archivos con .gitignore; por ahora quédate con la idea de que ese fichero existe y de que su sitio es antes del primer commit.

Un detalle importante: los ficheros existentes no se pierden

Conviene decirlo explícitamente porque genera ansiedad: git init no toca tus ficheros. No los mueve, no los modifica, no los borra. Solo crea .git/. Si ejecutas git init en una carpeta con trabajo de meses, ese trabajo sigue exactamente igual un segundo después. Lo único que cambia es que ahora puedes empezar a versionarlo.

  1. Repositorios bare: qué son y para qué sirven

Existe una variante que verás mencionada constantemente:

git init --bare gestor-tareas.git

Un repositorio bare (desnudo) es un repositorio sin directorio de trabajo. No contiene index.html ni app.js como ficheros con los que puedas trabajar: contiene únicamente la base de datos de objetos y las referencias, es decir, el contenido de lo que en un repositorio normal sería .git/, pero directamente en la raíz.

ls -F gestor-tareas.git
config  description  HEAD  hooks/  info/  objects/  refs/

¿Te suena? Es exactamente el mismo contenido que vimos dentro de .git/, pero sin carpeta contenedora y sin ficheros del proyecto al lado.

Repositorio normal Repositorio bare
Directorio de trabajo No
Puedes editar ficheros No
Puedes confirmar en él No directamente
Convención de nombre gestor-tareas gestor-tareas.git
Uso típico Tu copia de trabajo Servidor central compartido

Para qué sirve: es la forma correcta de montar el repositorio "central" al que todo el equipo envía y del que todo el equipo descarga. Cuando clonas de GitHub o GitLab, lo que hay al otro lado es un repositorio bare. Se hace así porque, si el repositorio central tuviera directorio de trabajo, recibir cambios podría dejarlo en un estado incoherente respecto a lo que hubiera en sus ficheros.

No lo desarrollamos más aquí: los repositorios bare cobran sentido cuando hablemos de remotos en el módulo 4. Quédate con la definición —repositorio sin copia de trabajo, pensado para compartir— y con el hecho de que en la próxima lección Bruno clonará precisamente de uno.

  1. Cómo deshacer un git init accidental

Le pasa a todo el mundo: ejecutas git init, miras el mensaje y descubres una ruta que no esperabas. El caso clásico:

cd ~
git init
Initialized empty Git repository in /home/ana/.git/

Acabas de convertir toda tu carpeta personal en un repositorio. git status intentará listar decenas de miles de ficheros sin seguimiento y, peor aún, cualquier proyecto que tengas dentro empezará a comportarse de forma rara, porque Git busca el .git más cercano hacia arriba y ahora encuentra ese.

La solución es tranquilizadora por lo simple: borrar la carpeta .git.

cd ~
rm -rf .git

En Windows, desde PowerShell:

Remove-Item -Recurse -Force .git

Eso es todo. Como git init no había hecho nada más que crear esa carpeta, borrarla devuelve el sistema exactamente a como estaba. Tus ficheros no se ven afectados en absoluto.

Precauciones antes de borrar

Aquí conviene ir con cuidado, porque rm -rf .git es inofensivo en un repositorio recién inicializado y catastrófico en uno con historial: borrarías todos los commits, ramas y etiquetas, y no habría forma de recuperarlos (salvo que exista una copia en otro sitio). Antes de ejecutarlo, hazte tres preguntas:

1. ¿Estoy donde creo que estoy?

pwd

2. ¿Cuál es el repositorio que voy a borrar?

git rev-parse --show-toplevel
/home/ana

Este comando devuelve la raíz del repositorio actual, que es justo lo que vas a eliminar. Si la ruta no es la que esperabas, no borres nada todavía.

3. ¿Tiene historial?

git log --oneline

Si responde does not have any commits yet, borrar es seguro. Si lista confirmaciones, detente y averigua qué repositorio es antes de tocar nada.

Cómo evitarlo la próxima vez

  • Lee siempre la ruta del mensaje de Initialized.... Un segundo de atención evita el problema entero.
  • Prefiere git init <nombre> cuando empieces de cero: crea la carpeta él mismo, así que no puedes equivocarte de sitio.
  • Comprueba antes de inicializar si ya estás dentro de un repositorio:
git rev-parse --is-inside-work-tree
fatal: not a git repository (or any of the parent directories): .git

Ese fatal es, en este caso, la respuesta que quieres: significa que no estás dentro de ningún repositorio y que git init creará uno nuevo de verdad.

Un aviso sobre repositorios anidados. Si ejecutas git init dentro de una carpeta que ya está bajo control de versiones, Git no protesta: creas un repositorio dentro de otro. El repositorio interior queda invisible para el exterior y suele acabar en confusión y trabajo perdido. Si de verdad necesitas anidar proyectos, la herramienta correcta son los submódulos, no un git init a mano.

  1. Comprobaciones útiles antes de seguir

Un puñado de comandos de diagnóstico que conviene tener a mano:

# ¿Estoy dentro de un repositorio?
git rev-parse --is-inside-work-tree     # → true

# ¿Cuál es la raíz del repositorio?
git rev-parse --show-toplevel           # → /home/ana/proyectos/gestor-tareas

# ¿Dónde está el directorio .git?
git rev-parse --git-dir                 # → .git

# ¿Es un repositorio bare?
git rev-parse --is-bare-repository      # → false

# ¿En qué rama estoy?
git branch --show-current               # → main

# ¿Con qué identidad voy a confirmar aquí?
git config user.name && git config user.email

Esta última es especialmente valiosa cuando usas perfiles condicionales con includeIf, como vimos al final de Configurando Git: te confirma qué identidad se aplicará en este repositorio concreto antes de crear una confirmación con el correo equivocado.

Errores Comunes y Consejos

  • Ejecutar git init sin mirar dónde estás. Es el error número uno. Lee la ruta del mensaje de confirmación; si no es la esperada, borra .git de inmediato antes de añadir nada.
  • Creer que git init ya guarda los ficheros. No guarda nada. Un repositorio recién inicializado tiene la base de datos vacía y todos tus ficheros en estado untracked. Hasta el primer git commit no hay ninguna copia de seguridad de nada.
  • Hacer git add . como primer comando en un proyecto existente. Sin .gitignore, arrastrarás dependencias, ficheros temporales, binarios de compilación y, en el peor de los casos, secretos. Escribe el .gitignore primero y revisa git status antes de preparar.
  • Confundir "repositorio vacío" con "carpeta vacía". El mensaje Initialized empty Git repository alarma a mucha gente que teme haber perdido su trabajo. "Vacío" se refiere al historial, no a los ficheros.
  • Inicializar un repositorio dentro de otro. Git lo permite en silencio y el resultado es desconcertante. Comprueba con git rev-parse --show-toplevel antes.
  • No confirmar nada durante días. Un repositorio sin commits no te protege de nada. La primera confirmación, aunque sea imperfecta, ya es una red de seguridad.
  • Consejo: acostúmbrate a que el primer commit de un proyecto se llame algo como "Estructura inicial del proyecto". Es la convención más extendida y ayuda a localizar el origen del historial de un vistazo.
  • Consejo: si tienes dudas sobre si una carpeta es un repositorio, ls -a en Linux/macOS o dir /a en Windows te lo dice en un segundo: si aparece .git, lo es.

Ejercicios

Ejercicio 1: Crear un repositorio desde cero y observarlo

Crea un repositorio nuevo llamado notas-equipo sin usar mkdir, y responde con comandos —no de memoria— a estas cuatro preguntas:

  1. ¿A qué rama apunta HEAD?
  2. ¿Existe ya esa rama como fichero en refs/heads?
  3. ¿Cuántos objetos hay en la base de datos?
  4. ¿Existe el fichero .git/index?

Después crea un README.md con una línea de texto, haz la primera confirmación y vuelve a responder a las cuatro preguntas. Explica qué ha cambiado y por qué.

Ejercicio 2: Convertir un proyecto existente sin meter basura

Prepara esta situación de partida:

mkdir -p ~/practica/tienda-online/node_modules/libreria
cd ~/practica/tienda-online
echo "<h1>Tienda</h1>" > index.html
echo "body { margin: 0; }" > estilos.css
echo "API_KEY=abc123secreto" > .env
echo "ruido" > node_modules/libreria/indice.js
echo "error del jueves" > depuracion.log

Convierte la carpeta en repositorio y haz un primer commit que contenga exclusivamente index.html, estilos.css y el .gitignore. Demuestra con la salida de git status que .env, node_modules/ y depuracion.log no aparecen ni siquiera como ficheros sin seguimiento.

Ejercicio 3: Diagnosticar y reparar un git init accidental

Un compañero te escribe: "he ejecutado git init no sé dónde y ahora git status me tarda muchísimo y me lista miles de ficheros que no son de mi proyecto. Además, cuando entro en mi carpeta ~/proyectos/api-clientes y hago git log, me dice que no hay confirmaciones, cuando yo sé que llevo veinte."

  1. ¿Qué ha pasado, exactamente?
  2. ¿Qué comandos debe ejecutar para confirmar el diagnóstico antes de tocar nada?
  3. ¿Cómo lo arregla sin perder el historial de api-clientes?
  4. ¿Por qué git log en api-clientes decía que no había confirmaciones si el .git de ese proyecto seguía intacto?

Soluciones

Solución al Ejercicio 1

cd ~/proyectos
git init notas-equipo
cd notas-equipo

Estado inicial:

cat .git/HEAD
# → ref: refs/heads/main

ls .git/refs/heads
# → (vacío)

find .git/objects -type f | wc -l
# → 0

ls .git/index
# → ls: cannot access '.git/index': No such file or directory

Respuestas: (1) HEAD apunta a refs/heads/main; (2) no, la rama no existe todavía; (3) cero objetos; (4) no existe el índice.

Después de la primera confirmación:

echo "# Notas del equipo" > README.md
git add README.md
git commit -m "Estructura inicial del proyecto"
[main (root-commit) 3f7b2e9] Estructura inicial del proyecto
 1 file changed, 1 insertion(+)
 create mode 100644 README.md
cat .git/HEAD
# → ref: refs/heads/main            (sin cambios)

cat .git/refs/heads/main
# → 3f7b2e9c1a5d8b4f2e6a9c3d7b1f5e8a4c2d9b6f    (ahora sí existe)

find .git/objects -type f | wc -l
# → 3

ls .git/index
# → .git/index                      (ahora sí existe)

Qué ha cambiado y por qué:

  • HEAD sigue igual. Siempre apuntaba a main; lo que faltaba era la rama, no la referencia.
  • main ya existe como fichero con un hash dentro: el commit le ha dado a la rama algo a lo que apuntar. Aquí se ve literalmente que una rama es un fichero de texto con un hash.
  • Hay 3 objetos, no 1. Son los tres objetos que exige el modelo de datos de 01-04: un blob con el contenido de README.md, un tree con el directorio raíz (una entrada: el nombre README.md y el hash del blob) y un commit que apunta a ese tree y no tiene padre.
  • .git/index existe porque git add lo creó al preparar el fichero.

Solución al Ejercicio 2

El orden es lo que resuelve el ejercicio: primero el .gitignore, después git add.

cd ~/practica/tienda-online
git init
Initialized empty Git repository in /home/ana/practica/tienda-online/.git/

Antes de preparar nada, escribimos las exclusiones:

cat > .gitignore <<'FIN'
node_modules/
.env
*.log
FIN

Comprobación:

git status --short
?? .gitignore
?? estilos.css
?? index.html

.env, node_modules/ y depuracion.log han desaparecido de la lista: Git los ve en el disco pero los ignora, así que ni siquiera los cuenta como untracked. Esa es la demostración que pedía el enunciado.

Ahora ya es seguro preparar todo:

git add .
git commit -m "Estructura inicial de la tienda"
[main (root-commit) 8c2f5a1] Estructura inicial de la tienda
 3 files changed, 5 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 estilos.css
 create mode 100644 index.html

Tres ficheros, los tres correctos.

Si alguien hubiera hecho git add . antes de crear el .gitignore, el fichero .env ya estaría en el área de preparación y añadir el .gitignore después no lo sacaría: .gitignore solo afecta a ficheros sin seguimiento. Habría que quitarlo explícitamente del índice, algo que veremos en Preparando y Confirmando Cambios. Y si ya se hubiera confirmado, el secreto quedaría en el historial de forma permanente y habría que rotar la clave.

Solución al Ejercicio 3

1. Qué ha pasado. Ha ejecutado git init en su directorio personal (~) o en ~/proyectos, es decir, en una carpeta por encima de sus proyectos. Ahora existe un .git en ese nivel superior.

Los dos síntomas se explican solos:

  • git status lista miles de ficheros porque el repositorio abarca toda la carpeta personal y todo está sin seguimiento.
  • Dentro de api-clientes ve un historial vacío porque... en realidad no lo ve. Ver el punto 4.

2. Confirmar el diagnóstico sin modificar nada:

cd ~/proyectos/api-clientes
git rev-parse --show-toplevel
# → /home/companero          ← la raíz NO es api-clientes: ahí está el problema

git rev-parse --git-dir
# → /home/companero/.git

ls -d ~/.git
# → /home/companero/.git     ← existe, no debería

Y comprobar que el repositorio accidental está realmente vacío antes de borrarlo:

git -C ~ log --oneline
# → fatal: your current branch 'main' does not have any commits yet

Ese fatal es la luz verde: no hay historial que perder.

3. La reparación:

rm -rf ~/.git

Y verificar que todo ha vuelto a su sitio:

cd ~/proyectos/api-clientes
git rev-parse --show-toplevel
# → /home/companero/proyectos/api-clientes

git log --oneline | head -3
# → sus veinte confirmaciones, intactas

4. Por qué git log fallaba. Porque Git busca el directorio .git subiendo por el árbol de directorios desde donde estás, y usa el primero que encuentra. Lo esperable es que el primero sea el de api-clientes... y así era. Aquí está la clave del enunciado: el compañero no estaba dentro de api-clientes cuando lanzó git log, o bien el .git de su proyecto no existía porque nunca lo creó dentro y había estado trabajando, sin saberlo, contra el repositorio de nivel superior.

Este matiz es el que hace peligroso el git init accidental: el .git de arriba no oculta al de abajo, pero sí captura cualquier comando que ejecutes en carpetas que no tengan su propio repositorio. Si el compañero llevaba días haciendo commits desde ~ creyendo que iban a api-clientes, todo su historial estaba en ~/.git y borrarlo con rm -rf lo destruiría. De ahí la insistencia del apartado 9: comprueba siempre git log --oneline antes de borrar. Si hubiera listado confirmaciones, la solución no sería borrar, sino mover ese .git a un lugar seguro y recuperar el trabajo desde él.

Conclusión

El proyecto de Ana ya no es una carpeta: es un repositorio. En esta lección hemos visto que:

  • git init convierte el directorio actual en repositorio, y git init <nombre> crea además la carpeta. Su único efecto es crear .git/; no toca ni guarda tus ficheros.
  • Lo que crea es el andamiaje vacío que ya conocíamos del modelo de datos: HEAD apuntando a main, objects/ sin ningún objeto, refs/heads/ sin ninguna rama y ni siquiera un fichero index.
  • git status es la brújula desde el primer segundo: nos dice la rama, que no hay confirmaciones todavía y qué ficheros están sin seguimiento, sugiriendo siempre el comando siguiente.
  • "No commits yet" tiene un significado técnico: HEAD apunta a una rama que aún no existe. Por eso git log y git diff HEAD fallan hasta la primera confirmación.
  • La primera confirmación es un root-commit: el único commit del historial sin padre. Al crearla aparecen los blobs, el tree y el commit, y refs/heads/main nace como fichero con un hash dentro.
  • Convertir un proyecto existente exige una precaución extra: escribir el .gitignore antes del primer git add, porque el historial es inmutable y sacar de él un secreto es mucho más caro que no meterlo.
  • Los repositorios bare son repositorios sin copia de trabajo, pensados para servir de punto central compartido.
  • Un git init accidental se deshace borrando .git, pero solo después de comprobar con git rev-parse --show-toplevel y git log --oneline que ese repositorio no contiene nada valioso.

Ana tiene su repositorio y su primer commit. Pero un proyecto de equipo no vive en un solo portátil: Bruno se incorpora la semana que viene y necesita su propia copia, con todo el historial, en su MacBook. Para eso no sirve git init —eso crearía un repositorio nuevo y vacío, sin relación con el de Ana— sino un comando distinto.

En la siguiente lección, Clonando un Repositorio, veremos qué hace realmente git clone: cómo copia toda la base de datos de objetos, crea el directorio de trabajo, registra el remoto origin y deja a Bruno con un historial idéntico al de Ana. Compararemos los protocolos disponibles (HTTPS, SSH y ruta local), las opciones más útiles como --branch y --depth, y dejaremos clara de una vez la diferencia conceptual entre init y clone.

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