6. Продвинутые операции
Цель темы: уверенно использовать stash, cherry-pick, интерактивный rebase, bisect и blame для временного сохранения изменений, точечного переноса коммитов, «причёсывания» истории, поиска регрессий и аудита кода.
Определения терминов
Stash (заначка)
Stash — временное хранилище незакоммиченных изменений (рабочая директория и индекс). Позволяет «спрятать» правки, переключиться на другую ветку или подтянуть изменения, а затем вернуть их обратно. Изменения хранятся в виде коммитов в .git/refs/stash.
Cherry-pick
Cherry-pick — применение изменений из одного указанного коммита другой ветки к текущей ветке. Создаётся новый коммит с тем же содержимым, но другим хешем и родителем. Удобно для точечного переноса фикса или фичи без полного merge ветки.
Интерактивный rebase
Интерактивный rebase (git rebase -i) — перебазирование с возможностью редактировать список коммитов: менять порядок, объединять (squash/fixup), менять сообщения (reword), удалять (drop). Используется для «причёсывания» истории перед отправкой в общую ветку.
Bisect (бинарный поиск)
Bisect — инструмент бинарного поиска коммита, в котором появился баг. Вы задаёте «хороший» и «плохой» коммит; Git по очереди подставляет коммиты посередине, вы помечаете их good/bad, пока не будет найден первый «плохой» коммит.
Blame
Blame — показ по каждой строке файла: в каком коммите и кем она была последний раз изменена. Используется для аудита и понимания истории правок.
Stash: временно спрятать изменения
Сохранить изменения в stash
git stash
# или с сообщением:
git stash push -m "WIP: форму логина"
По умолчанию в stash попадают только изменения в отслеживаемых файлах (уже известных Git). Чтобы включить неотслеживаемые и игнорируемые (осторожно с секретами):
git stash -u # неотслеживаемые
git stash -a # неотслеживаемые + игнорируемые
Посмотреть список stash
git stash list
# stash@{0}: WIP on main: abc1234 Add readme
# stash@{1}: On feature: WIP: форму логина
Вернуть изменения из stash
git stash pop # применить последний stash и удалить его из списка
git stash apply # применить и оставить запись в stash (можно применить снова)
git stash apply stash@{1} # применить конкретную запись
При конфликтах pop не удалит запись, пока вы не разрешите конфликт; после разрешения можно выполнить git stash drop.
Удалить запись stash
git stash drop stash@{0}
git stash clear # удалить все
Практика
Stash удобен для быстрого переключения контекста («подтянуть main», «посмотреть другую ветку»). Для долгой работы лучше закоммитить в ветку или создать временную ветку — stash легко забыть.
Cherry-pick: применить один коммит
Базовое использование
Переключитесь на ветку, в которую хотите перенести коммит, затем:
git checkout main
git cherry-pick abc1234
Создаётся новый коммит на main с теми же изменениями, что и коммит abc1234 (например, с другой ветки).
Несколько коммитов
git cherry-pick abc1234 def5678
# или диапазон (не включая первый коммит):
git cherry-pick abc1234..def5678
При конфликтах
Разрешите конфликты в файлах, затем:
git add .
git cherry-pick --continue
Отменить cherry-pick в процессе:
git cherry-pick --abort
Без автоматического коммита
git cherry-pick -n abc1234
# или
git cherry-pick --no-commit abc1234
Изменения попадут в рабочую директорию и индекс; коммит нужно создать вручную.
Пример
Фикс сделали в ветке feature/x, но нужно срочно внедрить его в main без полного merge ветки. Переключаетесь на main и делаете git cherry-pick <hash-фикса>.
Интерактивный rebase
Запуск
git rebase -i HEAD~3
# или до конкретного коммита (не включая его):
git rebase -i abc1234
Откроется редактор со списком коммитов и действиями по умолчанию pick.
Действия в списке
| Команда | Описание |
|---|---|
pick |
Оставить коммит как есть |
reword |
Оставить коммит, но изменить сообщение |
squash |
Объединить с предыдущим коммитом, добавить сообщение |
fixup |
Объединить с предыдущим, сообщение текущего отбросить |
drop |
Удалить коммит |
Пример: объединить последние два коммита в один:
pick abc1234 Первый коммит
fixup def5678 Второй коммит (войдёт в первый без отдельного сообщения)
Или переименовать последний коммит:
reword def5678 Fix typo
После редактирования
Сохраните и закройте редактор. Git выполнит rebase. При конфликтах:
# разрешить конфликты в файлах
git add .
git rebase --continue
# или отменить:
git rebase --abort
Внимание
Интерактивный rebase переписывает историю (меняются хеши). Применяйте только к коммитам, которые ещё не запушены в общую ветку, или в личной ветке перед созданием merge request.
Bisect: поиск «плохого» коммита
Запуск
git bisect start
git bisect bad HEAD # текущий коммит — с багом
git bisect good v1.0 # тег или хеш — точно без бага
Git переключит репозиторий на коммит посередине. Проверьте поведение (сборка, тест, ручная проверка) и отметьте:
git bisect good # бага здесь нет
# или
git bisect bad # баг уже есть
Повторяйте, пока Git не укажет первый «плохой» коммит. Завершение:
git bisect reset
Автоматизация
Если есть скрипт или команда, которая возвращает 0 при «хорошем» и не 0 при «плохом» состоянии:
git bisect start HEAD v1.0
git bisect run ./test-script.sh
git bisect reset
Production
Bisect полезен при регрессии: «после обновления что-то сломалось». Задаёте последний известный хороший коммит (или тег) и текущий плохой — Git сужает круг до одного коммита.
Blame: кто менял строки
По файлу целиком
git blame readme.md
Для каждой строки выводится: хеш коммита, автор, дата, содержимое строки.
По диапазону строк
git blame -L 10,20 src/main.py
Удобный формат (короткий хеш, дата)
git blame -s -L 10,30 src/main.py
Опции -C, -M помогают отслеживать перемещённый или скопированный код между файлами.
Пример
Нашли баг в функции. git blame -L 50,80 src/handler.py покажет, в каком коммите и кем были изменены эти строки — можно открыть коммит и понять контекст.
Паттерны использования
| Паттерн | Описание |
|---|---|
| Stash для краткого переключения | Спрятать правки, переключиться на другую ветку или подтянуть main, затем вернуть. |
| Cherry-pick точечно | Переносить один-два коммита (фикс, хотфикс) без merge целой ветки. |
| Интерактивный rebase перед MR | «Причесать» историю: объединить WIP-коммиты, поправить сообщения перед отправкой на ревью. |
| Bisect при регрессии | Когда есть чёткий «хороший» и «плохой» коммит — быстро найти вводящий баг коммит. |
| Blame для аудита | Понять, кто и когда менял участок кода при расследовании бага или ревью. |
Антипаттерны
| Антипаттерн | Почему плохо | Что делать |
|---|---|---|
| Много stash и забыть про них | Старые stash теряют контекст, конфликтуют при apply. | Использовать stash для кратких переключений; не хранить долго; подписывать сообщениями. |
| Массовый cherry-pick вместо merge | Дублирование коммитов, путаница в истории. | Cherry-pick — для единичных коммитов; для целой ветки — merge или rebase. |
| Интерактивный rebase общих веток | Переписывание истории, сломанные клоны и CI. | Rebase только своих ещё не запушенных коммитов или личной ветки. |
| Bisect без однозначного good/bad | Неточный результат, потеря времени. | Убедиться, что «хороший» коммит действительно без бага (теги релизов, известные коммиты). |
Примеры из production
Чистая история перед merge request
Перед отправкой MR делают интерактивный rebase: объединяют «WIP», «fix typo» в осмысленные коммиты, правят сообщения. Ревьюер видит логичную историю; в main потом merge с понятными коммитами.
Поиск регрессии
«На проде сломалось после деплоя вчера». Запускают bisect: good — предыдущий релизный тег, bad — HEAD. Запускают автотесты или ручную проверку на каждом шаге. Находят коммит — открывают diff, исправляют или делают revert.
Аудит и расследование
При инциденте смотрят, кто последним менял проблемный модуль: git blame, затем просмотр коммита и связанного тикета. Помогает при постмортеме и код-ревью практиках.