lsw: одна команда, чтобы увидеть все свои git worktree

—

от автора

Если вы работаете в стиле «одна задача — свой worktree», у вас наверняка скопилась папка, где рядом лежат полдюжины чекаутов одного проекта. Я в какой-то момент устал бегать по ним с git status и написал lsw — это «ls» для worktree. Одна команда показывает все репозитории и worktree вокруг: ветку каждого, есть ли незакоммиченные правки, насколько ветка ушла от origin. Read-only, без зависимостей, один бинарник.

Откуда это взялось

Напомню на всякий случай, для тех кто не пользуется: git worktree add ../webapp-1042 feature/TASK-1042 создаёт второй рабочий каталог того же репозитория. Две ветки чекнуты одновременно, никаких стэшей и git checkout-каруселей: в одной папке чините прод, в другой спокойно пишете фичу. Штука недооценённая, я почти всю разработку так и веду — каждая задача в своём каталоге. Через месяц рядом с проектом скапливается примерно такое:

webapp/            ← main, главный клонwebapp-1042/       ← фича TASK-1042, в ней сейчас работаюwebapp-hotfix/     ← а тут висят незакоммиченные правкиwebapp-review/     ← ревью чужого PRdocs/

И каждый раз, возвращаясь к проекту, я прохожу один и тот же квест: в какой папке я вчера остановился, где висят незакоммиченные правки, какая ветка уехала вперёд от origin, а какая отстала, и вообще жива ли ещё та remote-ветка. Руками это cd по всем каталогам плюс git status в каждом. С двумя терпимо, с пятью — начинает бесить. git worktree list помогает частично: он работает только внутри одного репозитория и ничего не знает ни про dirty-состояние, ни про ahead/behind.

Готовой лёгкой утилиты, которая отвечала бы на всё это одной командой по всей папке проектов, мне не попалось. Так и появился lsw.

Что показывает

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

lsw — древовидный вывод

lsw — древовидный вывод

Внутри репозитория lsw показывает его worktree, внутри обычной папки — сканирует репозитории на уровень вглубь (-2 и -3 — глубже). Для каждого worktree — ветка, грязность и дистанция до upstream.

Токен

Значение

./repo · host/user/repo

заголовок группы: путь и origin в компактном виде

* (зелёный)

worktree, в котором вы сейчас находитесь

N dirty

незакоммиченные изменения (жёлтым)

↑N

ветка впереди upstream на N коммитов (циан)

↓N

позади upstream на N (красный)

no upstream

у ветки нет remote tracking

upstream gone

remote-ветка удалена

bare

bare-репозиторий

Флаг -b переключает на карточки, там добавляются remote tracking branch и последний коммит:

lsw -b — блочный вывод

lsw -b — блочный вывод

А -bp расписывает грязное состояние пофайлово, с +добавлено/−удалено:

lsw -bp — пофайловая детализация

lsw -bp — пофайловая детализация

Чего в lsw намеренно нет

В нём нет нескольких вещей. Состояния, конфигов, демона — lsw только читает, в ваши репозитории не пишет никогда. Сторонних крейтов — только Rust std и тот самый git, который у вас уже стоит; ни libgit2, ни OpenSSL, аудитить нечего, один статический бинарник на ~1 МБ. И управления worktree — создание, удаление, prune я оставил git worktree, он с этим и так справляется, дублировать смысла нет.

Весь ввод-вывод сводится к одному git worktree list на репозиторий и одному git status на worktree, дальше только разбор текста. Я писал это под omarchy и старался держать в духе: маленькие острые тулзы, которые стыкуются друг с другом. Цветной вывод включается только в TTY, в пайпе текст чистый — так что lsw | grep dirty и lsw | fzf работают как надо. NO_COLOR и NO_ICONS уважаются, а без Nerd Font иконки деградируют в ASCII (или можно --no-icons принудительно).

Под капотом

Чистый Rust: около двух тысяч строк вместе с тестами, ноль сторонних крейтов, 53 теста. Собирается на Rust 1.70+ плюс системный git.

Скан каталогов

Обход идёт по уровням (по умолчанию один, -2/-3 — глубже); скрытые каталоги, node_modules и target не трогаем. Узлы различаются по .git: каталог с .git/ внутри — репозиторий, .git-файл — linked worktree (именно так git оформляет связанные чекауты: файл с gitdir:-ссылкой). На каждый найденный репозиторий — один git worktree list, который сразу отдаёт все его worktree, включая bare.

Дальше начинается интересное. Worktree, вложенные внутрь каталога репозитория (классическая раскладка repo/{main,wt1,wt2}), попадают под его заголовок и не дублируются. Worktree-сиблинги, лежащие рядом с репозиторием, получают собственные группы.

Как я поймал баг

Разделить эти два случая с первого раза не получилось. До v0.1.3 дедуп в скане выкидывал worktree, чей «настоящий» корень уже встречался в списке. Для вложенных worktree логика «уже показан этим репозиторием» работала, но сиблинг, встретившийся после своего репозитория по алфавиту, тоже выкидывался — а рендерил его только репозиторий, в чьё поддерево сиблинг не попадал. В итоге две папки просто исчезали из выдачи. Причём результат зависел от порядка обхода: another_tasks (a < c) выживал, а supports и support_tasks (s > c) — нет.

Починилось одной строкой условия: дедуп применяем только к worktree, вложенным внутрь каталога уже показанного репозитория. Писал по TDD — сначала два падающих теста (репро реальной раскладки и проверка независимости от сортировки), потом фикс, потом все 53 зелёные. Докстринги врут даже в коде на несколько сотен строк, я это ещё раз проверил.

Состояние и деградации

Dirty-счётчик и ↑/↓ берутся из одного git status на worktree; -bp разворачивает полный список файлов. Stale-worktree, у которых .git указывает на исчезнувший репозиторий, пропускаются молча, а --debug покажет на stderr, что и почему пропущено. Когда ничего не найдено, lsw не молчит, а печатает короткую заметку: где искал, на какой глубине, сколько каталогов было нечитаемо — и выходит с кодом 0.

Установка

# инсталлятор: prebuilt-бинарник последнего релиза, x86_64-linuxcurl -fsSL https://raw.githubusercontent.com/apilot/lsw/main/install.sh | sh# или сборка из исходников (любая платформа с Rust и git)cargo install --git https://github.com/apilot/lsw

Скрипт проверяет sha256 скачанного бинарника и кладёт его в ~/.local/bin (перезадаётся через LSW_PREFIX). Работает на любой ОС, где в PATH есть git.

Вместо заключения

Утилита маленькая и специально ограниченная: она не управляет worktree, а просто делает видимым то, что git уже знает. Мне она заменила утренний обход папок одной командой — может, и вам пригодится.

GitHub: apilot/lsw — issue и PR жду, особенно раскладки worktree-хозяйства, которые скан обрабатывает неправильно: таких layout’ов столько, сколько у людей привычек. AUR-пакет уже готов, ждал открытия регистрации (её прикрывали из-за наплыва ботов), уйдёт в репозиторий как только откроют.

ссылка на оригинал статьи https://habr.com/ru/articles/1096168/