Перейти к содержанию

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, затем просмотр коммита и связанного тикета. Помогает при постмортеме и код-ревью практиках.


Дополнительные материалы