Пятница, 17:43. Твоя модель тренируется уже шесть часов. Loss красиво падает, метрики растут — ещё пара эпох и можно будет отправить результаты с победным эмодзи.

И тут приходит сообщение: «Срочно глянь PR, блокер для релиза».

Ты смотришь на терминал. Потом в чат. Потом снова на терминал.

git checkout feature/urgent-fix убьёт твой эксперимент. git stash — это русская рулетка для ML-кода с его конфигами, весами и кешами. Клонировать репозиторий заново? 47 гигабайт датасетов скажут тебе «спасибо».

Знакомо?

Большинство ML-инженеров живут в этом аду переключения контекста годами. А потом узнают про git worktree — и мир делится на «до» и «после».

Что такое Git Worktree (в двух словах)

Git worktree позволяет держать несколько веток в разных папках одновременно, при этом все они используют один .git. Никакого клонирования, никакого дублирования истории, никаких конфликтов при переключении.

Одна команда — и у тебя параллельная вселенная для эксперимента. Ещё одна — и ты ревьюишь PR коллеги, пока твоя модель спокойно тренируется в соседней директории.

Содержание

  1. Часть 1: Основы Git Worktree — концепция, команды, структура

  2. Часть 2: Worktree vs Альтернативы — почему не stash/clone/branches

  3. Часть 3: ML-специфичные сценарии — параллельные эксперименты, изоляция environments, большие датасеты

  4. Часть 4: Интеграция с Cursor — multi-root workspaces, tasks, настройки

  5. Часть 5: Продвинутые техники — bare repos, автоматизация, cleanup

  6. Заключение — takeaways и cheatsheet

К концу статьи у тебя будет готовый workflow, который можно внедрить в свой проект за 10 минут.

Погнали.


Часть 1: Основы Git Worktree

Аналогия, которая всё объясняет

Представь, что твой git-репозиторий — это офис. Обычно у тебя один рабочий стол (working directory), и чтобы переключиться на другую задачу, тебе нужно убрать все бумаги в ящик (stash), переложить новые, поработать, потом вернуть старые обратно.

Worktree — это когда у тебя появляется второй рабочий стол в том же офисе. Все шкафы с документами (.git) общие, но столов несколько. Можешь работать за одним, пока на другом разложен незавершённый проект.

В терминах git: один репозиторий, несколько рабочих директорий, каждая на своей ветке.

Базовые команды

Всё, что тебе нужно знать для старта — четыре команды:

Создать новый worktree:

# Создать worktree для существующей ветки
git worktree add ../ml-classifier-experiment experiment/lr-sweep

# Создать worktree с новой веткой
git worktree add -b feature/new-arch ../ml-classifier-new-arch main

Посмотреть все worktrees:

git worktree list

Вывод будет примерно таким:

/home/user/ml-image-classifier         abc1234 [main]
/home/user/ml-classifier-experiment    def5678 [experiment/lr-sweep]
/home/user/ml-classifier-new-arch      ghi9012 [feature/new-arch]

Удалить worktree:

git worktree remove ../ml-classifier-experiment

Почистить «мёртвые» записи:

git worktree prune

Это если ты удалил папку руками, а git ещё помнит о ней.

Структура директорий

Вот как это выглядит на диске:

~/projects/
├── ml-image-classifier/           # Основной репозиторий (main)
│   ├── .git/                      # Единственный .git на все worktrees
│   ├── src/
│   ├── data/ → /shared/datasets/  # Симлинк на данные
│   ├── models/
│   └── train.py
│
├── ml-classifier-exp-lr-001/      # Worktree 1 (experiment/lr-sweep)
│   ├── .git                       # Это ФАЙЛ, не папка! Ссылка на основной .git
│   ├── src/
│   ├── data/ → /shared/datasets/  # Тот же симлинк
│   ├── models/
│   └── train.py
│
└── ml-classifier-transformer/     # Worktree 2 (feature/transformer)
    ├── .git                       # Тоже файл-ссылка
    ├── src/
    └── ...

Важно: В worktree .git — это текстовый файл с одной строкой: gitdir: /path/to/main/repo/.git/worktrees/name. Вся история, все объекты хранятся в основном репозитории.

Связь с ветками: правило одной ветки

