Multi-Worktree ist manchmal nützlich

Einige Beispiele:

  • Der Build ist gebrochen. Lokal funktionieren alle Tests, aber in der CI klemmt was. Während der Wartezeit zwischen Check-In und dem nächsten Resultat aus der CI möchte ich mit dem Feature weitermachen, an dem ich gerade entwickle.

  • Allgemeiner gesprochen: Als DevOp entwickle ich gleichzeitig an einem Dev-Feature und einem Op-Infrastrukturthema.

  • Eine Kollegin bittet mich um einen Codereview ihres Feature-Branches. Ich unterbreche dazu meine laufenden Tätigkeiten. Meinen Worktree möchte ich einerseits für das Review nicht durcheinander bringen. Andererseits hätte ich ihren Code gerne lokal auf meiner Festplatte.

  • Ich möchte den Code des letzten offiziellen Standes (Produktionsrelease, letzte freigegebene Version, …) ausgecheckt parat haben, für schnelle Vergleiche.

  • Ich entwickle parallel an mehreren Feature-Branches.

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.  ↩