Kamil Owczarek
Opublikowano

Git worktrees i Vercel CLI: jak naprawić niekompatybilność

Autorzy

Dlaczego git worktrees są świetne

Git worktrees pozwalają pracować nad kilkoma branchami naraz, bez stashowania i przełączania. Każdy worktree to pełny katalog roboczy z własnym branchem:

# Main repo on 'dev'
~/project/

# Worktree for feature work
~/project-feature-auth/

# Worktree for bug fix
~/project-bugfix-123/

Przy pracy w monorepo to zmienia wszystko. Możesz:

  • Puszczać testy na jednym branchu, kodując na drugim
  • Porównywać dwie implementacje obok siebie
  • Przełączać kontekst bez tracenia stanu

Problem: Vercel CLI wywala się w worktree

W skrypcie dev używamy vercel env pull, żeby pobrać zmienne środowiskowe:

{
  "scripts": {
    "dev": "vercel env pull .env -y --environment=development && nuxt dev"
  }
}

W zwykłym repo działa to bez zarzutu. W worktree — nie:

$ npm run dev

Error: Could not read from .git/config

Dlaczego się wywala

Kluczowa różnica to sposób, w jaki działa .git:

Zwykłe repo:

.git/Directory containing git internals
  config       ← Vercel reads this
  HEAD
  objects/
  ...

Git worktree:

.gitFile (not directory!) pointing to main repo
# Contents: "gitdir: /path/to/main/repo/.git/worktrees/feature-name"

Vercel CLI próbuje odczytać .git/config, zakładając, że .git jest katalogiem. W worktree to plik, więc taka ścieżka po prostu nie istnieje.

Rozwiązanie: omijamy Vercel CLI w worktree

Wystarczy osobny skrypt dev, który pomija vercel env pull:

Krok 1: dodaj skrypt dev:worktree

// package.json
{
  "scripts": {
    "dev": "vercel env pull .env -y --environment=development && nuxt dev --port 3000",
    "dev:worktree": "npx prisma generate && nuxt dev --port 3000"
  }
}

Skrypt dla worktree:

  1. Pomija vercel env pull (korzysta ze skopiowanego wcześniej .env)
  2. Regeneruje klienta Prismy (na wypadek zmian w schemacie)
  3. Startuje dev server jak zwykle

Krok 2: skonfiguruj Turbo (monorepo)

Jeśli używasz Turborepo, dodaj nowe zadanie:

// turbo.json
{
  "tasks": {
    "dev": {
      "cache": false,
      "persistent": true
    },
    "dev:worktree": {
      "cache": false,
      "persistent": true
    }
  }
}

Krok 3: skrypt musi trafić do każdej aplikacji

Każda aplikacja w monorepo potrzebuje własnego skryptu worktree:

// apps/main-app/package.json
{
  "scripts": {
    "dev": "vercel env pull .env -y --environment=development && nuxt dev --port 3000",
    "dev:worktree": "npx prisma generate && nuxt dev --port 3000"
  }
}

// apps/admin/package.json
{
  "scripts": {
    "dev": "vercel env pull .env -y --environment=development && nuxt dev --port 3001",
    "dev:worktree": "npx prisma generate --schema=../main-app/prisma/schema.prisma && nuxt dev --port 3001"
  }
}

// apps/docs/package.json
{
  "scripts": {
    "dev": "nuxt dev --port 3002",
    "dev:worktree": "echo 'docs - skipped in worktree mode'"
  }
}

Krok 4: skrypt w root package.json

// package.json (root)
{
  "scripts": {
    "dev": "turbo run dev",
    "dev:worktree": "turbo run dev:worktree"
  }
}

Zakładanie nowego worktree

Tak wygląda mój workflow przy tworzeniu worktree:

1. Stwórz worktree

# From main repo directory
git worktree add ../project-feature-name -b feature/branch-name

To tworzy:

  • Nowy katalog ../project-feature-name
  • Nowy branch feature/branch-name
  • Checkout kodu w tym katalogu

2. Skopiuj niezbędne pliki

