El vocabulario que jj toma prestado de Git, cambia y descarta

La trampa con jj es que reutiliza varias palabras de Git para cosas distintas, en lugar de inventar términos nuevos de principio a fin. Eso es peor que una ruptura limpia, al menos al principio: una palabra genuinamente nueva obliga a buscarla, una familiar deja asumir que ya la conoces.

Branch se convirtió en bookmark, y la diferencia no es cosmética. Un branch de Git siempre sigue su punta - haces commit sobre él y avanza contigo, automáticamente. Un bookmark de jj no hace nada de eso; se queda donde lo dejaste hasta que lo mueves con jj bookmark set. La primera vez que hice commit tres veces esperando que mi bookmark de feature me siguiera y descubrí que seguía apuntando al primer commit, esto dejó de ser una rareza de nombres y pasó a ser un hecho que tuve que interiorizar de verdad.

HEAD se convirtió en @, y @ es un commit real, no un puntero a uno. El HEAD de Git es una referencia que apunta a un commit; el @ de jj nombra directamente el commit de la copia de trabajo, tomado de disco antes de cada comando. No existe un equivalente a “detached HEAD” porque no hay nada de qué separarse - @ es simplemente donde estás en cada momento.

Un SHA de commit se convirtió en dos ids en lugar de uno. El id de commit es el mismo tipo de hash de contenido que Git siempre tuvo, y cambia cada vez que se reescribe el commit, igual que lo haría un SHA. El id de cambio es nuevo: una etiqueta estable para “este trozo de trabajo” que sobrevive intacta a ediciones, rebases y amends. Referirse al trabajo por id de cambio en lugar de por id de commit es el hábito que más ha dado resultado - la referencia simplemente sigue funcionando después de un rebase que habría dejado huérfano un SHA de Git.

Bajo las palabras renombradas hay un par de conceptos que no corresponden a nada que tenga Git. El registro de operaciones es más amplio que un reflog - registra cada cambio al estado del repositorio, no solo los movimientos de referencias, por lo que jj undo puede deshacer un rebase, un describe o un cambio de configuración con el mismo comando. Los revsets sustituyen el desorden de nombres de referencias y sintaxis de rangos que Git acumuló en quince años por un pequeño lenguaje de consultas real. Y el modo colocado en sí - .jj junto a .git, leyendo la misma base de datos de objetos - no tiene nombre del lado de Git porque Git nunca lo necesitó; es la respuesta de jj a “cómo me instalo sobre algo que ya existe”.

Y luego está lo que sencillamente ya no está. Sin área de staging, porque la copia de trabajo ya es un commit. Sin detached HEAD, por la razón de arriba. Sin stash, porque apartar trabajo es simplemente jj new - el trabajo anterior sigue siendo un commit real, no hace falta guardarlo en ningún sitio. Sin estado a mitad de un rebase en el que quedarse atascado: un rebase con conflicto produce un commit que registra el conflicto y te deja seguir trabajando, en lugar de congelar el repositorio hasta que resuelvas o abortes.

Vale la pena decirlo con claridad, porque es fácil perderlo de vista cuando se acumulan las diferencias: remotos, fetch, push, marcadores de conflicto, .gitignore - nada de eso se reinventó. Los conceptos propios de jj se detienen justo en el límite de la base de datos de objetos de Git, y todo lo que queda al otro lado de ese límite significa exactamente lo que siempre significó.