Git не даст тебе создать два worktree на одной ветке. Логика простая: если ты редактируешь файлы в двух местах на одной ветке — это путь к безумию и конфликтам.

# Это сработает
git worktree add ../exp-1 experiment/lr-001

# А это — нет, если experiment/lr-001 уже занята
git worktree add ../exp-2 experiment/lr-001
# fatal: 'experiment/lr-001' is already checked out at '/home/user/exp-1'

Хочешь второй эксперимент — создай вторую ветку. Это не баг, это фича.

До и после: наглядно

Раньше (классический git):

# Тренируешь модель на feature/experiment-1
python train.py --lr 0.001  # Запущено, ждём 6 часов...

# Нужно срочно посмотреть другую ветку
git stash                    # Молимся, что ничего не сломается
git checkout main
# Смотрим код, делаем фикс
git checkout feature/experiment-1
git stash pop                # Иногда работает, иногда — конфликты
python train.py --lr 0.001   # Начинаем заново...

Теперь (с worktree):

# В основной папке тренируем модель
cd ~/ml-image-classifier
python train.py --lr 0.001  # Запущено, работает...

# Нужно глянуть main? Без проблем
git worktree add ../ml-classifier-hotfix main
cd ../ml-classifier-hotfix
# Делаем что нужно, модель в соседней папке продолжает тренироваться

# Закончили — удаляем
cd ..
git worktree remove ml-classifier-hotfix

Никаких stash, никаких прерванных экспериментов, никакой боли.


Часть 2: Worktree vs Альтернативы

«Окей, но я и так справляюсь с branches/stash/clone» — скажешь ты. Давай разберём, почему для ML-проектов worktree выигрывает почти всегда.

Таблица сравнения

Критерий

git checkout

git stash

git clone

git worktree

Скорость переключения

Мгновенно

Мгновенно

Минуты

Мгновенно

Параллельная работа

Нет

Нет

Да

Да

Дисковое пространство

Минимум

Минимум

x2-3 репо

+~1% на worktree

Сохранение процессов

Убивает

Убивает

Изолированы

Изолированы

Общая история

Одна

Одна

Копии

Одна

Синхронизация веток

Авто

Авто

Ручная

Авто

Риск потери данных

Средний

Высокий

Низкий

Низкий

Разбор каждого подхода

Git Checkout (переключение веток)

git checkout feature/experiment-2

Плюсы:

  • Просто и привычно

  • Нет накладных расходов

Минусы:

  • Убивает все запущенные процессы (тренировки, серверы)

  • Uncommitted changes? Конфликты или потеря

  • Невозможно работать над двумя задачами одновременно

Когда использовать: Быстрые правки в 2-3 файла, когда ничего не запущено.


Git Stash

git stash push -m "эксперимент lr=0.001"
git checkout main
# ... делаем дела ...
git checkout feature/experiment
git stash pop

Плюсы:

  • Сохраняет uncommitted изменения

  • Можно иметь несколько stash-ей

Минусы:

  • Не сохраняет untracked файлы (по умолчанию)

  • stash pop на конфликтующих изменениях — боль

  • Легко забыть что в stash-е

  • Не сохраняет запущенные процессы

Когда использовать: Срочное переключение на 5 минут, когда изменения минимальны.

ML-специфичная проблема: Конфиги, чекпоинты, кеши — часто в .gitignore. Stash их не трогает, но checkout может привести к несовместимости.


Git Clone

git clone git@github.com:user/ml-project.git ml-project-experiment

Плюсы:

  • Полная изоляция

  • Никаких конфликтов между копиями

  • Можно работать параллельно

Минусы:

  • Дублирование всей истории (может быть гигабайты)

  • Ручная синхронизация (git pull в каждой копии)

  • Датасеты нужно копировать или настраивать симлинки вручную

  • N репозиториев = N×размер на диске

Когда использовать: Когда нужна полная изоляция от основного репо (например, эксперименты с ломающими изменениями в зависимостях).


Git Worktree

git worktree add ../ml-project-experiment feature/experiment

Плюсы:

  • Мгновенное создание (~секунда)

  • Общий .git — экономия места

  • Автоматическая синхронизация веток

  • Параллельная работа без ограничений

  • Процессы изолированы по директориям