Worktrees nie kopiują plików ignorowanych przez gita. Będziesz potrzebować:

  • node_modules/ (albo instalacji od nowa)
  • .env (kopia z głównego repo)
  • .nuxt/, .output/ (i tak się regenerują)

Do śledzenia tego, co kopiować, używam pliku .worktreeinclude:

# .worktreeinclude
node_modules
.env
.nuxt
.output
.turbo

I krótkiego skryptu:

#!/bin/bash
# scripts/setup-worktree.sh
SOURCE_DIR="$1"
TARGET_DIR="$2"

while read -r file; do
  if [ -e "$SOURCE_DIR/$file" ]; then
    cp -r "$SOURCE_DIR/$file" "$TARGET_DIR/$file"
  fi
done < "$SOURCE_DIR/.worktreeinclude"

3. Odpal dev w worktree

cd ../project-feature-name
npm run dev:worktree

Zarządzanie wieloma worktree'ami

Lista wszystkich worktree

git worktree list
# /Users/you/project          abc1234 [dev]
# /Users/you/project-feature  def5678 [feature/auth]
# /Users/you/project-bugfix   ghi9012 [fix/issue-123]

Usuwanie worktree

Kiedy feature jest gotowy:

# Remove the worktree directory
git worktree remove ../project-feature-name

# If you also want to delete the branch
git branch -d feature/branch-name

Czyszczenie osieroconych worktree

Jeśli skasowałeś katalog worktree ręcznie:

git worktree prune

Typowe problemy

Konflikty portów

Każdy worktree odpala własny dev server. Ustaw różne porty:

// Main repo
"dev:worktree": "nuxt dev --port 3000"

// Worktree 1
"dev:worktree": "nuxt dev --port 3010"

// Worktree 2
"dev:worktree": "nuxt dev --port 3020"

Albo dynamicznie, przez zmienną środowiskową:

PORT=3010 npm run dev:worktree

Problemy z klientem Prismy

Każdy worktree potrzebuje własnej generacji klienta Prismy:

cd ../project-feature-name
npx prisma generate

Właśnie dlatego dev:worktree zawiera npx prisma generate.

Migracje bazy danych

Z migracjami w worktree trzeba uważać. Jeśli siedzisz na branchach z różnymi schematami:

  1. Używaj osobnej bazy dla branchy eksperymentalnych
  2. Albo synchronizuj schematy, zanim przeskoczysz między worktree'ami

Czemu nie po prostu branche?

Przełączanie branchy:

  • Ubija dev server
  • Gubi niezapisany stan w edytorze
  • Przebudowuje wszystko od zera

Z worktree:

  • Każdy branch ma własny, działający dev server
  • Możesz mieć otwartych kilka okien VS Code naraz
  • Przełączenie kontekstu jest natychmiastowe

W monorepo, gdzie npm run dev startuje ponad 30 sekund, worktrees oszczędzają godziny.

Docelowy układ

~/project/                    # Main repo (dev branch)
  ├── package.json           # dev & dev:worktree scripts
  ├── turbo.json            # Both tasks configured
  ├── .worktreeinclude      # Files to copy to new worktrees
  └── apps/
      └── main-app/
          └── package.json   # App-specific worktree script

~/project-feature-auth/       # Worktree (feature/auth branch)
  ├── node_modules/          # Copied from main
  ├── .env                   # Copied from main
  └── apps/
      └── main-app/
          └── .nuxt/         # Regenerated

~/project-bugfix-123/         # Another worktree
  └── ...

Ściąga

# Create worktree with new branch
git worktree add ../project-name -b feature/name

# Create worktree on existing branch
git worktree add ../project-name existing-branch

# List worktrees
git worktree list

# Remove worktree
git worktree remove ../project-name

# Run dev in worktree
cd ../project-name && npm run dev:worktree

Jednorazowa konfiguracja zajmuje 10 minut. Zysk na produktywności zostaje na zawsze.


Prawdziwy workflow z monorepo na Nuxcie 3 z Turborepo i Vercelem. Kilka feature'ów rozwijanych równolegle, bez ani jednego przełączania brancha.