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.