Минусы:

  • Нельзя два worktree на одну ветку

  • Нужно следить за cleanup старых worktrees

  • Менее известен (коллеги могут не понять структуру)

Когда использовать: Практически всегда для ML-проектов.

Вердикт для ML

Если ты работаешь с машинным обучением, worktree — твой выбор по умолчанию. Вот почему:

  1. Долгие процессы — тренировки идут часами. Worktree не прерывает их.

  2. Эксперименты — запустил три варианта гиперпараметров в трёх worktrees, сравниваешь результаты.

  3. Большие данные — симлинки на датасеты работают идентично во всех worktrees.

  4. Code review — создал worktree для PR коллеги, посмотрел, удалил. Основная работа не тронута.

Единственный случай для clone: Тебе нужно радикально изменить зависимости (другая версия CUDA, несовместимый PyTorch) и ты не хочешь рисковать основным репо.

Единственный случай для stash: «Подожди 30 секунд, гляну одну строчку в main и вернусь».

Во всех остальных случаях — worktree.


Часть 3: ML-специфичные сценарии

Теория — это хорошо. Давай разберём конкретные ситуации, с которыми ты сталкиваешься каждый день.

Сценарий 1: Параллельные эксперименты с гиперпараметрами

Задача: Запустить три эксперимента с разными learning rates одновременно.

Структура:

# Создаём worktrees для каждого эксперимента
git worktree add -b experiment/lr-001 ../ml-proj-lr-001 main
git worktree add -b experiment/lr-0001 ../ml-proj-lr-0001 main
git worktree add -b experiment/lr-00001 ../ml-proj-lr-00001 main

После этого структура выглядит так:

~/projects/
├── ml-image-classifier/      # main — здесь основная разработка
├── ml-proj-lr-001/           # experiment/lr-001
├── ml-proj-lr-0001/          # experiment/lr-0001
└── ml-proj-lr-00001/         # experiment/lr-00001

Запуск экспериментов:

# Терминал 1
cd ~/projects/ml-proj-lr-001
python train.py --lr 0.001 --experiment-name "lr-001"

# Терминал 2
cd ~/projects/ml-proj-lr-0001
python train.py --lr 0.0001 --experiment-name "lr-0001"

# Терминал 3
cd ~/projects/ml-proj-lr-00001
python train.py --lr 0.00001 --experiment-name "lr-00001"

Три модели тренируются параллельно. Каждая в своей директории. Никаких конфликтов.

После экспериментов:

# Лучший результат на lr=0.0001? Мержим в main
cd ~/projects/ml-image-classifier
git merge experiment/lr-0001

# Удаляем ненужные worktrees
git worktree remove ../ml-proj-lr-001
git worktree remove ../ml-proj-lr-0001
git worktree remove ../ml-proj-lr-00001

# Чистим ветки
git branch -d experiment/lr-001 experiment/lr-0001 experiment/lr-00001

Сценарий 2: Изоляция Python environments

Проблема: Один эксперимент требует PyTorch 2.0, другой — PyTorch 1.13 (для совместимости со старым кодом).

Решение: Каждый worktree имеет свой .venv:

# Основной проект с PyTorch 2.0
cd ~/projects/ml-image-classifier
python -m venv .venv
source .venv/bin/activate
pip install torch==2.0.0

# Worktree для legacy эксперимента
git worktree add -b experiment/legacy-model ../ml-proj-legacy main
cd ../ml-proj-legacy
python -m venv .venv
source .venv/bin/activate
pip install torch==1.13.0

Или с conda:

# Создаём окружения
conda create -n ml-proj-py39 python=3.9 pytorch=1.13 -y
conda create -n ml-proj-py311 python=3.11 pytorch=2.0 -y

# В каждом worktree активируем нужное
cd ~/projects/ml-proj-legacy
conda activate ml-proj-py39

cd ~/projects/ml-image-classifier
conda activate ml-proj-py311

Важно: .venv/ должен быть в .gitignore. Каждый worktree создаёт свой environment, они не шарятся.

Автоматизация с direnv:

Создай .envrc в каждом worktree:

# ~/projects/ml-proj-legacy/.envrc
source .venv/bin/activate
export CUDA_VISIBLE_DEVICES=0
# ~/projects/ml-image-classifier/.envrc
source .venv/bin/activate
export CUDA_VISIBLE_DEVICES=1

