Spis treści

Worktree - nowe drzewo gita

Mechanizm worktree w gicie

Wielu programistów używa gita do kontroli wersji. Stał się on już pewnego rodzaju standardem we współczesnym narzędziowniku dewelopera. Kiedy zabieramy się za nowe zadanie, zaczynamy od stworzenia brancha ze zmianami. Co jednak, gdy musimy przełączać się między zadaniami, a zmiana brancha jest dla nas zbyt mało wygodna? Z pomocą przychodzi worktree.

tl;dr

  • git worktree pozwala pracować na kilku branchach jednocześnie, każdy w osobnym folderze
  • Wszystkie foldery korzystają z jednego repozytorium, więc commity z jednego są od razu widoczne w pozostałych
  • Nowy worktree tworzymy poleceniem git worktree add, listę sprawdzamy przez git worktree list, a usuwamy przez git worktree remove
  • Jeden branch może być wybrany tylko w jednym worktree naraz
  • Konfiguracja gita i hooki są współdzielone, a prywatne pliki można kopiować automatycznie hookiem post-checkout
  • Przydaje się przy długich testach, osobnych buildach i równoległej pracy z agentami AI

Czym jest worktree?

Sytuacja jest prosta. Mamy swój folder z projektem i chcemy zacząć pracę nad nowym zadaniem. Tworzymy więc nowy branch i zaczynamy pracę. Uruchamiamy aplikację i gdy robota jest już rozgrzebana, manager prosi nas o sprawdzenie czegoś w międzyczasie.

Najczęściej w takim przypadku commitujemy zmiany (lub korzystamy z git stash), aby nie stracić swojej pracy, a następnie w tym samym folderze przełączamy się na nowy branch. Nie ma problemu, kiedy kosztuje nas to tylko zmianę brancha, ale jeśli w tym miejscu mamy już skompilowany projekt w odpowiedniej wersji, powrót do poprzedniej wersji może kosztować sporo nerwów.

Może się też zdarzyć, że na projekcie chcemy odpalić jakieś testy, które na bieżąco będziemy poprawiać. Jeśli trwają długo i działają w tle, mogą zablokować naszą pracę – chyba że zrobimy kopię całego folderu.

Nowe zadanie, nowy folder

Sprytniejsi stworzą nowy folder, z którego zaczną wprowadzać nowe zmiany. Jest to wygodne rozwiązanie, ponieważ oddziela od siebie foldery z pracą i umożliwia oddzielenie wygenerowanych przy budowaniu paczek od reszty pracy. Niestety taka kopia to w praktyce osobne repozytorium – commity zrobione w jednym folderze nie są widoczne w drugim bez dodania remote i git fetch, co utrudnia mergowanie pomiędzy branchami.

Tutaj z pomocą przychodzi worktree. Pozwala stworzyć nowy folder z osobnym branchem roboczym, ale zachowuje połączenie w systemie kontroli wersji. Możemy więc porównać swoje zmiany pomiędzy stworzonymi gałęziami, bez łączenia repozytoriów za pomocą dodatkowych remote.

Dodatkowo, jeśli usuniemy taki folder stworzony za pomocą worktree, rzeczy, które zostały zrobione na branchu, zostają w naszej historii (o ile je zacommitujemy).

Wątek AI

Warto też wspomnieć, że funkcjonalność ta jest przydatna do pracy z agentami AI. Możemy zlecić im pracę w pętli na wydzielonej gałęzi, dostarczając niezależną kopię programu. Jeśli potrzebujemy wywołać jakieś testy lub uruchomić program, nie będzie to kolidowało z innym środowiskiem, w którym pracujemy nad czymś innym. Warto tutaj uruchamiać program w kontenerze, żeby nie kolidowały też ze sobą porty urządzenia, ale to temat na inny wpis.

Co ciekawe, agenty, takie jak Claude Code, potrafią same tworzyć własne worktree, aby nie modyfikować naszego kodu. Z tego punktu widzenia nie jest to dla nich nic nowego.

Obsługa gita

Jak już wspomniałem wcześniej, użyjemy git worktree, aby stworzyć kopię naszego projektu w kolejnym folderze z innym branchem.

Nowy worktree

Zakładamy, że tworzymy katalog o nazwie project-feature1 i nowy branch dla niego branch/feature-1.

git worktree add -b branch/feature-1 ../project-feature1

Możemy też pracować na wcześniej stworzonym branchu:

git worktree add ../project-feature1 branch/feature-1
Ostrzeżenie
Pamiętajmy, że z powodów bezpieczeństwa i spójności danych git nie pozwoli zrobić checkoutu brancha, który jest już używany w innym worktree. Nie możemy mieć kopii tej samej gałęzi w dwóch miejscach. Musimy stworzyć nowy branch.

Lista worktree

Jeśli chcemy sprawdzić, jakie worktree istnieją i na jakich branchach pracują, wystarczy wpisać:

git worktree list

Usuwanie worktree

