Repositories forken mit jj
“Fork” ist eines der meistgenutzten Wörter in der täglichen Arbeit mit GitHub,
GitLab, Gitea, Forgejo oder Bitbucket - und es existiert weder in git noch in jj.
git help kennt keinen fork-Befehl, jj help genauso wenig. Alles, was Forken
wie eine natürliche Erweiterung der Versionsverwaltung wirken lässt, ist ein
Feature der Hosting-Plattform, aufgesetzt auf ein Werkzeug, das nur Repositories,
Commits und Refs kennt - und diese Grenze rührt jj gar nicht erst an. Die
Beispiele unten nutzen durchgehend jj, im Colocated Mode.
Ein Fork ist ein Plattform-Feature, und diese Lücke erbt jj unverändert. Wenn
eine Plattform ein Repository forkt, legt sie ein zweites Repository an, das
einem anderen Account oder einer anderen Gruppe gehört und dessen Historie
zunächst identisch zum Original ist. Aus Sicht von git - und jj widerspricht
git an dieser Stelle nie - sind das schlicht zwei unabhängige Repositories, die
zufällig einen gemeinsamen Vorfahren-Commit teilen. Weder .git noch etwas, das
jj darüberlegt, vermerkt “das hier ist ein Fork von jenem”. Diese Zuordnung
existiert ausschließlich in der Datenbank der Plattform, erreichbar über deren
Web-UI oder API.
jj benennt das lokale Vokabular um, nicht die Remote-Mechanik. Aus Branch
wurde Bookmark, aus HEAD wurde @ - aber jj git clone, jj git remote,
jj git fetch und jj git push sprechen weiterhin mit denselben git-Remotes,
über dieselben Protokolle, wie ihre git-Pendants. Den eigenen Fork zu klonen und
das Original als zweiten Remote zu ergänzen liest sich fast wie git mit einem
vorangestellten Präfix:
jj git clone --colocate https://example.com/du/project.git
cd project
jj git remote add upstream https://example.com/original/project.git
jj git fetch --remote upstream
origin und upstream sind weiterhin reine Namen, erzwungen von nichts außer
Gewohnheit.
Ein Bookmark, der einem nicht folgt, ist genau das, was das Synchronisieren
hier schmerzfrei macht. Ein git-Branch, der auf main ausgecheckt ist, bewegt
sich bei jedem Commit mit - genau deshalb erfordert es Disziplin, das main
eines Forks sauber zu halten: ein unachtsamer Commit, und es ist bereits
abgedriftet. Ein jj-Bookmark bewegt sich nie von selbst, solange niemand darauf
committet. Solange das gilt, ist Synchronisieren kein Merge und kein Rebase,
sondern nur das Verschieben eines Zeigers dorthin, wo der Fetch bereits gelandet
ist:
jj git fetch --remote upstream
jj bookmark set main -r main@upstream
jj git push --remote origin --bookmark main
Ist git.auto-local-bookmark ausgeschaltet, bleibt der Zustand von upstream nach
diesem ersten Fetch nur als main@upstream erreichbar, bis jj bookmark track main@upstream ihn zu etwas befördert, das jj bookmark set unter seinem
schlichten Namen ansprechen kann.
Beiträge bedeuten weiterhin, einen Bookmark zum Fork zu pushen, niemals zu
upstream. Dort besteht so gut wie nie Schreibzugriff, mit oder ohne jj. Ein
Bookmark wird erstellt, zu origin gepusht, und in dem Moment, in dem er auf den
Remote übergeht, hört er auf, ein Bookmark zu sein - jj git push schreibt ihn
als gewöhnlichen git-Branch, denn das ist das einzige Vokabular, das der Remote
und die Plattform, die ihn liest, verstehen.
jj new main -m "fix timeout"
jj bookmark create fix-timeout
jj git push --remote origin --bookmark fix-timeout
Der Pull- oder Merge-Request wird anschließend über die UI oder API der Plattform geöffnet und vergleicht diesen Branch mit dem Zielbranch von upstream
- ein Vergleich, den die Plattform vollständig in git-Begriffen anstellt, ohne zu wissen, dass jemals ein Bookmark im Spiel war.
Der Colocated Mode sorgt dafür, dass der Fallback von vor den Plattformen nie
verschwunden ist. Ein bloßes Bare-Repository per SSH, ohne GitHub, GitLab oder
eine vergleichbare Schicht darüber, kennt kein Fork-Konzept und keinen Pull
Request, den man öffnen könnte. jj git push und jj git fetch funktionieren
trotzdem problemlos dagegen - sie funktionierten immer schon gegen jeden
git-Remote, Plattform hin oder her -, aber der Schritt “eine Änderung
vorschlagen” fällt zurück auf das, was git schon vor all diesen Plattformen
konnte. Weil der Colocated Mode ein echtes .git-Verzeichnis direkt daneben
hält, sind git format-patch und git am genauso verfügbar wie eh und je. jj
hat nie versucht, diese Schicht zu ersetzen, also gab es dort auch nichts zu
verlieren.
Wird das Upstream-Repository umbenannt oder gelöscht, bleibt das für jj genauso
unsichtbar wie für git. Ein Eintrag unter jj git remote ist weiterhin nur
eine URL; jj hat nicht mehr Vokabular als git für “die Plattform hat das unter
dir weggezogen”. Dieser blinde Fleck war nie lokale Buchführung - er liegt
jenseits derselben Grenze, an der auch das Forken selbst lebt, genau der
Grenze, die jj bewusst nicht überschritten hat.