Теперь при cd в директорию автоматически активируется нужный environment.


Сценарий 3: Работа с большими датасетами

Проблема: У тебя 200GB данных в /data/imagenet/. Копировать в каждый worktree — безумие.

Решение: Симлинки

# Структура данных (вне репозитория)
/shared/
└── datasets/
    ├── imagenet/     # 200GB
    ├── coco/         # 50GB
    └── custom/       # 10GB

# В основном репозитории
cd ~/projects/ml-image-classifier
ln -s /shared/datasets/imagenet data/imagenet
ln -s /shared/datasets/coco data/coco

Важно для .gitignore:

# .gitignore
data/imagenet
data/coco
data/custom

# Но трекаем структуру
!data/.gitkeep

# Игнорируем чекпоинты и кеши
checkpoints/
*.pt
*.pth
__pycache__/
.cache/

При создании worktree симлинки копируются автоматически (если они закоммичены как симлинки, а не добавлены в .gitignore).

Если симлинки в .gitignore, создай скрипт setup_data.sh:

#!/bin/bash
# setup_data.sh — запускать после создания worktree

ln -sf /shared/datasets/imagenet data/imagenet
ln -sf /shared/datasets/coco data/coco
echo "Data symlinks created!"

DVC (Data Version Control):

Если ты используешь DVC для версионирования данных:

# Worktree подхватывает .dvc файлы автоматически
cd ../ml-proj-experiment
dvc pull  # Скачивает данные по симлинкам/кешу

DVC хранит данные в общем кеше (~/.dvc/cache), так что даже при dvc pull в новом worktree данные не дублируются.


Сценарий 4: Code Review без прерывания работы

Ситуация: Коллега просит посмотреть PR #42, но у тебя тренируется модель.

Классический подход (боль):

# Останавливаем всё
Ctrl+C  # RIP 4 часа тренировки
git stash
git checkout feature/colleague-pr
# Смотрим код
git checkout -
git stash pop
# Перезапускаем тренировку заново...

С worktree (счастье):

# Модель продолжает тренироваться в текущей директории

# Создаём временный worktree для review
git fetch origin
git worktree add ../ml-proj-review-pr42 origin/feature/colleague-pr

# Открываем в новом окне Cursor
cursor ../ml-proj-review-pr42

# Смотрим код, оставляем комментарии
# ...

# Закончили — удаляем
git worktree remove ../ml-proj-review-pr42

Вся операция занимает 30 секунд на setup и не трогает твой текущий эксперимент.

Pro tip: Для частых review создай alias:

# ~/.bashrc или ~/.zshrc
review-pr() {
    git fetch origin
    git worktree add "../review-pr-$1" "origin/$2"
    cursor "../review-pr-$1"
}

# Использование:
review-pr 42 feature/new-model

Часть 4: Интеграция с Cursor

Worktree сам по себе мощный. Но в связке с Cursor он становится ещё удобнее. Разберём, как выжать максимум.

Открытие worktree как отдельного окна

Самый простой способ — открыть каждый worktree в отдельном окне Cursor:

# Основной проект
cursor ~/projects/ml-image-classifier

# Эксперимент в новом окне
cursor ~/projects/ml-proj-lr-001

Или из терминала внутри Cursor:

# Создаём worktree и сразу открываем
git worktree add ../ml-proj-experiment experiment/new-arch
cursor ../ml-proj-experiment

Каждое окно — независимый контекст. Свой терминал, свой git status, свои открытые файлы.

Multi-root Workspace

Если хочешь видеть все worktrees в одном окне — используй multi-root workspace.

Создай файл ml-project.code-workspace:

{
  "folders": [
    {
      "name": "[MAIN] ml-image-classifier",
      "path": "ml-image-classifier"
    },
    {
      "name": "[EXP] LR-001",
      "path": "ml-proj-lr-001"
    },
    {
      "name": "[EXP] Transformer",
      "path": "ml-proj-transformer"
    }
  ],
  "settings": {
    "files.exclude": {
      "**/__pycache__": true,
      "**/.pytest_cache": true,
      "**/checkpoints": true
    },
    "python.analysis.extraPaths": [
      "${workspaceFolder:[MAIN] ml-image-classifier}/src"
    ]
  }
}

Открыть workspace:

cursor ml-project.code-workspace

