Multi-Worktree ist manchmal nützlich

Einige Beispiele:

Wie so oft gibt es verschiedene Wege, auf solche Herausforderungen zu reagieren. Man kann zwischen mehreren Branches hin- und her wechseln. Man kann mit git stash arbeiten. Ich finde es in solchen Situationen angenehm, für jede Aktivität in ein eigenes Verzeichnis wechseln zu können, in git-Sprech: einen eigenen Worktree zu haben. Darum geht es hier.

Dazu kann man natürlich mit git clone in verschiedenen Verzeichnissen jeweils ganz von vorne anfangen. Schneller und bequemer geht es mit dem Git-Subkommando git worktree. Es erlaubt, vom selben lokalen Repository aus mehrere Worktrees zu bedienen.

Konkretes Beispiel

Während ich diesen Artikel schreibe, habe ich tatsächlich das Problem, dass der Build (mal wieder…) gebrochen ist. Lokal laufen die Tests durch. Ich vermute, dass im CI-Setup eine Testdatenbank nicht richtig initialisiert wird.

Wenn ich nur an diesem Buildproblem arbeiten wollte, würde ich einen neuen Branch eröffnen und loslegen:

git fetch -p
git checkout -b build_repair origin/master

Aber leider braucht unsere CI-Pipeline knapp 10 Minuten vom git push bis zum Fehlschlagen des Tests. Insofern mag ich meine sonstige Projektarbeit nicht völlig beiseite legen, bis ich das Problem gefixt habe.

Also mache ich es anders. Einen neuen Branch eröffne ich auch, aber ich checke ihn in ein Nachbarverzeichnis aus. Das git checkout Kommando ersetze ich dafür durch:

git worktree add -b build_repair ../build-repair origin/master

Das erzeugt mir einen neuen Branch build_repair und checkt ihn in einem zweiten Worktree im Nachbarverzeichnis ../build-repair aus. (Zur Unterscheidung benutze ich “-” im Verzeichnis und “_” im Branch.)

Wenn es den Branch schon gibt, verkürzt sich das zu:

git worktree add ../build-repair build_repair

Damit ist build-repair ein reiner Worktree ohne eigene Kopie des lokalen Repositories. Statt des üblichen Unterverzeichnisses .git findet sich dort nur ein Verweis.

Beide Worktrees teilen sich ein Repository. Dadurch stehen Commits, Branches und so weiter einheitlich zur Verfügung. Was man im einen Worktree erarbeitet hat, kann man im anderen sofort nutzen, zum Beispiel für git rebase oder git merge oder sogar für git push. Ein Umweg über origin ist nicht nötig.

Natürlich gibt es einen getrennten Index für den neuen Worktree. Damit funktionieren git add, git rm, git reset und so weiter wie erwartet.

Im konkreten Beispiel arbeite ich im build-repair-Verzeichnis ganz normal: git commit, git push. Wartezeiten kann ich mit Arbeiten im Originalverzeichnis sinnvoll füllen. Wenn der Build endlich läuft (im konkreten Fall war das nach zwei Commits der Fall), putze ich mit git rebase -i die Historie sauber[1]. Schließlich stelle ich den Pull Request.

Habe ich den Build repariert und brauche das build-repair - Verzeichnis nicht mehr, so kann ich es (aus dem Haupt-Worktree heraus) sauber abräumen mit

git worktree remove ../build-repair

Fazit

Ich empfinde es als übersichtlich und angenehm, wenn verschiedene Dinge nebeneinander in verschiedenen Verzeichnissen liegen. Mit git worktree kann ich Aktivitäten an Git-Repos so organisieren. Seit ich es kennengelernt habe, nutze ich es häufig. Es kommt meiner Arbeitsweise sehr entgegen.

  1. Ich empfehle, “rewriting of history” in allen Feature Branches mindestens bis zum Pull Request zuzulassen. Und sogar weiter bis zum Merge, wenn man sicherstellt, dabei die Reviewer nicht zu stören, zum Beispiel durch Absprachen. Übersichtlichkeit der entstehenden Historie ist (mir) wertvoller als ein Ablaufprotokoll.  ↩