Commits atómicos
Un commit atómico contiene exactamente un cambio lógico. La recompensa no es el orden por el orden mismo — son tres capacidades concretas que el equipo pierde cuando los commits están mezclados:
- Revisión: un revisor puede mantener una idea en la cabeza y juzgarla.
- Revertir: un cambio malo se puede deshacer sin llevarse cambios buenos con él.
- Bisect:
git bisectpuede identificar el commit que introdujo un error, lo cual solo funciona si cada commit es un cambio y cada commit compila.
Esa última cláusula es la que más a menudo se pasa por alto. Atómico no significa "lo más pequeño posible" — significa "un cambio completo". Un commit que separa una función del lugar que la llama es más pequeño pero no atómico, porque deja el árbol roto.
El flujo de trabajo
1. Inspeccionar antes de agregar nada al stage
Entiende todo el árbol antes de decidir cómo dividirlo:
git status --porcelain # cada ruta, incluyendo no rastreadas
git diff # cambios no agregados al stage
git diff --cached # cualquier cosa ya en stage
git log --oneline -15 # dónde está HEAD, y el estilo local
Resiste git add -A y git commit -a como primer movimiento. Así es como un árbol mezclado se convierte en un commit mezclado, y una vez commiteado, dividirlo es estrictamente más trabajo que dividirlo antes. También verifica si hay cambios que no deberían commitearse en absoluto — salida de compilación, archivos .env, prints de depuración, una configuración de editor extraviada.
2. Agrupar cambios en unidades lógicas
La prueba más fiable es lingüística: redacta el asunto del commit, y si necesita un "y" para ser preciso, tienes más de un commit. "Corregir redirección de login y actualizar lodash" son dos. La pregunta del revisor es la segunda prueba: ¿tendrían que entender el cambio X para poder juzgar el cambio Y? Si no, los cambios son independientes y deben ir separados.
Separar:
| Mantener separado | Por qué |
|---|---|
| Formato / espacios en blanco vs. lógica | Un formateo entierra las tres líneas reales en 400 líneas de ruido. Esta es la división de mayor valor. |
| Renombres vs. cambios de comportamiento | Misma razón — un renombre toca todo y oculta la única línea que importa. |
| Actualizaciones de dependencias vs. código | Cada una necesita su propia historia de revertido. |
| Correcciones de errores no relacionadas | Revertidos independientes, bisects independientes. |
| Refactorización vs. la característica que la usa | Permite que un revisor confirme que la refactorización preserva el comportamiento. |
Mantener juntos:
| Mantener en un solo commit | Por qué |
|---|---|
| Un cambio y sus pruebas | Son una unidad lógica; pruebas para código no enviado, o código sin pruebas, son cada uno medio cambio. |
| Un renombre y todos sus lugares de llamada | Dividir deja el árbol roto — lo opuesto a atómico. |
| Un cambio de firma y sus llamadores | Igual. |
| Un cambio y la documentación/changelog que lo describe | Describen la misma decisión y deberían revertirse juntos. |
3. Ordenar los commits para que cada uno compile
El orden importa porque cada commit debe dejar el árbol funcionando. Pon las bases antes de las cosas que las usan: agrega la función auxiliar antes del código que la llama, haz la refactorización antes de la característica construida sobre ella, sube el esquema antes de la consulta. Si dos grupos son mutuamente dependientes, es una señal de que en realidad son un solo commit.
4. Agregar cada grupo al stage
Usa la técnica menos invasiva que funcione. Escala solo cuando la más simple no pueda expresar la división:
Nivel 1 - agregar archivos completos. Esto cubre la gran mayoría de las divisiones reales, porque los cambios lógicos normalmente viven en archivos diferentes:
git add src/auth.py tests/test_auth.py
git commit -m "..."
Nivel 2 - apartar archivos completos cuando algo en el árbol podría commitearse accidentalmente:
git stash push -- requirements.txt # aparcarlo
# ... commitear el resto ...
git stash pop # traerlo de vuelta
Nivel 3 - dividir cambios dentro de un solo archivo. Solo necesario cuando un archivo realmente contiene dos cambios lógicos. La mecánica es delicada y fácil de errar sutilmente, así que lee references/splitting-one-file.md antes de intentarlo.
git add -pno funciona en entornos no interactivos, y falla silenciosamente. Muestra el primer hunk, llega a EOF en stdin, no agrega nada, y sale con 0. Un código de salida exitoso aquí significa que no pasó nada. Lo mismo aplica paragit add -i,git commit -pygit checkout -p. Como el fallo parece un éxito, el daño suele ser un commit vacío o que le falta el cambio previsto — así que usa las técnicas del archivo de referencia en su lugar, y verifica el índice congit diff --cachedantes de commitear.
5. Escribir cada mensaje con la voz del repositorio
Iguala el historial circundante en lugar de importar una convención. Un prefijo feat: metido en un registro de asuntos en prosa simple es ruido, y hace que el historial parezca dos proyectos. Lee primero el estilo local:
git log --no-merges --format='%s' -n 30 # estilo de asuntos
git log --no-merges -n 10 --format='%n---%n%s%n%b' # si se usan cuerpos
Busca: un prefijo tipo: o tipo(ámbito): (Conventional Commits), un prefijo de ticket como [PROY-123], capitalización, modo imperativo vs. pasado ("Agregar" vs. "Agregado"), longitud típica del asunto, y si los cuerpos son normales o los asuntos están solos. Adopta lo que encuentres. Conventional Commits es un buen valor por defecto solo cuando el registro ya lo usa, o el repositorio no tiene historial y el usuario no tiene preferencia.
Sea cual sea el estilo local, el asunto debe decir qué cambió y el cuerpo — cuando el historial usa cuerpos — debe decir por qué, ya que el diff ya muestra el qué. Si el repositorio tiene un CONTRIBUTING.md o un hook commit-msg, estos anulan la inferencia del registro.
Igualar una convención nunca significa inventar los datos que lleva. Si el estilo local incluye una referencia a ticket o incidencia — [PROY-123], Fixes #456 — y no se te ha dado el identificador real, no adivines uno, y no continúes la numeración que ves en el registro. Un [PROY-124] que parezca plausible es peor que ningún prefijo: es permanente, parece trazabilidad real, y probablemente apunta a un ticket no relacionado de otra persona. Omite la referencia, iguala el resto del estilo, y di en tu respuesta que falta el ID para que el usuario lo proporcione — el commit aún no está subido y es barato de enmendar. Pide el número si el usuario está ahí para preguntar.
6. Verificar antes de devolver el control
git log --oneline -n <count> # la forma de lo que construiste
git status --porcelain # qué, si algo, queda
Confirma que cada commit contiene solo su propio cambio (git show --stat <sha>), y que no se perdió nada. Si quedan cambios sin commitear por diseño, dilo explícitamente — un árbol que el usuario creía limpio pero no lo está es el peor resultado aquí, porque lo descubrirá más tarde y no sabrá si fue deliberado.
Guardarraíles
- Dar forma a cómo commitear, no a si hacerlo. Si el usuario no ha pedido un commit, este skill no hace apropiado crear uno, y nunca implica hacer push.
- Nunca descartar trabajo para facilitar una división.
git checkout -- <archivo>,git reset --hardygit stash dropdestruyen trabajo no commiteado que ningún commit contiene aún — es irrecuperable. Si una técnica necesita uno de estos, primero respalda el contenido y confirma que la copia de seguridad existe antes de ejecutarlo. - No reescribir historia publicada para ordenarla. Dividir commits ya subidos significa un force-push que reescribe historia que otros pueden haber descargado; pregunta primero.
- No sobredividir para parecer minucioso. Tres commits que cada uno rompe la compilación son peores que uno que funciona. En caso de duda, prefiere la agrupación que mantenga cada commit funcional.
- Si el árbol resulta tener un solo cambio lógico, commitéalo como uno solo. No todo árbol necesita división, y decirlo es un resultado perfectamente válido.