Теперь в sidebar видны все три проекта. Можно переключаться между ними, сравнивать код, копировать файлы.

Совет: Используй префиксы в именах ([MAIN], [EXP], [DEV]), чтобы быстро различать worktrees.

Cursor Tasks для Worktree Management

Автоматизируй рутину через Cursor Tasks. Создай .cursor/tasks.json:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Worktree: Create",
      "type": "shell",
      "command": "git worktree add -b ${input:branchName} ../${input:folderName} main",
      "problemMatcher": [],
      "presentation": {
        "reveal": "always",
        "panel": "new"
      }
    },
    {
      "label": "Worktree: Remove",
      "type": "shell",
      "command": "git worktree remove ../${input:folderName}",
      "problemMatcher": []
    },
    {
      "label": "Worktree: List",
      "type": "shell",
      "command": "git worktree list",
      "problemMatcher": [],
      "presentation": {
        "reveal": "always"
      }
    },
    {
      "label": "Worktree: Train",
      "type": "shell",
      "command": "cd ../${input:folderName} && source .venv/bin/activate && python train.py",
      "problemMatcher": [],
      "presentation": {
        "reveal": "always",
        "panel": "dedicated"
      },
      "isBackground": true
    },
    {
      "label": "Worktree: Cleanup",
      "type": "shell",
      "command": "git worktree prune -v",
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "branchName",
      "type": "promptString",
      "description": "Branch name (e.g., experiment/lr-001)"
    },
    {
      "id": "folderName",
      "type": "promptString",
      "description": "Folder name (e.g., ml-proj-lr-001)"
    }
  ]
}

Использование:

  1. Cmd/Ctrl + Shift + P → "Tasks: Run Task"

  2. Выбери Worktree: Create

  3. Введи имя ветки и папки

  4. Done!

Настройки Python Interpreter per Worktree

Каждый worktree может иметь свой Python interpreter. Cursor подхватывает это автоматически.

В каждом worktree создай .vscode/settings.json:

{
  "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
  "python.terminal.activateEnvironment": true,
  "python.analysis.extraPaths": ["${workspaceFolder}/src"]
}

Или для conda:

{
  "python.defaultInterpreterPath": "~/miniconda3/envs/ml-proj-py311/bin/python",
  "python.condaPath": "~/miniconda3/bin/conda"
}

Важно: .vscode/ можно добавить в .gitignore, чтобы настройки были локальными для каждого worktree.

Terminal Profiles для разных Worktrees

Настрой разные terminal profiles для быстрого переключения:

В settings.json (глобальном или workspace):

{
  "terminal.integrated.profiles.linux": {
    "ML Main": {
      "path": "/bin/bash",
      "args": ["-c", "cd ~/projects/ml-image-classifier && source .venv/bin/activate && exec bash"]
    },
    "ML Experiment": {
      "path": "/bin/bash", 
      "args": ["-c", "cd ~/projects/ml-proj-lr-001 && source .venv/bin/activate && exec bash"]
    }
  },
  "terminal.integrated.defaultProfile.linux": "ML Main"
}

Теперь при создании нового терминала можно выбрать профиль — и сразу оказаться в нужном worktree с активированным environment.


Часть 5: Продвинутые техники

Для тех, кто хочет выжать из worktree максимум.

Bare Repository Pattern

Обычно у тебя есть «основной» репозиторий с checked out веткой (обычно main), и worktrees — дополнительные. Но есть альтернативный подход: bare repository.

Что это: Репозиторий без рабочей директории. Только .git содержимое.

Зачем: Все ветки равноправны. Нет «главного» worktree. Чище структура.

Setup:

# Клонируем как bare
git clone --bare git@github.com:user/ml-image-classifier.git ml-image-classifier.git

# Переходим внутрь
cd ml-image-classifier.git

# Создаём worktrees для нужных веток
git worktree add ../ml-main main
git worktree add ../ml-dev develop
git worktree add ../ml-experiment experiment/transformer

Структура:

~/projects/
├── ml-image-classifier.git/   # Bare repo (только .git)
├── ml-main/                   # worktree → main
├── ml-dev/                    # worktree → develop  
└── ml-experiment/             # worktree → experiment/transformer

