Das Vokabular, das jj von Git übernimmt, verändert und weglässt

Die Falle bei jj ist, dass es mehrere Git-Begriffe für etwas anderes wiederverwendet, statt durchgängig neue zu erfinden. Das ist zu Beginn schlimmer als ein sauberer Schnitt gewesen wäre: Ein wirklich neues Wort zwingt dazu, nachzuschlagen, ein vertrautes lässt einen annehmen, man kenne es schon.

Branch wurde zu Bookmark, und der Unterschied ist nicht kosmetisch. Ein Git-Branch folgt immer seiner Spitze - committest du darauf, rückt er automatisch mit vor. Ein jj-Bookmark tut das nicht; es bleibt dort stehen, wo du es hingesetzt hast, bis du es mit jj bookmark set bewegst. Als ich zum ersten Mal dreimal committete und erwartete, dass mein Feature-Bookmark mitwandert, und feststellte, dass es immer noch auf den ersten Commit zeigte, hörte das auf, eine Namenskuriosität zu sein, und wurde zu einer Tatsache, die ich tatsächlich verinnerlichen musste.

HEAD wurde zu @, und @ ist ein echter Commit, kein Zeiger auf einen. Gits HEAD ist ein Ref, das auf einen Commit zeigt; jjs @ benennt den Working-Copy-Commit direkt, vor jedem Befehl frisch von der Festplatte gesnapshotet. Es gibt kein Äquivalent zu “detached HEAD”, weil es nichts gibt, wovon man sich lösen könnte - @ ist einfach dort, wo man gerade ist.

Aus einem Commit-SHA wurden zwei Ids statt einer. Die Commit-Id ist derselbe Content-Hash, den Git schon immer hatte, und sie ändert sich jedes Mal, wenn der Commit umgeschrieben wird, genau wie ein SHA es tun würde. Die Change-Id ist neu: eine stabile Bezeichnung für “dieses Stück Arbeit”, die Edits, Rebases und Amends unverändert übersteht. Arbeit über die Change-Id statt über die Commit-Id anzusprechen ist die eine Gewohnheit, die sich am meisten ausgezahlt hat - die Referenz funktioniert einfach weiter, nachdem ein Rebase eine Git-SHA verwaist hätte.

Unter den umbenannten Begriffen gibt es ein paar Konzepte, die auf nichts abbilden, was Git hat. Das Operation-Log ist umfassender als ein Reflog - es zeichnet jede Änderung am Repository-Zustand auf, nicht nur Ref-Bewegungen, weshalb jj undo ein Rebase, ein Describe oder eine Config-Änderung mit demselben Befehl rückgängig machen kann. Revsets ersetzen das Durcheinander aus Ref-Namen und Range-Syntax, das Git über fünfzehn Jahre angesammelt hat, durch eine tatsächliche kleine Abfragesprache. Und der Colocated-Modus selbst - .jj liegt neben .git und liest dieselbe Objektdatenbank - hat auf Git-Seite keinen Namen, weil Git ihn nie brauchte; es ist jjs Antwort auf die Frage “wie setze ich mich auf etwas, das bereits existiert”.

Und dann gibt es das, was schlicht nicht mehr da ist. Keine Staging-Area, weil die Working Copy schon ein Commit ist. Kein Detached HEAD, aus dem oben genannten Grund. Kein stash, weil Arbeit beiseitezulegen einfach jj new ist - die alte Arbeit bleibt ein echter Commit, nichts muss zwischengelagert werden. Kein Zustand mitten im Rebase, in dem man steckenbleiben kann: Ein Rebase mit Konflikt erzeugt einen Commit, der den Konflikt festhält, und lässt einen weiterarbeiten, statt das Repository einzufrieren, bis man auflöst oder abbricht.

Es lohnt sich, das klar zu sagen, weil es leicht untergeht, sobald sich die Unterschiede häufen: Remotes, Fetch, Push, Konfliktmarker, .gitignore - nichts davon wurde neu erfunden. jjs eigene Konzepte hören genau an der Grenze der Git-Objektdatenbank auf, und alles jenseits dieser Grenze bedeutet exakt das, was es immer bedeutet hat.