diff --git a/01-git/git-new-command-set-mality/Readme.md b/01-git/git-new-command-set-mality/Readme.md new file mode 100644 index 00000000..c69d8af4 --- /dev/null +++ b/01-git/git-new-command-set-mality/Readme.md @@ -0,0 +1,251 @@ +# Новый command set для git + +## Про подход + +Для начала рассмотрим базовый набор команд гит, которые используют разработчики в крупных рф компаниях(бекендеры, мл, фронт, мобильная разработка). Команды связанные с доставкой кода в фича ветку и дальнейшим созданием PR в мастер ветку. Также затроним команды которые необходимы для махинаций с ветками: rebase, merge, reset, ... + +Текущий набор команд обладает на мой взгляд достаточной "полнотой" для практически любых махинаций с деревом коммитов, чтобы получить желаемый результат. В худшем случае можно покомитно выстроить необходимую целевую структуру из любой текущей + +Поэтому предлагается рассмотреть самые частотные сценарии использования и попробовать их соптимизировать. Таким образом получим новый набор команд, которые лучше закрывают эти сценарии + +## Сценарии использования + +--- + +### 1. Начало работы над задачей — создание ветки от актуального мастера + +Разработчик берёт новый тикет и хочет создать фича-ветку от свежего состояния основной ветки. + +``` +git checkout main +git pull origin main +git checkout -b feature/TASK-123-add-user-auth +``` + +**Проблема:** Три команды для одного концептуального действия. Разработчик часто забывает сначала переключиться на main и создаёт ветку от текущей фичи — это потом вылезает при rebase. Нужно помнить имя remote. + +**Решение:** Одна команда, которая сама подтягивает базовую ветку и создаёт новую от её актуального состояния. + +**Изменения в командах:** Новая команда `git new [--from ]`. По умолчанию base — main/master (определяется автоматически). Делает fetch + создание ветки + переключение. Пример: `git new feature/TASK-123-add-user-auth`. + +--- + +### 2. Актуализация фича-ветки с мастером (rebase) + +Пока разработчик работал над задачей, в main накопились изменения коллег. Нужно обновить свою ветку. + +``` +git checkout main +git pull origin main +git checkout feature/TASK-123-add-user-auth +git rebase main +# при конфликтах: +git status +# редактируем конфликтующий файл вручную +git add src/auth/login.py +git rebase --continue +``` + +**Проблема:** Четыре команды только чтобы начать rebase. При конфликтах нет явного прогресса — непонятно сколько ещё конфликтов осталось. `--continue` / `--abort` / `--skip` неочевидны для новичков. Если конфликтов много, цикл `add -> continue` повторяется и легко запутаться в состоянии rebase. + +**Решение:** Одна команда синхронизации, которая сама обновляет base-ветку. Явный прогресс при конфликтах. + +**Изменения в командах:** +- Новая команда `git sync [base-branch]` — делает fetch base-ветки и rebase текущей ветки на неё. +- При конфликтах `git sync` делает не пошаговый ребейз каждого коммита как сейчас, в решает конфликты сразу с актуальной версией. Таким образом гораздо проще решать конфликты при большом количестве коммитов в текущую ветку + +--- + +### 3. Срочное переключение контекста с сохранением незакоммиченных изменений + +Разработчик в середине задачи — прилетает срочный баг в проде. Нужно переключиться, не потеряв незафиксированный прогресс. + +``` +git stash push -m "wip: login form validation" +git checkout hotfix/PROD-456-fix-crash +# ... правим баг ... +git checkout feature/TASK-123-add-user-auth +git stash pop +``` + +**Проблема:** `git stash` — отдельное хранилище, никак не связанное с деревом коммитов. Легко забыть про stash. `stash@{0}`, `stash@{1}` — неинтуитивные имена. При нескольких накопленных stash-ах сложно понять что в каждом без `git stash show`. + +**Решение:** Привязать stash к ветке. Сделать его частью обычного рабочего процесса. + +**Изменения в командах:** +- `git wip` автоматически помечается именем текущей ветки. +- При `git checkout `, если для неё есть stash, git предлагает: "У ветки есть сохранённые изменения, применить?". +- Не даем пушить или коммитить ветку, пока есть хотя бы один активный wip + + +--- + +### 4. Приведение истории коммитов в порядок перед PR (interactive rebase) + +Разработчик накоммитил в процессе работы много мелких коммитов ("fix", "wip", "ещё fix") и хочет сделать историю чистой перед отправкой на ревью. + +``` +git log --oneline main..HEAD +git rebase -i main +# открывается редактор с текстовым файлом: +# pick abc1234 feat: add login +# pick def5678 fix typo +# pick ghi9012 wip +# меняем pick на squash/fixup/reword вручную +# сохраняем, выходим +git push origin feature/TASK-123 --force-with-lease +``` + +**Проблема:** `git rebase -i` открывает текстовый редактор со специфическим синтаксисом, который нужно знать. Ошибка в файле — rebase ломается. Необходимость знать разницу между `--force` и `--force-with-lease`. Многие разработчики избегают interactive rebase из-за страха. + +**Решение:** Визуальный интерфейс для управления историей без ручного редактирования файла. + +**Изменения в командах:** +- `git rebase -i` получает флаг `--ui` — запускает TUI с визуальным списком коммитов, клавишами для squash/rename/reorder. Таким образом команда становится более user friendly + +--- + +### 5. Откат изменений на разных уровнях + +Разработчик допустил ошибку и хочет её отменить — незакоммиченные изменения, последний коммит, коммит в shared-ветке. + +``` +# Сценарий A — отменить изменения в файле (не закоммичены) +git restore src/auth/login.py + +# Сценарий B — убрать последний коммит, сохранив изменения +git reset HEAD~1 + +# Сценарий C — отменить коммит, который уже у других (shared ветка) +git revert abc1234 +``` + +**Проблема:** Три разные команды для концептуально одного действия "отменить". `git checkout --` vs `git restore` — две команды делают одно и то же (legacy). Разница между `reset --soft` / `--mixed` / `--hard` неочевидна. Новички путают `reset` и `revert` и получают потерянную историю или конфликты при пуше. + +**Решение:** Единая команда `git undo` с умным контекстом. + +**Изменения в командах:** +- `git undo` без аргументов — отменяет последнее действие (добавление в стейдж, коммит, merge и т.д.), определяя контекст автоматически. +- `git undo --file ` — восстанавливает файл до последнего коммита. +- `git undo --commit` — убирает последний коммит, сохраняя изменения в рабочей директории. +- `git undo --safe` — автоматически выбирает `revert` вместо `reset`, если ветка уже запушена (чтобы не сломать историю у других). + +--- + +### 6. Поиск коммита, сломавшего функциональность (bisect) + +В проде обнаружен баг. Известно, что в версии v2.1.0 его не было. Нужно найти виновный коммит среди ~200 коммитов. + +``` +git bisect start +git bisect bad HEAD +git bisect good v2.1.0 +# git переключает на середину диапазона +# разработчик вручную проверяет: воспроизводится ли баг? +git bisect good # или bad +# ... повторяем 7-8 раз ... +git bisect reset +``` + +**Проблема:** Нужно вручную проверять каждый шаг. Сессия bisect хрупкая — случайный `git checkout` ломает процесс и нет автосохранения. Нет прогресс-бара. Если есть автоматический тест воспроизводящий баг — `git bisect run` решает задачу, но об этой возможности мало кто знает. + +**Решение:** Сделать автоматизированный bisect основным флоу, улучшить UX ручного режима. + +**Изменения в командах:** +- `git bisect --test "<команда>"` — сокращение для `git bisect run`, запускает команду на каждом шаге автоматически. Пример: `git bisect --test "pytest tests/auth_test.py" --good v2.1.0`. +- `git bisect` показывает прогресс: "Шаг 4 из ~8, осталось проверить ~4 коммита". +- `git bisect --resume` — продолжение прерванной bisect-сессии. +- Состояние bisect сохраняется в `.git/bisect-session` и переживает случайные переключения веток. + +--- + +### 7. Понять что произошло с веткой (reflog) + +Разработчик что-то сломал — rebase пошёл не так, коммиты пропали, ветка выглядит не так. Нужно понять что случилось и вернуться к нормальному состоянию. + +``` +git reflog +# HEAD@{0}: rebase (finish): returning to refs/heads/feature/TASK-123 +# HEAD@{1}: rebase (pick): fix validation +# HEAD@{2}: rebase (pick): feat: add login +# HEAD@{3}: rebase (start): checkout main +# HEAD@{4}: commit: wip +# HEAD@{5}: checkout: moving from main to feature/TASK-123 +# HEAD@{6}: pull: Fast-forward +# ...ещё 200 строк глобальной истории всех веток... + +git checkout HEAD@{4} # попытка вернуться к нужному состоянию +``` + +**Проблема:** `git reflog` показывает глобальную историю всех перемещений HEAD в хронологическом порядке. Это плоский список из криптичных записей без группировки по веткам. Чтобы понять что случилось с конкретной веткой — нужно мысленно фильтровать 200 строк. Операции rebase разбиты на десятки отдельных записей "pick", "start", "finish". Существует `git reflog `, но он также слабо структурирован и мало известен. + +**Решение:** История ветки как понятный нарратив с группировкой по операциям. + +**Изменения в командах:** +- `git branch --history [branch]` — показывает историю изменений конкретной ветки, сгруппированную по операциям. Пример вывода: + ``` + [2 часа назад] rebase onto main + до: abc1234 "wip" + после: def5678 "wip" + [3 часа назад] 2 новых коммита + + ghi9012 "fix validation" + + jkl3456 "feat: add login" + [вчера] ветка создана от main (mno7890) + ``` +- `git branch --restore` — интерактивный выбор из снимков состояния ветки (без необходимости знать хэши из reflog). +- `git reflog` получает флаг `--branch ` как более очевидный способ фильтрации вместо позиционного аргумента. + +--- + +### 8. Фича-ветка, зависящая от другой ещё не влитой ветки (stacked branches) + +Разработчик работает над задачей B, которая требует функциональность из задачи A. A ещё на ревью, не влита. Разработчик создаёт ветку B поверх A. + +``` +# Структура: +# main → feature/A → feature/B (текущая) + +# Сценарий 1: коллега переписал историю feature/A (force push после ревью) +git rebase feature/A +# Конфликты — git пытается повторно применить коммиты A, которые уже есть в B + +# Сценарий 2: feature/A влита в main, нужно перебазировать B на main +git rebase main +# Конфликты — git видит коммиты A в истории B и не понимает что они уже в main + +# Правильный способ для сценария 2 (мало кто знает): +git rebase --onto main feature/A feature/B +# "Возьми коммиты из B которых нет в feature/A, перенеси на main" +``` + +**Проблема:** При переписывании истории A — B теряет связь с ней, автоматического rebase нет, конфликты неизбежны. При слиянии A в main — обычный `git rebase main` тащит коммиты A снова, нужен `--onto` с явным указанием старой базы, что неочевидно. + +Мержи вместо ребейзов **не решают проблему**: если сделать `merge feature/A → feature/B`, а потом A вольётся в main, то при `merge main → feature/B` конфликты будут те же самые — просто они возникнут в merge-коммите, а не при rebase. Плюс история станет ещё запутаннее из-за merge-коммитов. + +Современные инструменты (Graphite, ghstack, git-branchless) решают это через явное отслеживание стека веток, но это внешние утилиты. + +**Решение:** Встроенная концепция стека веток — git знает о зависимости B от A и автоматически обновляет всю цепочку. + +**Изменения в командах:** +- `git branch --depends-on feature/A` — явно объявляет зависимость текущей ветки. Git сохраняет это в `.git/config` или отдельном файле стека. +- `git sync` (из сценария 3) при наличии зависимости автоматически делает rebase всей цепочки: сначала обновляет A от main, потом переносит B через `--onto` на новую A. +- `git rebase --onto` получает более читаемый синтаксис: `git rebase --onto main --drop feature/A` — "перебазировать на main, считая что коммиты из feature/A уже там есть". +- `git log --stack` — визуализация стека: какие ветки на что опираются. + +--- + +## Итоговый набор изменений + +| Команда | Что делает | Зачем | +|---|---|---| +| `git new [--from ]` | fetch + создание ветки + переключение одной командой | Три команды сейчас — часто забывают переключиться на main и создают ветку от текущей фичи | +| `git sync [base-branch]` | fetch base + rebase текущей ветки; конфликты решаются один раз против финального состояния base | Четыре команды для старта rebase; пошаговые конфликты при большом числе коммитов | +| `git wip` | stash привязанный к текущей ветке; при `checkout` git предлагает применить; запрет пуша при активном wip | Глобальный безымянный stash-стек легко потерять или перепутать | +| `git rebase -i --ui` | TUI с визуальным списком коммитов вместо текстового файла | Синтаксис файла rebase пугает; ошибка в нём ломает процесс | +| `git undo [--file ] [--commit] [--safe]` | единая команда отмены по контексту; `--safe` выбирает `revert` вместо `reset` для запушенных веток | Три разные команды (`restore`, `reset`, `revert`) для одного действия; новички путаются и теряют историю | +| `git bisect --test "" --good ` | делает `bisect run` основным флоу; прогресс-бар; сессия переживает случайные `checkout` | Автоматический режим мало кто знает; ручной режим хрупкий | +| `git branch --history [branch]` | история ветки сгруппированная по операциям (rebase, коммиты, создание) | `git reflog` выдаёт 200+ строк для всех веток разом | +| `git branch --restore` | интерактивный выбор снимка состояния ветки без хэшей | Возврат через `HEAD@{N}` требует ручного поиска по reflog | +| `git branch --depends-on ` | явная декларация зависимости ветки; `git sync` автоматически обновляет всю цепочку | Нет встроенного способа работать со стеком веток; `rebase --onto` с тремя позиционными аргументами неочевиден | +| `git rebase --onto main --drop ` | читаемый синтаксис для `--onto`: "перебазировать, считая что коммиты из ветки уже в base" | Классический `--onto` требует знания трёх позиционных аргументов | +| `git log --stack` | визуализация дерева зависимостей между ветками | Нет встроенного способа увидеть stacked branches |