Когда использовать:

  • Работаешь с несколькими долгоживущими ветками одновременно

  • Не хочешь иметь "главный" worktree

  • Нужна чёткая организация проекта

Когда НЕ использовать:

  • Простой проект с одной основной веткой

  • Временные эксперименты (обычный подход проще)


Worktree + tmux/screen для долгих тренировок

Тренировка на 12 часов? SSH-соединение может оборваться. Решение: tmux или screen.

Базовый workflow:

# Создаём tmux сессию для эксперимента
tmux new-session -d -s exp-lr-001

# Отправляем команды в сессию
tmux send-keys -t exp-lr-001 "cd ~/projects/ml-proj-lr-001" Enter
tmux send-keys -t exp-lr-001 "source .venv/bin/activate" Enter
tmux send-keys -t exp-lr-001 "python train.py --lr 0.001" Enter

# Можно отключиться — тренировка продолжится

Скрипт для запуска эксперимента:

#!/bin/bash
# run_experiment.sh

WORKTREE_PATH=$1
SESSION_NAME=$2
TRAIN_ARGS=$3

# Создаём сессию
tmux new-session -d -s "$SESSION_NAME"

# Запускаем тренировку
tmux send-keys -t "$SESSION_NAME" "cd $WORKTREE_PATH && source .venv/bin/activate && python train.py $TRAIN_ARGS" Enter

echo "Experiment started in tmux session: $SESSION_NAME"
echo "Attach with: tmux attach -t $SESSION_NAME"

Использование:

./run_experiment.sh ~/projects/ml-proj-lr-001 exp-lr-001 "--lr 0.001 --epochs 100"
./run_experiment.sh ~/projects/ml-proj-lr-0001 exp-lr-0001 "--lr 0.0001 --epochs 100"

# Проверить статус
tmux list-sessions

# Подключиться к сессии
tmux attach -t exp-lr-001

Shell Aliases и Functions

Добавь в ~/.bashrc или ~/.zshrc:

# Быстрое создание worktree для эксперимента
wt-exp() {
    local name=$1
    local base=${2:-main}
    git worktree add -b "experiment/$name" "../exp-$name" "$base"
    cd "../exp-$name"
    echo "Created worktree: experiment/$name"
}

# Быстрое создание worktree для фичи
wt-feature() {
    local name=$1
    local base=${2:-main}
    git worktree add -b "feature/$name" "../feature-$name" "$base"
    cd "../feature-$name"
}

# Удаление worktree с подтверждением
wt-remove() {
    local path=$1
    echo "Removing worktree: $path"
    read -p "Are you sure? (y/n) " -n 1 -r
    echo
    if [[ $REPLY =~ ^[Yy]$ ]]; then
        git worktree remove "$path"
        echo "Removed!"
    fi
}

# Список worktrees с красивым выводом
wt-list() {
    git worktree list | while read -r line; do
        path=$(echo "$line" | awk '{print $1}')
        branch=$(echo "$line" | grep -o '\[.*\]')
        echo "  $path $branch"
    done
}

# Переход в worktree по имени ветки
wt-cd() {
    local branch=$1
    local path=$(git worktree list | grep "\[$branch\]" | awk '{print $1}')
    if [ -n "$path" ]; then
        cd "$path"
        echo "Switched to: $path"
    else
        echo "Worktree for branch '$branch' not found"
    fi
}

Примеры использования:

wt-exp transformer-v2        # Создаёт experiment/transformer-v2
wt-feature new-dataloader    # Создаёт feature/new-dataloader
wt-list                      # Красивый список
wt-cd experiment/lr-001      # Переход в worktree по ветке
wt-remove ../exp-old         # Удаление с подтверждением

Cleanup: когда и как удалять Worktrees

Worktrees накапливаются. Вот стратегии cleanup:

1. Ручная проверка раз в неделю:

git worktree list
# Удаляем ненужные
git worktree remove ../old-experiment

2. Автоматический prune:

# Удаляет записи о worktrees, папки которых уже удалены
git worktree prune

# С verbose выводом
git worktree prune -v

3. Скрипт для очистки старых worktrees:

#!/bin/bash
# cleanup_worktrees.sh

DAYS_OLD=${1:-7}  # По умолчанию старше 7 дней

echo "Worktrees older than $DAYS_OLD days:"

