En la lección anterior recorrimos el ciclo completo —editar, preparar, confirmar— y vimos cómo git status nos guía en cada paso. Pero usamos git add y git commit en su forma más elemental: preparar ficheros enteros y confirmar con un mensaje corto.
En la práctica eso se queda corto enseguida. Una tarde de trabajo real no produce cambios ordenados: produce un fichero con dos arreglos que no tienen nada que ver entre sí, un fichero de configuración que nunca debió versionarse, otro que hay que renombrar y un mensaje de confirmación con una errata. Git tiene una respuesta precisa para cada una de esas situaciones, y dominarlas es la diferencia entre un historial que se puede leer y uno que solo se puede sufrir.
Esta lección es la más densa del módulo. Vamos a ver git add a fondo —incluida la diferencia entre -A, -u y ., que confunde a casi todo el mundo— el modo interactivo por fragmentos, cómo deshacer una preparación, cómo mover y borrar ficheros dentro de Git, y todas las variantes útiles de git commit, incluida --amend. Cerraremos con el concepto que da sentido a todo: el commit atómico.
Contenido
git add: las formas de indicar qué preparar-Afrente a-ufrente a.: la tabla que resuelve la duda- Preparación por fragmentos con
git add -p - Quitar del área de preparación:
git restore --staged - Descartar cambios del directorio de trabajo:
git restore - Mover y renombrar con
git mv - Borrar con
git rm(y el caso especial--cached) git commit: todas las formas de confirmar--amend: corregir la última confirmación- El commit atómico
git add: las formas de indicar qué preparar
git add: las formas de indicar qué preparargit add copia el contenido actual de uno o varios ficheros al área de preparación. Acepta muchas formas de decirle sobre qué actuar:
# Un fichero concreto
git add app.js
# Varios ficheros
git add app.js estilos.css index.html
# Un directorio entero, recursivamente
git add src/
# Todo lo que hay bajo el directorio actual
git add .
# Todo el repositorio, estés donde estés dentro de él
git add -A
# Un patrón (comillas obligatorias, ver más abajo)
git add "*.css"Un detalle importante sobre los patrones
Cuando escribes git add *.css sin comillas, quien expande el asterisco es tu intérprete de órdenes, no Git. El resultado es que solo se expanden los ficheros del directorio actual, y si no hay ninguno el comando falla.
Con comillas, el patrón llega intacto a Git, que lo aplica de forma recursiva sobre todo el repositorio:
git add "*.css" # todos los .css del repositorio, a cualquier profundidad
git add *.css # solo los .css de la carpeta actual (los expande el shell)Es una diferencia sutil con consecuencias reales. La recomendación: usa comillas cuando quieras el comportamiento de Git y sé consciente de cuál estás pidiendo.
git add también prepara borrados y ficheros nuevos
Un malentendido frecuente es pensar que git add sirve solo para "añadir". En realidad su función es sincronizar el área de preparación con el directorio de trabajo para las rutas indicadas. Eso incluye:
- Ficheros nuevos → se empiezan a rastrear.
- Ficheros modificados → se actualiza su versión preparada.
- Ficheros borrados del disco → se prepara el borrado.
Este último caso sorprende:
Sí: git add sobre un fichero que ya no existe registra su desaparición. Tiene sentido si piensas en add como "toma nota del estado actual de esta ruta".
Comprobar sin ejecutar
Dos opciones muy útiles antes de un add amplio:
En un repositorio grande o desconocido, git add -n . cuesta un segundo y evita disgustos.
-A frente a -u frente a .: la tabla que resuelve la duda
-A frente a -u frente a .: la tabla que resuelve la dudaEsta es, con diferencia, la confusión más extendida sobre git add. Las tres formas se parecen y hacen cosas distintas. La diferencia se explica con dos ejes: qué tipos de cambio incluyen y sobre qué parte del repositorio actúan.
| Forma | Ficheros nuevos | Ficheros modificados | Ficheros borrados | Alcance |
|---|---|---|---|---|
git add . |
Sí | Sí | Sí | Solo desde el directorio actual hacia abajo |
git add -A (--all) |
Sí | Sí | Sí | Todo el repositorio, estés donde estés |
git add -u (--update) |
No | Sí | Sí | Todo el repositorio |
git add <ruta> |
Sí | Sí | Sí | Solo esa ruta |
Dos conclusiones se leen directamente de la tabla:
-ues el único que no incluye ficheros nuevos. Actúa exclusivamente sobre ficheros que Git ya rastrea. Es la opción correcta cuando quieres registrar todo tu trabajo sobre ficheros conocidos sin arriesgarte a añadir un fichero nuevo por accidente..y-Ahacen lo mismo salvo por el alcance. La diferencia solo se nota si no estás en la raíz del repositorio.
Demostración práctica
Preparemos el escenario en el repositorio de Ana:
cd ~/proyectos/gestor-tareas
mkdir -p src
echo "// utilidades" > src/utilidades.js # nuevo, dentro de src/
echo "/* nuevo */" >> estilos.css # modificado, en la raíz
rm README.md # borrado, en la raíz
echo "# temporal" > BORRADOR.md # nuevo, en la raíz
git status -sCaso A — git add . desde la raíz:
Todo entra: nuevos, modificados y borrados.
Caso B — git add . desde un subdirectorio:
Aquí está la trampa. Solo se ha preparado src/utilidades.js, porque . significa "el directorio actual". Los cambios de la raíz siguen sin preparar. Alguien que confirmara ahora, convencido de haber añadido todo, dejaría fuera tres cambios.
Caso C — git add -A desde el mismo subdirectorio:
-A ignora dónde estás y actúa sobre todo el repositorio. Es el comportamiento que la mayoría de la gente cree estar pidiendo cuando escribe git add ..
Caso D — git add -u:
Solo se han preparado los cambios de ficheros ya rastreados: la modificación de estilos.css y el borrado de README.md. Los dos ficheros nuevos siguen sin seguimiento. Este es el comportamiento que quieres cuando estás trabajando en medio de una carpeta llena de ficheros generados y no te fías de un add masivo.
Nota histórica. Antes de Git 2.0,
git add .no preparaba los borrados, y esa asimetría causaba muchísimos problemas. Desde Git 2.0 el comportamiento es el descrito en la tabla. Si lees documentación antigua que dice lo contrario, está desactualizada.
Cuál usar
git add <ruta>cuando sabes exactamente qué quieres. Es la opción por defecto de quien cuida su historial.git add -upara registrar todo tu trabajo sobre ficheros conocidos, sin sorpresas.git add -Acuando de verdad quieres todo y tienes un.gitignoreen el que confías.git add .solo si estás en la raíz y sabes que estás en la raíz.
Y en cualquier caso: git status antes de confirmar.
- Preparación por fragmentos con
git add -p
git add -pLlega el martes siguiente. Ana quiere implementar el marcado de tareas como completadas, pero mientras está en ello ve un color feo en estilos.css y lo corrige de paso. Al terminar, app.js contiene dos cambios sin relación: la funcionalidad nueva y una corrección menor de un mensaje de consola que también arregló al vuelo.
Preparar app.js entero mezclaría las dos cosas en la misma confirmación. La solución es el modo por fragmentos:
Git recorre el fichero mostrando los cambios en trozos (hunks) y pregunta qué hacer con cada uno:
diff --git a/app.js b/app.js
index 7b2e8f1..3c9d4a2 100644
--- a/app.js
+++ b/app.js
@@ -12,4 +12,5 @@ function anadirTarea(texto) {
tareas.push({ id: Date.now(), texto: texto, hecha: false });
pintarLista();
+ actualizarContador();
}
(1/3) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?Las respuestas posibles son muchas, pero en la práctica se usan cinco:
| Tecla | Acción |
|---|---|
y |
Sí, prepara este fragmento |
n |
No, déjalo sin preparar |
s |
Divide (split) el fragmento en otros más pequeños |
e |
Edita el fragmento a mano, línea a línea |
q |
Sal del modo interactivo |
a |
Prepara este fragmento y todos los que quedan del fichero |
d |
No prepares este ni ninguno de los que quedan del fichero |
? |
Muestra la ayuda con todas las opciones |
Veamos la sesión de Ana completa. Primer fragmento: la llamada al contador, que forma parte de la funcionalidad nueva.
Segundo fragmento: la función de marcado.
@@ -25,4 +26,12 @@ function pintarLista() {
const li = document.createElement('li');
li.textContent = tarea.texto;
+ li.addEventListener('click', function () {
+ tarea.hecha = !tarea.hecha;
+ pintarLista();
+ actualizarContador();
+ });
+ if (tarea.hecha) {
+ li.classList.add('hecha');
+ }
lista.appendChild(li);
(2/3) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? yTercer fragmento: la corrección del mensaje de consola, que no pertenece a esta confirmación.
@@ -48,4 +57,4 @@ document.querySelector('#form-tarea').addEventListener('submit', function (event
if (campo.value.trim() !== '') {
anadirTarea(campo.value.trim());
- console.log('tarea aniadida');
+ console.log('Tarea añadida correctamente');
campo.value = '';
(3/3) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? nResultado:
app.js sale como MM: una parte preparada (la funcionalidad) y otra pendiente (el mensaje). Ahora Ana puede confirmar solo lo primero:
Y después, en una confirmación aparte, lo demás:
git add app.js estilos.css
git commit -m "Corregir el mensaje de consola y el color del texto secundario"Dos confirmaciones limpias donde había un revoltijo.
s: dividir un fragmento
Si dos cambios independientes caen dentro del mismo fragmento porque están cerca, s lo parte:
Git solo puede dividir cuando hay al menos una línea de contexto sin cambios entre los dos bloques. Si los cambios son literalmente contiguos, s no estará disponible y habrá que usar e.
e: editar el fragmento a mano
Es la opción más potente y la que más asusta. Git abre el fragmento en tu editor (el que configuraste en Configuración Inicial) con instrucciones al final:
Las dos reglas que necesitas:
- Para no preparar una línea añadida (
+), bórrala del fragmento. - Para no preparar una línea eliminada (
-), cámbiale el-por un espacio.
Nunca borres una línea que empiece por - ni añadas líneas nuevas: el parche dejaría de aplicar y Git protestará (aunque te dará la oportunidad de reintentarlo).
Comandos relacionados.
-pfunciona también con otros comandos:git restore -p,git stash -p,git checkout -p. Ygit add -iabre un menú interactivo completo, del que-pes solo una opción. En la práctica,-pcubre el 95 % de los casos.
- Quitar del área de preparación:
git restore --staged
git restore --stagedPreparaste algo que no querías. La operación inversa es:
Ejemplo. Ana prepara todo por costumbre y se da cuenta de que BORRADOR.md no debía entrar:
BORRADOR.md ha vuelto a ser un fichero sin seguimiento. Su contenido en el disco no se ha tocado: --staged actúa solo sobre el área de preparación.
La forma antigua: git reset HEAD
Verás muchísima documentación —y muchos compañeros— usando esto:
git reset HEAD <fichero>
git reset <fichero> # equivalente, HEAD es el valor por defecto
git reset # quita TODO del área de preparaciónHace exactamente lo mismo. La diferencia es de diseño: git reset es un comando con tres modos y varios significados, y git checkout también estaba sobrecargado. Para resolverlo, Git 2.23 (2019) introdujo dos comandos con un propósito claro cada uno:
| Tarea | Forma moderna | Forma antigua |
|---|---|---|
| Quitar del área de preparación | git restore --staged <f> |
git reset HEAD <f> |
| Descartar cambios del disco | git restore <f> |
git checkout -- <f> |
| Cambiar de rama | git switch <rama> |
git checkout <rama> |
Las formas antiguas siguen funcionando y las verás por todas partes. Usa las modernas en tu trabajo y reconoce las antiguas cuando las leas. El uso de git reset para mover el HEAD y reescribir el historial es otro asunto, y se trata en Deshaciendo Cambios.
- Descartar cambios del directorio de trabajo:
git restore
git restoreSin --staged, git restore hace algo bastante más serio: sobrescribe el fichero del disco con la versión de referencia, descartando tus cambios.
Ana experimenta con un diseño en estilos.css, no le gusta nada y quiere volver atrás:
El fichero ha vuelto a como estaba. Los cambios se han perdido de forma irrecuperable: no estaban en ninguna confirmación ni en el área de preparación, así que Git no tiene copia de ellos. Este es uno de los pocos comandos de Git que destruye trabajo sin red de seguridad.
Aviso serio. Antes de un
git restore, pregúntate si de verdad quieres tirar ese trabajo. Si dudas, hay alternativas que lo guardan:git stash(lección 05-04) o simplemente confirmarlo y decidir después.
La combinación importa
Cuando un fichero está en estado MM —preparado y modificado después— hay que tener claro qué descarta cada variante:
| Comando | Área de preparación | Directorio de trabajo |
|---|---|---|
git restore --staged f |
Se restaura desde HEAD |
No se toca |
git restore f |
No se toca | Se restaura desde el área de preparación |
git restore --staged --worktree f |
Se restaura desde HEAD |
Se restaura desde HEAD |
git restore --source=HEAD~2 f |
— | Se pone la versión de hace 2 commits |
Fíjate en la segunda fila: git restore f a secas restaura desde el área de preparación, no desde el último commit. Si habías preparado una versión intermedia, es a esa a la que vuelves.
Opciones útiles:
# Descartar cambios de TODOS los ficheros del repositorio (peligroso)
git restore :/
# Descartar por fragmentos, revisando cada uno
git restore -p estilos.css
# Traer al disco una versión antigua de un fichero
git restore --source=HEAD~3 app.jsEsta última no es un "deshacer": modifica tu directorio de trabajo con contenido antiguo, que después puedes preparar y confirmar normalmente. Es una forma sencilla de recuperar el estado de un fichero concreto sin tocar el resto del proyecto.
- Mover y renombrar con
git mv
git mvAna ha creado un fichero notas-version.md y quiere llamarlo CAMBIOS.md. Podría hacerlo con el explorador de ficheros, pero entonces Git vería un fichero borrado y otro nuevo. Lo correcto es:
El código R indica un renombrado, y el cambio ya está preparado: git mv prepara el resultado automáticamente.
git mv es un atajo
Este comando no es magia. Es exactamente equivalente a tres órdenes:
De hecho, si hubieras usado mv a secas, Git detectaría el renombrado igualmente al preparar ambos cambios:
Esto conecta con algo fundamental del modelo de datos que vimos en 01-04: Git no almacena renombrados. Un blob guarda contenido, y el nombre vive en el tree. Lo que Git hace es detectar renombrados a posteriori, comparando el contenido de los ficheros borrados y añadidos. Si el contenido es idéntico, la detección es infalible; si cambiaste mucho el fichero al moverlo, quizá no lo detecte y lo muestre como un borrado más un fichero nuevo.
Usos habituales
# Renombrar
git mv estilos.css estilos-principal.css
# Mover a un subdirectorio (debe existir)
mkdir css
git mv estilos.css css/estilos.css
# Mover varios ficheros a un directorio
git mv app.js utilidades.js src/
# Forzar la sobrescritura del destino
git mv -f borrador.md CAMBIOS.mdY el caso incómodo: cambiar solo mayúsculas y minúsculas en macOS o Windows, donde el sistema de ficheros no distingue entre Leeme.md y LEEME.md. Como Bruno trabaja en macOS, le pasará:
La solución es el paso intermedio:
O usar -f, que en las versiones recientes de Git resuelve este caso concreto.
- Borrar con
git rm (y el caso especial --cached)
git rm (y el caso especial --cached)Para eliminar un fichero del proyecto:
Hace dos cosas a la vez: borra el fichero del disco y prepara el borrado. Equivale a rm antiguo.js && git add antiguo.js.
Si Git detecta que el fichero tiene cambios sin confirmar, se niega:
error: the following file has local modifications:
app.js
(use --cached to keep the file, or -f to force removal)Es una protección deliberada: te avisa de que estás a punto de perder trabajo. Si estás seguro, -f.
git rm --cached: el caso realmente importante
Esta variante quita el fichero del control de versiones pero lo deja en el disco. Es la respuesta a un problema muy frecuente: un fichero que nunca debió versionarse y que ya está rastreado.
Recuerda del apartado 6 de la lección anterior: .gitignore solo actúa sobre ficheros sin seguimiento. Si config.local.json ya está en el historial, añadirlo al .gitignore no hace nada. La secuencia correcta es:
# 2. Asegurarse de que no vuelva
echo "config.local.json" >> .gitignore
# 3. Confirmar ambas cosas
git add .gitignore
git commit -m "Dejar de versionar la configuración local del entorno"ls config.local.json
# → config.local.json ← sigue ahí, intacto
git status -s
# → (vacío: ahora está ignorado)Para un directorio entero hace falta -r (recursivo):
Un truco muy útil cuando has arreglado el .gitignore y quieres que se aplique a todo lo ya rastreado:
git rm -r --cached . # deja de rastrear todo (sin borrar nada del disco)
git add . # vuelve a añadir, ahora respetando el .gitignore
git status -s # revisa qué se ha quedado fuera
git commit -m "Aplicar el .gitignore a los ficheros ya versionados"Advertencia importante.
git rm --cacheddeja de rastrear un fichero desde ahora, pero no lo borra del historial. Todas las confirmaciones anteriores lo siguen conteniendo, y cualquiera con acceso al repositorio puede recuperarlo. Si el fichero contenía credenciales, esto no las protege: hay que rotarlas y, si es imprescindible, reescribir el historial. Lo trataremos en Mejores Prácticas de Seguridad.
Resumen de borrado
| Comando | Fichero en el disco | Rastreado por Git |
|---|---|---|
rm f (del sistema) |
Borrado | Sigue rastreado; borrado sin preparar |
git rm f |
Borrado | Borrado, preparado |
git rm --cached f |
Se conserva | Deja de rastrearse |
git rm -f f |
Borrado aunque tenga cambios | Borrado, preparado |
git rm -r --cached dir/ |
Se conserva | Todo el directorio deja de rastrearse |
git commit: todas las formas de confirmar
git commit: todas las formas de confirmarCon el área de preparación lista, toca registrar. La forma que ya conoces:
Y estas son las variantes que merece la pena conocer.
Sin -m: el editor
Git abre tu editor con una plantilla:
# Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # # On branch main # Changes to be committed: # modified: app.js # modified: estilos.css #
Escribes arriba, guardas y cierras. Si guardas sin escribir nada, el commit se cancela. Las líneas que empiezan por # se descartan.
Esta forma tiene dos ventajas sobre -m: te muestra qué vas a confirmar mientras escribes el mensaje (una última oportunidad de detectar un error), y facilita escribir mensajes de varias líneas.
Mensajes multilínea
Un mensaje de confirmación tiene dos partes: una primera línea de resumen y, opcionalmente, un cuerpo separado por una línea en blanco.
Desde el editor es natural. Desde la línea de órdenes, hay dos formas:
# Con varios -m: cada uno es un párrafo, separado por línea en blanco
git commit -m "Marcar tareas como completadas al hacer clic" \
-m "El estado se guarda en el array en memoria; al recargar la página se pierde. La persistencia se abordará cuando exista el almacenamiento local."# Con un salto de línea real (Bash y Zsh)
git commit -m "Marcar tareas como completadas al hacer clic
El estado se guarda en el array en memoria; al recargar la
página se pierde."El resultado es idéntico. Muchos comandos —git log --oneline, la lista de confirmaciones de las plataformas web— muestran solo la primera línea, así que esa línea debe funcionar por sí sola.
-a: preparar y confirmar de un golpe
git commit -a -m "Ajustar el color del texto secundario"
git commit -am "Ajustar el color del texto secundario" # abreviadoPrepara automáticamente todos los ficheros rastreados que estén modificados o borrados, y confirma.
Su riesgo es que se salta el área de preparación, es decir, se salta la decisión de qué entra. Con -a confirmas todo lo que hayas tocado, incluidos:
- El
console.logde depuración que dejaste enapp.js. - El ajuste de tres líneas en un fichero que no tiene nada que ver con lo que estás confirmando.
- El cambio que ibas a revisar mejor antes de registrarlo.
Además, no incluye ficheros sin seguimiento, lo que produce el error contrario: crees haber confirmado todo y el fichero nuevo se queda fuera, el proyecto no compila en la máquina de tu compañero y nadie entiende por qué.
git add + git commit |
git commit -a |
|
|---|---|---|
| Ficheros nuevos | Se incluyen si los añades | Nunca se incluyen |
| Ficheros modificados | Los que elijas | Todos |
| Ficheros borrados | Los que elijas | Todos |
| Control sobre el contenido | Total | Ninguno |
| Riesgo de commit revuelto | Bajo | Alto |
Úsalo cuando estés seguro de que todo lo modificado pertenece a la misma confirmación, y aun así mira git status antes. Como práctica por defecto, no lo recomiendo.
Otras opciones útiles
# Ver el diff de lo que vas a confirmar mientras escribes el mensaje
git commit -v
# Confirmar aunque no haya nada preparado (útil en scripts y para marcar hitos)
git commit --allow-empty -m "Disparar el despliegue"
# Fijar una fecha concreta de autoría
git commit --date="2026-07-15 10:00:00" -m "..."
# Confirmar en nombre de otra persona (trabajo en pareja)
git commit --author="Bruno Salas <[email protected]>" -m "..."
# Firmar criptográficamente la confirmación
git commit -S -m "..."git commit -v merece un comentario aparte: añade al pie de la plantilla el diff completo de lo preparado. Es la mejor forma de revisar tu propio trabajo justo antes de registrarlo, y muchos desarrolladores lo activan siempre:
--amend: corregir la última confirmación
--amend: corregir la última confirmaciónAna confirma y, un segundo después, ve la errata en el mensaje:
Solución:
git commit --amend -m "Marcar tareas como completadas al hacer clic"
git log --oneline -1
# → 5c2e8b4 Marcar tareas como completadas al hacer clicCorregido. Fíjate en que el hash ha cambiado: era 9d1e4b7 y ahora es 5c2e8b4.
--amend sirve para tres cosas:
# 1. Cambiar solo el mensaje
git commit --amend -m "Mensaje corregido"
# 2. Añadir un fichero olvidado al último commit
git add fichero-que-olvide.js
git commit --amend --no-edit # --no-edit conserva el mensaje actual
# 3. Corregir la autoría
git commit --amend --author="Ana Ferrer <[email protected]>" --no-editEl segundo caso es el más útil en el día a día: acabas de confirmar y descubres que faltaba un fichero. En lugar de crear una confirmación "Añadir el fichero que faltaba", lo incorporas al commit anterior y el historial queda limpio.
--amend reescribe el historial
Esto es lo que hay que entender bien, y conecta directamente con El Modelo de Datos de Git.
--amend no modifica la confirmación existente. No puede: los objetos de Git son inmutables, y su identificador es el hash de su contenido. Lo que hace es:
- Crear un commit nuevo con el mismo padre que el anterior y el contenido/mensaje corregidos.
- Mover la rama actual a ese commit nuevo.
- Dejar el commit antiguo huérfano, sin nada que apunte a él.
graph LR
subgraph "Antes"
A1["c5d9b1e"] --> B1["2f6a3c8"] --> C1["9d1e4b7<br/>(mensaje con errata)"]
M1["main"] -.-> C1
end
subgraph "Después de --amend"
A2["c5d9b1e"] --> B2["2f6a3c8"] --> C2["9d1e4b7<br/>(huérfano)"]
B2 --> D2["5c2e8b4<br/>(mensaje corregido)"]
M2["main"] -.-> D2
end
De ahí la regla de oro:
Nunca hagas
--amendsobre una confirmación que ya has compartido.
Si la confirmación 9d1e4b7 ya estaba en el servidor y Bruno la había descargado, al reescribirla tu historial y el suyo divergen: él tiene un commit que tú ya no tienes, y tu commit nuevo le resulta desconocido. Arreglarlo requiere trabajo y coordinación. Mientras la confirmación esté solo en tu máquina, --amend es seguro y muy recomendable.
La regla general —reescribe libremente lo local, nunca lo compartido— vale también para el rebase (módulo 5) y para todas las técnicas de limpieza de historial (08-02).
- El commit atómico
Todo lo anterior existe para una cosa: poder crear confirmaciones atómicas.
Un commit atómico es aquel que contiene un cambio completo y solo uno. Dos condiciones simultáneas:
- Completo: incluye todo lo necesario para que ese cambio funcione. Si añades una función y su estilo, ambos van juntos; una confirmación que deja el proyecto roto no es atómica.
- Único: no incluye nada que no pertenezca a ese cambio. Ni el arreglo de ortografía de paso, ni el
console.logde depuración.
La regla mínima
Aquí va la única regla que necesitas por ahora, y que además funciona como test de atomicidad:
Si al describir tu confirmación necesitas la palabra "y", probablemente deberían ser dos confirmaciones.
Aplicado al caso de Ana: "Marcar tareas como completadas y corregir el mensaje de consola y ajustar el color". Tres "y" implícitos, tres confirmaciones. Por eso usó git add -p.
Por qué merece la pena
| Con commits atómicos | Con commits revueltos |
|---|---|
git log cuenta la historia real del proyecto |
El historial es una lista de "cambios varios" |
| Deshacer un cambio concreto es trivial | Deshacer arrastra cosas que no querías |
git bisect encuentra el commit culpable exacto |
El commit culpable contiene diez cambios |
| Las revisiones de código son rápidas | Nadie quiere revisar 40 ficheros mezclados |
git blame explica de verdad cada línea |
git blame apunta a "cambios del viernes" |
Los beneficios se cobran meses después, cuando algo falla y hay que averiguar por qué. Herramientas como Git Bisect y Git Blame dependen por completo de esta disciplina.
Sobre los mensajes
Un commit atómico necesita un mensaje que lo describa. Por ahora, tres normas suficientes:
- La primera línea, corta (unos 50 caracteres) y descriptiva por sí sola.
- En imperativo o infinitivo, describiendo lo que hace la confirmación: "Añadir el contador de tareas", no "Añadido el contador" ni "cambios".
- El qué en la primera línea, el porqué en el cuerpo si hace falta explicarlo.
Las convenciones completas —Conventional Commits, longitudes, cuerpo, pies, referencias a incidencias— se tratan en Escribiendo Buenos Mensajes de Confirmación. Con estas tres normas ya escribirás mejores mensajes que la media.
Errores Comunes y Consejos
- Usar
git add .desde un subdirectorio creyendo que añade todo. Solo añade desde ahí hacia abajo. Si quieres todo el repositorio, esgit add -A. - Esperar que
-uincluya ficheros nuevos. No lo hace, por diseño. Es su ventaja, no su defecto. - Confiar en
git commit -a. Confirma todo lo modificado sin filtro y deja fuera lo nuevo. Es la receta de los commits revueltos y de los ficheros olvidados. git restore <fichero>a la ligera. Destruye tus cambios sin copia de seguridad. Es de los poquísimos comandos de Git sin red. Si dudas,git stash.- Confundir
git restore --stagedcongit restore. El primero quita del área de preparación y no toca el disco; el segundo sobrescribe el disco. La diferencia es un cambio deshecho frente a un cambio perdido. - Creer que
git rm --cachedborra el fichero del historial. No lo borra: sigue en todas las confirmaciones anteriores. Para secretos, esto no basta. - Hacer
--amendsobre una confirmación ya compartida. Reescribe el historial y provoca divergencia con tus compañeros. Solo sobre lo que aún está en tu máquina. - Confirmar el trabajo de tres días en un solo commit. Nadie puede revisarlo, deshacerlo ni entenderlo. Confirma pronto y a menudo.
- Consejo: activa
git config --global commit.verbose true. Ver el diff mientras escribes el mensaje detecta muchísimos errores antes de que entren en el historial. - Consejo: si
git add -pte resulta lento al principio, insiste una semana. Es el hábito que más mejora la calidad de un historial, y acaba siendo automático. - Consejo: cuando dudes de qué vas a confirmar,
git diff --stagedte lo enseña exactamente. Es el tema de la siguiente lección.
Ejercicios
Ejercicio 1: Dominar -A, -u y .
Monta este escenario:
mkdir -p ~/practica/add-test/componentes && cd ~/practica/add-test
git init
echo "inicial" > raiz.txt
echo "inicial" > componentes/boton.js
git add -A && git commit -m "Estado inicial"
echo "cambio" >> raiz.txt
echo "cambio" >> componentes/boton.js
echo "nuevo" > raiz-nuevo.txt
echo "nuevo" > componentes/menu.js
rm raiz.txtSitúate dentro de componentes/ y, ejecutando git reset entre pruebas para volver al punto de partida, determina qué prepara exactamente cada uno de estos comandos. Anota la salida de git status -s en cada caso y explica el porqué:
git add .git add -Agit add -ugit add ..
Ejercicio 2: Separar dos cambios de un mismo fichero
En el repositorio de gestor-tareas (o en uno de prueba con un app.js similar), realiza estos dos cambios a la vez en app.js:
- Cambio A (funcionalidad): añade una función
borrarTarea(id)que elimine una tarea del array y repinte la lista. - Cambio B (mantenimiento): corrige un comentario mal escrito en la cabecera del fichero.
Después:
- Usa
git add -ppara preparar solo el cambio A. - Demuestra con
git status -sque el fichero está en estadoMM. - Confirma el cambio A con el mensaje "Añadir borrado de tareas al listado".
- Confirma el cambio B en una segunda confirmación.
- Comprueba con
git log --onelineque hay dos confirmaciones y congit show --statque cada una toca lo que debe.
Ejercicio 3: Rescatar un repositorio mal preparado
Un compañero te pasa un repositorio en este estado, justo después de haber confirmado:
.env | 3 + app.js | 24 ++++++++--- estilos.css | 6 ++-- node_modules/lib/a.js | 99 +++++++++++++++++ README.md | 2 +- 5 files changed, 128 insertions(+), 6 deletions(-)
La confirmación no se ha compartido con nadie todavía. Resuelve:
- ¿Cuáles son los tres problemas de esta confirmación?
- Escribe la secuencia de comandos que deja de versionar
.envynode_modules/, los añade al.gitignorey conserva ambos ficheros en el disco. - Corrige el mensaje de la confirmación por uno descriptivo, sin crear una confirmación adicional.
- ¿Qué habría cambiado en tu respuesta si la confirmación ya se hubiera compartido? ¿Y qué medida adicional habría que tomar por el hecho de que
.envhaya estado versionado?
Soluciones
Solución al Ejercicio 1
Estado de partida (visto desde la raíz):
1. git add . desde componentes/:
Prepara todo (nuevos, modificados y borrados) pero solo desde el directorio actual hacia abajo. Los cambios de la raíz quedan fuera.
2. git add -A:
Prepara todo el repositorio, ignorando por completo dónde estás. Las cuatro entradas tienen la primera columna ocupada.
3. git add -u:
Actúa sobre todo el repositorio, pero solo sobre ficheros rastreados: la modificación de boton.js y el borrado de raiz.txt. Los dos ficheros nuevos siguen sin seguimiento.
4. git add ..:
Mismo resultado que -A, pero por otra razón: .. es la raíz del repositorio, así que "desde ahí hacia abajo" es todo. Confirma que la diferencia entre . y -A es exclusivamente el punto de partida del recorrido.
Resumen:
Comando (desde componentes/) |
boton.js |
menu.js |
raiz.txt |
raiz-nuevo.txt |
|---|---|---|---|---|
git add . |
Sí | Sí | No | No |
git add -A |
Sí | Sí | Sí | Sí |
git add -u |
Sí | No | Sí | No |
git add .. |
Sí | Sí | Sí | Sí |
Solución al Ejercicio 2
Los dos cambios en app.js. Cabecera (cambio B):
Y la función nueva (cambio A):
function borrarTarea(id) {
const indice = tareas.findIndex(function (t) { return t.id === id; });
if (indice !== -1) {
tareas.splice(indice, 1);
pintarLista();
actualizarContador();
}
}1. Preparar solo el cambio A:
El primer fragmento que muestra Git es el de la cabecera (está más arriba en el fichero):
@@ -1,3 +1,3 @@
-// gestor-tareas — logica prinicpal
+// gestor-tareas — lógica principal
const tareas = [];
(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? nRespondemos n: no pertenece a esta confirmación. El segundo es la función:
@@ -30,6 +30,15 @@ function pintarLista() {
+function borrarTarea(id) {
+ const indice = tareas.findIndex(function (t) { return t.id === id; });
...
(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]? y2. Comprobar el estado MM:
Primera columna M: hay una versión preparada (con la función nueva, sin la corrección del comentario). Segunda columna M: el disco difiere de ella.
Se puede verificar con más precisión:
Nueve líneas preparadas (la función) y una línea pendiente (el comentario). Exactamente la separación buscada.
3 y 4. Las dos confirmaciones:
git commit -m "Añadir borrado de tareas al listado"
# → [main 4e7f2a9] Añadir borrado de tareas al listado
# → 1 file changed, 9 insertions(+)
git add app.js
git commit -m "Corregir una errata en el comentario de cabecera"
# → [main 8a1d5c3] Corregir una errata en el comentario de cabecera
# → 1 file changed, 1 insertion(+), 1 deletion(-)5. Verificación:
8a1d5c3 (HEAD -> main) Corregir una errata en el comentario de cabecera 4e7f2a9 Añadir borrado de tareas al listado
git show --stat 4e7f2a9 | tail -2
# → app.js | 9 +++++++++
# → 1 file changed, 9 insertions(+)
git show --stat 8a1d5c3 | tail -2
# → app.js | 2 +-
# → 1 file changed, 1 insertion(+), 1 deletion(-)Dos confirmaciones atómicas sobre el mismo fichero. Sin git add -p esto solo se conseguiría deshaciendo un cambio a mano, confirmando y volviéndolo a escribir.
Solución al Ejercicio 3
1. Los tres problemas:
.envestá versionado. Contiene credenciales; ahora están en el historial y las verá cualquiera con acceso al repositorio.node_modules/está versionado. Son dependencias reinstalables: hinchan el repositorio, ensucian los diffs y no aportan nada.- El mensaje "arreglos varios" no dice nada. Y, de fondo, la confirmación no es atómica: mezcla lógica (
app.js), estilos (estilos.css) y documentación (README.md) con ficheros que no debían estar.
2. Dejar de versionar .env y node_modules/:
--cached es la clave: los ficheros siguen en el disco, solo dejan de rastrearse.
Comprobación de que los ficheros siguen ahí:
3. Corregir el mensaje sin crear una confirmación nueva.
Como los cambios del punto 2 están preparados y la confirmación no se ha compartido, se pueden incorporar a la misma confirmación con --amend, arreglando de paso el mensaje:
[main 3b9e7d1] Añadir el borrado de tareas y ajustar los estilos del listado 4 files changed, 29 insertions(+), 6 deletions(-)
.gitignore | 2 ++ app.js | 24 ++++++++--- estilos.css | 6 ++-- README.md | 2 +- 4 files changed, 29 insertions(+), 6 deletions(-)
.env y node_modules/ han desaparecido de la confirmación, y el mensaje ya describe algo. El hash ha cambiado (7f3c9a2 → 3b9e7d1) porque, como sabemos, --amend crea una confirmación nueva.
Nota crítica: este
--amendfunciona limpiamente porque7f3c9a2era la única confirmación que contenía.env. Si el fichero llevara veinte confirmaciones en el historial,--amendsolo lo sacaría de la última y seguiría estando en las diecinueve anteriores.
Sobre el mensaje: sigue teniendo una "y", así que lo verdaderamente correcto habría sido separar la confirmación en dos (funcionalidad y estilos). Con git reset y un par de git add -p se podría hacer, pero eso ya entra en el terreno de Manteniendo un Historial Limpio.
4. Si la confirmación ya se hubiera compartido.
Cambiarían dos cosas:
- Nada de
--amend. Reescribir una confirmación que otros ya tienen provoca divergencia entre historiales. La solución correcta sería una confirmación nueva encima:
git rm --cached .env
git rm -r --cached node_modules/
git add .gitignore
git commit -m "Dejar de versionar credenciales y dependencias"El mensaje malo del commit anterior se queda ahí. Es el precio de haber publicado: el historial compartido es inmutable en la práctica.
- Y, sobre todo, hay que rotar las credenciales. Este es el punto que no se puede pasar por alto:
.envestuvo en el historial y, si ese historial se compartió, las claves se consideran comprometidas. Sacarlo del seguimiento no las protege; cualquiera que haya clonado el repositorio las tiene en su disco, ygit show 7f3c9a2:.envlas muestra. La única respuesta válida es invalidar esas credenciales y generar unas nuevas. Limpiar el historial (congit filter-repoo similar) es un paso complementario, nunca un sustituto. Lo veremos en Mejores Prácticas de Seguridad.
Conclusión
Esta lección te ha dado el control fino sobre qué entra en el historial y cómo. Recapitulando:
git addsincroniza rutas con el área de preparación, y eso incluye ficheros nuevos, modificados y borrados.-A,-uy.se diferencian en dos ejes:-ues el único que excluye ficheros nuevos, y.es el único limitado al directorio actual.-Aes "todo, en todas partes".git add -pprepara por fragmentos y es la herramienta que permite separar dos cambios mezclados en un mismo fichero. Es el hábito que más mejora un historial.git restore --stagedquita del área de preparación sin tocar el disco;git restoresobrescribe el disco y destruye tus cambios sin red de seguridad. Sus equivalentes antiguos songit reset HEADygit checkout --.git mvygit rmhacen la operación del sistema de ficheros y la preparan de una vez.git rm --cacheddeja de rastrear conservando el fichero, y es la pieza que falta cuando un.gitignore"no funciona".git commitadmite mensaje en línea o en el editor, mensajes multilínea y la opción-a, que es cómoda y arriesgada: confirma todo lo modificado y deja fuera lo nuevo.--amendcorrige la última confirmación creando una nueva y descartando la anterior. Es seguro y muy útil mientras esa confirmación no se haya compartido.- El commit atómico —un cambio completo y solo uno— es el objetivo de todo lo anterior. La regla mínima: si necesitas "y" para describirlo, sepáralo.
Ana ya sabe elegir con precisión qué confirma. Pero hay una pieza que hemos usado sin explicar: en el modo -p, Git le mostraba los cambios en un formato con líneas + y -, cabeceras @@ y fragmentos. Ese formato aparece por todas partes en Git y saber leerlo es imprescindible.
En la siguiente lección, Inspeccionando Cambios con git diff, aprenderemos a ver exactamente qué ha cambiado antes de confirmar: la diferencia entre git diff, git diff --staged y git diff HEAD según qué zonas comparan, cómo leer el formato unified diff línea a línea, cómo comparar dos confirmaciones concretas o un fichero suelto, y las opciones que hacen la salida legible en los casos difíciles.
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
