Forkeando repositorios con jj
“Fork” es una de las palabras más usadas en el trabajo diario con GitHub, GitLab,
Gitea, Forgejo o Bitbucket, y no existe ni en git ni en jj. git help no tiene
ningún comando fork, y jj help tampoco. Todo lo que hace que forkear parezca
una extensión natural del control de versiones es una funcionalidad de la
plataforma de hosting, añadida sobre una herramienta que solo entiende de
repositorios, commits y refs - y esa frontera jj ni siquiera la toca. Los
ejemplos de abajo usan jj en todo momento, en modo colocated.
Un fork es una funcionalidad de la plataforma, y jj hereda ese vacío sin
cambios. Cuando una plataforma forkea un repositorio, crea un segundo
repositorio, propiedad de otra cuenta o grupo, cuyo historial empieza siendo
idéntico al original. Desde el punto de vista de git - y jj nunca lo contradice
en este punto - son simplemente dos repositorios independientes que comparten un
commit ancestro común. Ni .git ni nada que jj añada encima registra “esto es
un fork de aquello”. Esa asociación vive por completo en la base de datos de la
plataforma, accesible desde su interfaz web o su API.
jj renombra el vocabulario local, no la mecánica de los remotos. Branch pasó
a ser bookmark, HEAD pasó a ser @, pero jj git clone, jj git remote,
jj git fetch y jj git push siguen hablando con los mismos remotos git, sobre
los mismos protocolos, que sus equivalentes en git. Clonar el propio fork y
añadir el original como segundo remoto se lee casi como git con un prefijo
delante:
jj git clone --colocate https://example.com/tu-usuario/project.git
cd project
jj git remote add upstream https://example.com/original/project.git
jj git fetch --remote upstream
origin y upstream siguen siendo solo nombres, sin nada que los imponga
salvo la costumbre.
Un bookmark que no te sigue es justo lo que hace que sincronizar aquí sea
indoloro. Una rama de git con main como checkout se mueve en cada commit -
por eso mantener el main de un fork impoluto exige disciplina: un commit
descuidado y ya se ha desviado. Un bookmark de jj nunca se mueve solo, mientras
nadie describa un commit encima de él. Y mientras eso se cumpla, sincronizar no
es un merge ni un rebase, solo mover un puntero hasta donde el fetch ya
aterrizó:
jj git fetch --remote upstream
jj bookmark set main -r main@upstream
jj git push --remote origin --bookmark main
Si git.auto-local-bookmark está desactivado, ese primer fetch deja el estado
de upstream accesible solo como main@upstream hasta que jj bookmark track main@upstream lo promueve a algo que jj bookmark set pueda referenciar por su
nombre simple.
Contribuir sigue significando empujar un bookmark al fork, nunca a upstream.
Ahí casi nunca se tiene acceso de escritura, con o sin jj. Un bookmark se crea,
se empuja a origin, y en el instante en que cruza al remoto deja de ser un
bookmark - jj git push lo escribe como una rama git corriente, porque ese es
el único vocabulario que el remoto, y la plataforma que lo lee, entienden.
jj new main -m "fix timeout"
jj bookmark create fix-timeout
jj git push --remote origin --bookmark fix-timeout
El pull o merge request se abre después desde la interfaz o la API de la plataforma, comparando esa rama con la rama destino de upstream - una comparación que la plataforma hace enteramente en términos de git, sin saber en ningún momento que hubo un bookmark de por medio.
El modo colocated hace que el mecanismo previo a las plataformas nunca haya
desaparecido. Un repositorio bare servido por SSH, sin GitHub, GitLab o una
capa similar encima, no tiene ningún concepto de fork ni ningún pull request
que abrir. jj git push y jj git fetch funcionan igual de bien contra él -
siempre funcionaron contra cualquier remoto git, hubiera plataforma o no -,
pero el paso de “proponer un cambio” vuelve a lo que git ya soportaba antes de
que existiera cualquiera de estas plataformas. Como el modo colocated mantiene
un .git real justo al lado, git format-patch y git am siguen tan
disponibles como siempre. jj nunca intentó reemplazar esa capa, así que ahí no
había nada que perder.
Renombrar o eliminar el repositorio upstream es tan invisible para jj como lo
es para git. Una entrada bajo jj git remote sigue siendo solo una URL; jj no
tiene más vocabulario que git para “la plataforma movió esto debajo de ti”. Ese
punto ciego nunca fue contabilidad local - está al otro lado de la misma
frontera donde vive el propio forkeo, la frontera que jj decidió deliberadamente
no cruzar.