Contribuir: el flujo del proyecto y la anatomía de un parche
Cómo se transforma el propio editor: el ciclo de fork, rama, commit convencional, integración continua y revisión; por dónde empezar sin escribir C; qué hace que un parche se acepte o se quede parado; y la pregunta previa que decide si algo pertenece al núcleo o a un plugin.
Queda una última capa del modelo que hasta ahora era opaca: el proceso por el que el editor cambia. Es una capa real, con reglas, con incentivos y con costes, y entenderla completa el arco de veintiséis niveles, porque cierra el círculo entre lo que el editor puede hacer y quién decide lo que podrá hacer mañana. Contribuir no es un acto de devoción ni una línea de currículum: es la forma más directa de que un comportamiento que necesitas exista, y la única manera de que tu conocimiento del sistema se someta a la revisión de gente que lo conoce mejor. Este capítulo describe el flujo real, sin romanticismo, incluida la parte que nadie cuenta: la mayoría de los parches rechazados no falla por el código sino por no haber contestado antes una pregunta.
- Recorrer el ciclo completo de una contribución desde la bifurcación hasta la integración y su retroporte.
- Escribir mensajes de commit conformes a la convención del proyecto y entender por qué son obligatorios.
- Identificar vías de entrada que no exigen escribir C: documentación, pruebas, triaje y portes de parches.
- Evaluar si un cambio pertenece al núcleo o a un plugin, y defender esa evaluación con argumentos de coste.
El flujo de trabajo
El ciclo es el habitual en proyectos grandes de código abierto y su forma importa menos que sus puntos de fricción, así que conviene recorrerlo señalando dónde se atasca la gente.
Bifurcas el repositorio, creas una rama descriptiva y trabajas sobre la rama principal de desarrollo, no sobre una de publicación. Compilas con las comprobaciones activadas y ejecutas las pruebas afectadas en local, porque descubrir un fallo en la integración continua cuesta media hora de espera y descubrirlo en tu máquina cuesta treinta segundos. Escribes el cambio en el menor número de commits que tengan sentido por separado: uno por idea, cada uno compilando y pasando pruebas por sí solo, y ninguno mezclando un arreglo con un reformateo.
El mensaje de commit sigue una convención estricta con un tipo, un ámbito y un asunto en imperativo. No es burocracia: de esos mensajes se generan las notas de publicación, se decide qué se retroporta a las ramas estables y se busca el origen de una regresión años después.
git switch -c fix-extmark-rango
# ... cambio minimo, mas una prueba que falla sin el ...
git commit -m "fix(api): validar el rango final en nvim_buf_set_extmark
Antes de este cambio, un end_row fuera del buffer provocaba un
recorte silencioso en lugar de un error, lo que ocultaba errores
en los plugins que calculan mal sus rangos.
Fixes #12345"
Hay un paso previo que no aparece en ningún diagrama y que decide más resultados que todos los demás juntos: discutir antes de escribir cuando el cambio es visible para el usuario. Una corrección evidente de un fallo puede llegar sin aviso, pero una funcionalidad nueva, un cambio de comportamiento por omisión o una adición a la API deberían empezar por una cuestión abierta que describa el problema y proponga una dirección. El motivo es puramente aritmético: escribir doscientas líneas cuesta un fin de semana y descubrir después que el proyecto prefiere otro enfoque cuesta ese fin de semana entero más la frustración de tirarlo. Media hora de conversación previa elimina esa apuesta.
Al abrir la propuesta, la integración continua ejecuta la batería completa en varios sistemas operativos y compiladores, con análisis estático y con las construcciones instrumentadas que detectan accesos indebidos a memoria. Después llega la revisión, hecha por quien mantiene esa área. Su ritmo es el de un proyecto sostenido por voluntarios: la respuesta puede tardar, y la reacción correcta ante el silencio es un recordatorio cortés, nunca un mensaje nuevo ni una propuesta duplicada. Cuando el cambio se acepta, se integra en la rama principal y, si corrige un fallo, viaja después a la rama de publicación vigente. Ese último paso explica por qué el proyecto separa con tanto cuidado los arreglos de las funcionalidades: solo lo que corrige un comportamiento defectuoso puede llegar a quienes usan una versión estable, porque quien la eligió lo hizo precisamente para que nada cambiara bajo sus pies.
flowchart LR A[Cuestion abierta o necesidad propia] --> B[Discusion previa si el cambio es visible] B --> C[Rama en tu bifurcacion] C --> D[Commits convencionales con prueba] D --> E[Integracion continua en varias plataformas] E -->|falla| D E -->|pasa| F[Revision del mantenedor del area] F -->|cambios pedidos| D F -->|aprobado| G[Integracion en la rama principal] G --> H[Retroporte a la rama estable si es un arreglo] style B fill:#f9e2af,color:#11111b style F fill:#cba6f7,color:#11111b style G fill:#a6e3a1,color:#11111b
Por dónde empezar
La creencia de que contribuir a un editor exige escribir C es falsa y frena a mucha gente competente. Hay al menos cinco vías de entrada, ordenadas aquí de menor a mayor exigencia técnica.
La documentación es la más infravalorada y la más útil. Buena parte de la referencia de la API en Lua se genera desde comentarios en el código, así que aclarar una descripción ambigua, documentar un valor de retorno o añadir un ejemplo es un cambio pequeño, verificable y con impacto inmediato sobre miles de personas. Si algo te costó entender la semana pasada, tienes delante una contribución identificada.
Hay una variante de la documentación que merece mención propia porque es didáctica además de útil: arreglar un ejemplo que no funciona. Los fragmentos de código de la ayuda envejecen, y comprobar que uno hace lo que promete exige entender la función entera. Cuando encuentres uno desfasado tendrás a la vez una contribución trivial de revisar y una lectura profunda de un módulo, que es la mejor relación entre esfuerzo y aprendizaje de toda esta lista.
El triaje es la segunda: reproducir una incidencia ajena en una versión reciente, reducirla a una configuración mínima y anotar la versión exacta donde empezó a fallar. Ese trabajo es el que convierte un informe inservible en un fallo arreglable, y quien lo hace suele acabar arreglándolo, porque la reducción ya contiene la mitad del diagnóstico.
Las pruebas son la tercera. Encontrar comportamiento sin cubrir y escribir el caso que lo fija no cambia nada visible y sin embargo protege ese comportamiento para siempre. Además es la mejor forma de aprender la suite antes de necesitarla para tu propio parche.
El porte de parches de Vim es la cuarta y es peculiar de este proyecto: Vim sigue vivo, sus correcciones se trasladan y existe un procedimiento asistido para localizarlas, adaptarlas y registrar su número en el mensaje. Es trabajo mecánico al principio y didáctico enseguida, porque te obliga a leer código del núcleo con una pregunta muy concreta.
El cambio en Lua del runtime es la quinta, y probablemente tu punto natural de entrada después de veinticinco niveles: runtime/lua/vim/ está escrito en el lenguaje que ya dominas, contra la API que ya conoces, y sus módulos tienen exactamente los problemas de diseño que has enfrentado escribiendo plugins.
Existe además una fuente de trabajo que casi nadie explota y que tienes delante todos los días: tu propia lista de fricciones. Cada vez que rodeas un comportamiento del editor con una función auxiliar en tu configuración, estás documentando un hueco. La mayoría de esos huecos son legítimamente tuyos y deben quedarse en tu configuración; alguno es general, y ese es material de contribución con una ventaja enorme sobre cualquier incidencia de la lista pública, porque tú ya sabes que el problema es real y ya has probado una solución durante meses.
# Preparar el entorno una sola vez y no volver a pensar en el
git clone https://github.com/neovim/neovim && cd neovim
make CMAKE_BUILD_TYPE=RelWithDebInfo
./build/bin/nvim --clean # tu construccion, sin tu configuracion
make functionaltest # la suite completa, para saber que partes de verde
El objetivo del primer parche no es aportar valor, es atravesar el proceso completo: construir, ejecutar pruebas, escribir el commit correcto, ver fallar la integración continua por algo tonto, arreglarlo, recibir una revisión y responderla. Elige algo aburrido a propósito. Cuando tengas algo importante que proponer, querrás que la mecánica ya no te distraiga.
Qué se espera de un parche
Un parche que se acepta rápido comparte cinco rasgos, y ninguno tiene que ver con la elegancia del código.
Conviene enunciarlos desde la perspectiva correcta, que no es la tuya sino la del revisor. Quien lee tu propuesta dispone de veinte minutos robados a otra cosa, no conoce tu contexto y responde con su nombre de las consecuencias si aprueba algo malo. Todo lo que sigue es, en el fondo, una lista de formas de abaratarle esos veinte minutos.
Es mínimo: cambia lo que hay que cambiar y ni una línea más. Un arreglo de dos líneas acompañado de treinta de reformateo obliga al revisor a separar el grano de la paja, y esa carga es la causa más común de que una propuesta se quede sin respuesta.
Está probado: incluye un caso que falla sin el cambio y pasa con él. Esa prueba es lo que impide que tu arreglo se pierda dentro de dos años, y es también el argumento más persuasivo que puedes ofrecer, porque demuestra el fallo en lugar de describirlo.
Está documentado si es visible: una función nueva de la API necesita su descripción y la anotación de la versión en la que aparece, porque los clientes externos deciden qué pueden usar consultando ese dato. Y como buena parte de la referencia se genera desde los comentarios del código, documentar no es un fichero aparte que se olvida, sino una línea junto a la implementación que envejece con ella.
Y a esos rasgos hay que sumarles uno que no es del parche sino de la propuesta: acompañarla de la forma de comprobarla. Un revisor que puede reproducir el fallo en treinta segundos con los pasos que le has dado decide en una sesión; uno que tiene que construir el escenario él mismo lo deja para más tarde, y más tarde es un lugar del que las propuestas rara vez vuelven.
Es compatible: la API pública es un contrato con interfaces gráficas, plugins y programas que sus autores ya no mantienen. Cambiar el significado de un argumento o el tipo de un retorno no es una mejora, es una rotura, y el proyecto prefiere una función nueva con mejor nombre a una función vieja con mejor comportamiento.
Y está justificado: el mensaje explica el problema antes que la solución. Un revisor que entiende el problema puede proponer un camino mejor; uno que solo ve el diff solo puede opinar sobre el estilo.
Queda una habilidad que no es técnica y que determina si tu segunda contribución existirá: recibir la revisión. Los comentarios de un mantenedor son casi siempre correctos y casi nunca amables, no por hostilidad sino por economía de tiempo, y leerlos como un juicio sobre ti en lugar de como una corrección del parche es la forma más común de abandonar. La respuesta profesional es preguntar cuando no entiendas, argumentar una vez cuando discrepes y aceptar la decisión cuando insistan, porque quien revisa carga con las consecuencias durante años y tú no. Conviene además asumir que el silencio no es rechazo: los proyectos sostenidos por voluntarios tienen colas largas, y la insistencia serena a intervalos razonables funciona mucho mejor que el reproche.
El tipo y el ámbito de cada mensaje alimentan procesos automáticos: qué aparece en las notas de la versión, qué se considera candidato a retroporte y cómo se agrupan los cambios por área. Un mensaje que no sigue la convención no es un descuido de estilo, es un dato que se pierde y que alguien tendrá que reconstruir a mano dentro de dos años, buscando el origen de una regresión.
# La comprobacion previa que evita la mitad de los rechazos
make lint # estilo de C y de Lua
TEST_FILE=test/functional/api/buffer_spec.lua make functionaltest
git diff --stat HEAD~1 # el diff deberia caber en la pantalla
git log --oneline HEAD~3..HEAD # un cambio por commit, sin ruido
Mínimo y probado
Dos líneas con una prueba pesan más que doscientas sin ella.
Discutir antes
Todo cambio visible para el usuario se discute en una cuestión abierta antes de escribirse.
Compatibilidad perpetua
La API es un contrato; se añade, se documenta y casi nunca se cambia.
Conviene entender la lógica que gobierna estas decisiones, porque desde fuera parece conservadurismo y no lo es. Cuando propones una funcionalidad al núcleo, el coste que estás proponiendo no es el de escribirla —ese lo pagas tú, hoy, y es el barato— sino el de sostenerla indefinidamente: mantenerla compilando en cada plataforma, no romperla al refactorizar lo que tiene debajo, responder a sus incidencias, documentarla, probarla y no poder retirarla nunca porque alguien la usa. Ese coste lo paga un grupo pequeño de personas que no te conocen, y lo pagan con el mismo tiempo con el que arreglan lo que ya existe. Por eso la pregunta previa a cualquier parche interesante no es funciona esto sino pertenece esto al núcleo, y la respuesta correcta suele ser que no: si puede vivir como plugin, debe vivir como plugin, porque ahí puede equivocarse, iterar rápido, morir sin consecuencias y ser sustituido por algo mejor sin que nadie tenga que negociarlo. Lo que sí pertenece al núcleo es lo que ningún plugin puede construir: un mecanismo que requiere acceso al almacenamiento del buffer, una primitiva que hoy no está expuesta, una corrección de comportamiento, un contrato que todos los plugins necesitan compartir para interoperar. Fíjate en que ese criterio es exactamente el de la separación entre mecanismo y política con la que abrimos el nivel, aplicada ahora a la gobernanza en lugar de a la arquitectura, y no es casualidad: los proyectos que sobreviven décadas son aquellos cuya estructura social refleja su estructura técnica. Interioriza esto y habrás ganado algo que trasciende por completo a este editor, porque es el mismo cálculo que gobierna tu propio código: cada línea que añades a un sistema compartido es una hipoteca que firmas en nombre de todos los que vendrán a mantenerlo, y la señal más fiable de madurez técnica no es la capacidad de construir cosas, sino el criterio para decidir dónde deben vivir y la disciplina de no ponerlas en el sitio cómodo cuando el sitio correcto es otro.
- Clona el repositorio, compila, ejecuta la suite funcional completa una vez y anota cuánto tarda en tu máquina.
- Lee el documento de contribución y el de mantenimiento, y localiza quién mantiene el área que más te interesa.
- Busca en el historial una función de
runtime/lua/vim/cuya documentación te resultara confusa y prepara un cambio que la aclare. - Escribe el commit con la convención del proyecto, ejecuta las comprobaciones de estilo y verifica que el diff cabe en una pantalla.
- Elige una funcionalidad que hayas deseado alguna vez en el editor y argumenta por escrito, en un párrafo, si pertenece al núcleo o a un plugin y por qué.