The Jerusalem PostAustralian lawmaker Helen Haines in critical condition after vehicle ramming attackPunchUCL: PSG face Barcelona, Bayern host Arsenal in matchweek three, other fixturesESPNA week removed from embarrassing tie, where do Commanders go from here?Inquirer‘Magnificent Seven’ pioneers reunite for historic PMA installationUN NewsProtecting Earth’s fragile biodiversity takes spotlight at Armenia talksBollywood HungamaImran Khan’s plea against ‘Leave India’ notice to be heard on October 26; Bombay High Court seeks Maharashtra government’s inclusionRTP DesportoJaime Faria sobe a 56.º do ranking mundial de ténisESPN CricinfoPowar appointed men's national selector; Ojha to serve as chairZDF heuteEntdecken Sie das ZDF-NachrichtenstudioPremium TimesBenue Catholic school relocates students over security concernsBellaNaijaSeven Women, Seven Innovations: InnovateNaija’s Role in Nigeria’s Tech StoryIl Fatto Quotidiano“Gli stranieri in Italia danno molto e ricevono poco”: il rapporto Migrantes sui confini che restano “ben oltre le frontiere”
The Daily Newsstand · Free, Always
Monday, October 19, 2026

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

Translate

Если вы работаете в стиле «одна задача — свой 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/     ← ревью чужого PR
docs/

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

Готовой лёгкой утилиты, которая отвечала бы на всё это одной командой по всей папке проектов, мне не попалось. Так и появился 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-linux
curl -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-пакет уже готов, ждал открытия регистрации (её прикрывали из-за наплыва ботов), уйдёт в репозиторий как только откроют.

Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.