El día que git rechazó mi push
Un día, git simplemente dejó de permitirme guardar mi trabajo. Mi push falló con un error de servidor que nunca había visto antes — no un conflicto de fusión, ni un problema de permisos, solo un rechazo rotundo desde el otro extremo. Había estado haciendo commits felizmente durante semanas, y ahora el comando más rutinario de mi día no se completaba.
La causa no era un fallo en git. Era que había pasado esas semanas llenando silenciosamente mi repositorio con miles de cosas que no tenían por qué estar ahí, y al final se había vuelto demasiado pesado para levantarlo.
El push que no salía
Al principio supuse que era una casualidad — un tropiezo de la red, un servidor pasando un mal momento. Pero seguía ocurriendo, de forma fiable, cada vez. El volumen acumulado del repositorio parecía ser la causa: el push seguía fallando de la misma manera cada vez, y ninguna cantidad de reintentos iba a arreglar un problema que era estructural en lugar de transitorio.
Cuando por fin miré lo que el repositorio contenía realmente, la respuesta fue vergonzosa. El código — aquello para lo que existe un repositorio — era una pequeña minoría de él. La abrumadora mayoría eran datos: miles de archivos generados, cachés y artefactos compilados, cada uno fielmente rastreado, versionado y arrastrado en cada operación.
Cómo un repo se convierte silenciosamente en un almacén
Nadie decide poner gigabytes de datos bajo control de versiones. Sucede un paso aparentemente razonable cada vez. Generas unos cuantos archivos y aterrizan en la carpeta de tu proyecto. Ejecutas un comando add, quizá con un poco de descuido, y se van con él. Funciona. Nada se queja. Así que lo haces de nuevo, y otra vez, y el repositorio acumula datos del mismo modo que un cajón acumula desorden — de forma invisible, hasta el día en que no cierra.
Para cuando git rechazó mi push, yo estaba rastreando miles de archivos de datos. Nunca había tomado la decisión consciente de ponerlos bajo control de versiones. Simplemente nunca había tomado la decisión de dejarlos fuera.
Los datos y el código quieren cosas opuestas
La razón por la que mezclarlos duele es que se versionan por motivos completamente distintos. El código es pequeño, significativo, y cambia de maneras que quieres ver línea por línea — git está construido precisamente para eso. Los datos son grandes, opacos, y tienden a cambiar por completo; un diff línea por línea de ellos no te dice casi nada, y almacenar para siempre cada versión pasada es en su mayoría desperdicio. Git es maravilloso en el primer trabajo; su historial estándar a menudo encaja mal con el segundo, al menos una vez que los datos se vuelven grandes o cambian constantemente.
Júntalos y obtienes lo peor de ambos. Tu historial se hincha con cambios enormes que nunca podrás leer de forma significativa, cada clon y cada push arrastra todo eso de un lado a otro, y la cosa pequeña y preciosa — el código fuente real — acaba enterrada en un repositorio que es en su mayoría lastre.
La solución: traza el límite que te saltaste
La reparación fue conceptualmente trivial y ligeramente tediosa: decirle a git, de forma explícita, qué no rastrear. Los archivos generados, las cachés, los artefactos compilados — todo ello quedó excluido, y el repositorio volvió a ser lo que siempre debería haber sido: un hogar para las cosas que había escrito intencionadamente — código, configuración, documentación, pruebas — en lugar de salida generada a granel. Los datos se trasladaron a donde pertenecen las cosas grandes y regenerables — producidas bajo demanda, o almacenadas en algún lugar construido para archivos grandes, pero nunca más versionadas línea por línea como si fueran código fuente.
El push pasó al primer intento. La solución no fue ingeniosa. Era simplemente un límite que debería haber trazado el primer día y que nunca había trazado.
El hábito por debajo: fuente frente a derivado
La lección duradera no trata realmente de git. Es una pregunta que ahora me hago sobre cada archivo que produce un proyecto: ¿escribí yo esto, o lo generó mi código? ¿Es fuente — la cosa irreemplazable que escribió un humano — o es derivado, una salida que puede recrearse ejecutando la fuente de nuevo?
La fuente generalmente pertenece al control de versiones. Los archivos derivados grandes o voluminosos normalmente no — solo deberían registrarse cuando la reproducibilidad o el flujo de trabajo realmente lo exija, e idealmente seguir siendo reproducibles a partir de la fuente en cualquier momento. Muchos de los repositorios que se pudren hasta convertirse en un desastre inmanejable lo hacen al perder de vista esa distinción — al tratar algo derivado como si fuera precioso, y dejar que las cosas que siempre pueden regenerarse se amontonen junto a las cosas que nunca pueden reemplazarse.
Git no se rompió aquel día. Simplemente me hizo notar al fin que había dejado de mantener la fuente y la salida en sus lugares separados, y que había estado pidiéndole a una herramienta que hiciera un trabajo para el que nunca fue construida.
— Sin señales, sin rendimientos, esto no es asesoramiento de inversión.