Kiedy już zakończymy pracę nad naszym zadaniem i folder nie jest nam już potrzebny, możemy usunąć worktree.

git worktree remove ../project-feature1

Może się także wydarzyć, że przez przypadek (lub z premedytacją) usuniemy folder z naszym featurem. Wtedy z pomocą przychodzi polecenie prune, które czyści metadane gita i dopasowuje je do stanu faktycznego.

git worktree prune
Info
Warto zauważyć, że chociaż nasz folder został usunięty, to zacommitowane zmiany i tak zostają w branchu. Nie stałoby się tak, gdyby folder ze zmianami został utworzony przez zwykłe kopiowanie.

Współdzielona konfiguracja

Niewątpliwą zaletą git worktree jest współdzielenie konfiguracji gita pomiędzy folderami. Wszystkie ustawienia dotyczące projektu w gicie są zatem widoczne we wszystkich worktree. Dotyczy to np. .git/info/exclude, który przetrzymuje listę plików niepodlegających wersjonowaniu.

Wskazówka

Domyślnie te ustawienia są wspólne. Jeśli potrzebujemy ustawień tylko dla jednego worktree, możemy włączyć opcję extensions.worktreeConfig, a następnie zapisywać je poleceniem git config --worktree. Trafiają one wtedy do pliku .git/worktrees/<nazwa>/config.worktree i nie wpływają na pozostałe foldery. Nie dotyczy to jednak info/exclude, który zawsze jest wspólny.

git config extensions.worktreeConfig true
git config --worktree core.sparseCheckout true

Może się jednak tak zdarzyć, że podczas pracy korzystamy z prywatnej konfiguracji, która nie jest częścią repozytorium (np. mamy własne pliki konfiguracyjne AI). git worktree add tworzy tylko pliki śledzone przez gita, więc takie pliki (np. .env czy lokalne konfiguracje) musimy w takim wypadku sami kopiować do nowo stworzonych folderów.

Można jednak ten proces łatwo zautomatyzować korzystając z hooków gita. W tym przypadku pomocny jest hook o nazwie post-checkout. Wywoływany jest za każdym razem po poprawnym procesie checkout.

#!/usr/bin/env bash

PREV_HEAD=$1
NEW_HEAD=$2
CHECKOUT_TYPE=$3

# Run only on `git worktree add` (null previous HEAD, branch checkout)
if [[ ! "$PREV_HEAD" =~ ^0+$ ]] || [[ "$CHECKOUT_TYPE" != "1" ]]; then
  exit 0
fi

# My code after `git worktree add`

Jeśli np. używamy konfiguracji dla stow (tak jak tutaj), możemy spróbować czegoś takiego:

#!/usr/bin/env bash

PREV_HEAD=$1
NEW_HEAD=$2
CHECKOUT_TYPE=$3

# Run only on `git worktree add` (null previous HEAD, branch checkout)
if [[ ! "$PREV_HEAD" =~ ^0+$ ]] || [[ "$CHECKOUT_TYPE" != "1" ]]; then
  exit 0
fi

WORKTREE_PATH=$(pwd)

cd ~/configuration-folder
stow --target "${WORKTREE_PATH}" configuration_variant

Plik zapisujemy w głównym repozytorium jako .git/hooks/post-checkout – hooki są wspólne dla wszystkich worktree. Nie zapomnijmy o dodaniu praw do uruchomienia za pomocą polecenia:

chmod +x .git/hooks/post-checkout

Po co mi to

A teraz kilka sytuacji, w których może nam się przydać git worktree:

  • Rozdzielenie zadań, w których mamy tymczasowe raporty niepodlegające wersjonowaniu
  • Budowanie aplikacji dla różnych wersji środowiska, żeby nie nadpisywać builda
  • Dłuższe testy, nieblokujące głównej gałęzi kodu
  • Praca z agentami nad kilkoma zadaniami jednocześnie, szczególnie gdy zlecamy im dłuższą pracę
  • Robienie prezentacji kodu, gdy nie chcemy nadpisywać swoich ustawień w trakcie pracy

Podsumowanie

Jak widzimy, git worktree to ciekawe rozwiązanie do rozłączenia pracy nad różnymi zadaniami w kodzie. Pozwala nie tylko stworzyć oddzielne, tymczasowe środowiska pracy, ale wszystkie mają wspólną historię i branche – commit zrobiony w jednym folderze od razu widać w pozostałych. Dzięki temu możliwe jest łatwe przeglądanie historii całego gita z dowolnej z gałęzi.

Sprawdzi się to więc idealnie do pracy z AI oraz z wieloma zadaniami równocześnie. Warto też połączyć to z innymi technologiami, takimi jak konteneryzacja czy tmux do oddzielenia terminala. Myślę, że to jedna z tych funkcjonalności, które przydadzą się w coraz szybciej zmieniającej się pracy programisty.

Bibliografia

  1. https://git-scm.com/docs/git-worktree - Oficjalna dokumentacja gita z opisem polecenia