git worktree list --porcelain | grep "^worktree" | cut -d' ' -f2 | while read -r path; do
    if [ -d "$path" ]; then
        # Проверяем дату последней модификации
        last_mod=$(find "$path" -type f -name "*.py" -mtime -"$DAYS_OLD" | head -1)
        if [ -z "$last_mod" ]; then
            echo "  OLD: $path"
        fi
    fi
done

echo ""
echo "Run 'git worktree remove <path>' to remove"

4. Git hooks для напоминания:

Добавь в .git/hooks/post-checkout:

#!/bin/bash
# Напоминание о cleanup каждые 10 checkouts

COUNT_FILE=".git/worktree_check_count"
count=$(cat "$COUNT_FILE" 2>/dev/null || echo 0)
count=$((count + 1))

if [ $count -ge 10 ]; then
    echo ""
    echo ">>> Time to cleanup worktrees?"
    git worktree list
    echo 0 > "$COUNT_FILE"
else
    echo $count > "$COUNT_FILE"
fi

Заключение

Ключевые Takeaways

  1. Worktree ≠ clone. Один .git, несколько рабочих директорий. Экономия места и автоматическая синхронизация веток.

  2. Для ML — must have. Долгие тренировки, параллельные эксперименты, изоляция environments — worktree решает все эти проблемы.

  3. Одна ветка = один worktree. Это ограничение, но оно защищает от хаоса.

  4. Симлинки — твой друг. Большие датасеты не нужно копировать. Один симлинк — и данные доступны во всех worktrees.

  5. Cursor + worktree = мощь. Multi-root workspaces, tasks, отдельные interpreters — всё работает из коробки.

  6. Автоматизируй. Shell aliases, Cursor tasks, tmux скрипты — один раз настроил, потом экономишь часы.

  7. Не забывай cleanup. Worktrees накапливаются. git worktree prune — твой друг.


Quick Reference

# ═══════════════════════════════════════════════════════
#                    GIT WORKTREE CHEATSHEET
# ═══════════════════════════════════════════════════════

# СОЗДАНИЕ
git worktree add <path> <branch>              # Существующая ветка
git worktree add -b <new-branch> <path> main  # Новая ветка от main

# ПРОСМОТР
git worktree list                             # Список всех worktrees
git worktree list --porcelain                 # Машиночитаемый формат

# УДАЛЕНИЕ
git worktree remove <path>                    # Удалить worktree
git worktree remove -f <path>                 # Принудительно (есть изменения)

# ОЧИСТКА
git worktree prune                            # Удалить мёртвые записи
git worktree prune -v                         # С подробностями

# БЛОКИРОВКА (защита от случайного удаления)
git worktree lock <path>                      # Заблокировать
git worktree unlock <path>                    # Разблокировать

# ВОССТАНОВЛЕНИЕ
git worktree repair                           # Починить сломанные связи

# ═══════════════════════════════════════════════════════
#                    ТИПИЧНЫЕ СЦЕНАРИИ
# ═══════════════════════════════════════════════════════

# Эксперимент с гиперпараметрами
git worktree add -b experiment/lr-001 ../exp-lr-001 main
cd ../exp-lr-001 && python train.py --lr 0.001

# Code review
git fetch origin
git worktree add ../review-pr-42 origin/feature/new-model
cursor ../review-pr-42

# Hotfix без остановки тренировки
git worktree add ../hotfix main
cd ../hotfix && # fix, commit, push
git worktree remove ../hotfix

Попробуй сейчас

Хватит читать — пора действовать. Вот план на ближайшие 10 минут:

Шаг 1: Открой свой ML-проект

cd ~/your-ml-project

Шаг 2: Создай первый worktree

git worktree add -b experiment/test-worktree ../my-first-worktree main

Шаг 3: Открой его в Cursor

cursor ../my-first-worktree

Шаг 4: Убедись, что всё работает

git worktree list

Шаг 5: Удали тестовый worktree

git worktree remove ../my-first-worktree
git branch -d experiment/test-worktree

Готово. Теперь ты знаешь, как это работает.

В следующий раз, когда коллега попросит «срочно глянуть PR» посреди 8-часовой тренировки — ты знаешь что делать.


Если статья была полезной — поделись с коллегами. Чем больше людей знают про worktree, тем меньше убитых экспериментов в мире.