Git stash, cherry-pick, revert
Git stash, cherry-pick, revert и другие инструменты
Git - это не только commit, push, pull. Есть набор команд, которые выручают в ситуациях, когда стандартный workflow не справляется: нужно срочно переключиться на другую ветку, перенести один коммит без мержа, отменить изменения в проде или восстановить то, что случайно удалил. Эти команды - не экзотика, а повседневные инструменты опытного разработчика.
Каждую из команд ниже ты будешь использовать регулярно. Разница между джуниором и мидлом часто именно в этом: мидл знает, что делать, когда что-то пошло не по плану.
git stash: спрятать изменения
git stash убирает незакоммиченные изменения во временное хранилище. Рабочая директория становится чистой, и ты можешь переключиться на другую ветку, не теряя работу.
Базовое использование
# Спрятать все изменения (tracked файлы)
git stash
# Спрятать с сообщением (рекомендуется)
git stash push -m "WIP: обработка ошибок в auth handler"
# Спрятать включая untracked файлы
git stash push -u -m "WIP: новый endpoint + миграция"
# Спрятать конкретные файлы
git stash push -m "only handler" backend/handler/auth.go
Список и восстановление
# Список всех stash-записей
git stash list
# stash@{0}: On feature/auth: WIP: обработка ошибок в auth handler
# stash@{1}: On develop: WIP: новый endpoint + миграция
# Применить последний stash (оставить в списке)
git stash apply
# Применить последний stash (удалить из списка)
git stash pop
# Применить конкретный stash
git stash apply stash@{1}
# Посмотреть содержимое stash без применения
git stash show stash@{0}
git stash show -p stash@{0} # с полным diff
apply vs pop
Разница принципиальная:
git stash apply # применяет изменения, stash остаётся в списке
git stash pop # применяет изменения, stash удаляется из списка
Используй apply, если не уверен, что изменения применятся без конфликтов. Если возникнет конфликт при pop, stash всё равно останется в списке (Git не удаляет его при конфликте), но лучше подстраховаться.
Очистка
# Удалить конкретный stash
git stash drop stash@{1}
# Удалить все stash-записи
git stash clear
Типичный сценарий
Ты работаешь над фичей, и коллега просит срочно посмотреть баг на другой ветке:
# Спрятать текущую работу
git stash push -m "WIP: feature/FL-200 progress API"
# Переключиться и проверить баг
git checkout fix/FL-195-login-bug
# ... посмотрел, помог, вернулся
# Вернуться к работе
git checkout feature/FL-200-progress
git stash pop
git cherry-pick: перенос коммитов
cherry-pick берёт один (или несколько) коммитов из любой ветки и применяет их в текущую. Это не мерж - переносится содержимое конкретных коммитов.
Один коммит
# Найти хеш нужного коммита
git log --oneline feature/auth
# a1b2c3d Fix: корректная валидация email ← нужен этот
# d4e5f6g Add: email validation
# h7i8j9k Initial auth handler
# Перенести в текущую ветку
git cherry-pick a1b2c3d
Диапазон коммитов
# Перенести несколько коммитов (от..до, не включая первый)
git cherry-pick d4e5f6g..a1b2c3d
# Перенести несколько коммитов (включая оба)
git cherry-pick d4e5f6g^..a1b2c3d
Cherry-pick с конфликтами
Если cherry-pick вызывает конфликт, разрешается он ровно так же, как при merge и rebase:
git cherry-pick a1b2c3d
# CONFLICT (content): Merge conflict in backend/handler/auth.go
# Варианты:
# 1. Решить конфликт вручную
vim backend/handler/auth.go
git add backend/handler/auth.go
git cherry-pick --continue
# 2. Отменить cherry-pick
git cherry-pick --abort
Когда использовать
- Хотфикс: баг исправлен в develop, нужно перенести фикс в main без полного мержа
- Потерянный коммит: случайно удалил ветку, но знаешь хеш коммита
- Выборочный бэкпорт: перенести конкретную фичу в старую версию
git revert: безопасная отмена
revert создаёт новый коммит, который отменяет изменения указанного коммита. История не переписывается - это безопасно для командной работы.
Отмена одного коммита
# Отменить последний коммит
git revert HEAD
# Отменить конкретный коммит
git revert a1b2c3d
# Отменить без автоматического коммита (чтобы поправить сообщение или объединить с другими изменениями)
git revert --no-commit a1b2c3d
Отмена merge-коммита
Merge-коммит имеет двух родителей, и Git не знает, относительно какого отменять. Нужно указать -m:
# Отменить мерж, оставив изменения из main (parent 1)
git revert -m 1 <merge-commit-hash>
# -m 1 означает: отменить то, что пришло из замерженной ветки
# -m 2 означает: отменить то, что было в принимающей ветке
В 99% случаев нужен -m 1 - «отменить то, что влили».
git reset: перемотка истории
В отличие от revert, reset перемещает HEAD назад и может удалить коммиты из истории. Есть три режима:
# --soft: убирает коммит, но изменения остаются в staging (index)
git reset --soft HEAD~1
# --mixed (по умолчанию): убирает коммит, изменения в working directory
git reset HEAD~1
# --hard: убирает коммит И все изменения. Навсегда.
git reset --hard HEAD~1
Сравнение:
Режим │ Коммит │ Staging │ Working Dir
──────────┼─────────┼─────────┼────────────
--soft │ Удалён │ Есть │ Есть
--mixed │ Удалён │ Нет │ Есть
--hard │ Удалён │ Нет │ Нет
Когда что использовать
Ситуация → Команда
───────────────────────────────────────────────────────────
Отменить коммит в общей ветке (main/develop) → git revert
Отменить последний коммит в своей ветке → git reset --soft HEAD~1
Полностью откатить ветку до состояния remote → git reset --hard origin/develop
Убрать файл из staging → git reset HEAD file.go
git clean: удаление untracked файлов
git clean удаляет файлы, которые Git не отслеживает. Полезно после сборки или эксперимента, который оставил мусор.
# Показать, что будет удалено (dry run)
git clean -n
# Удалить untracked файлы
git clean -f
# Удалить untracked файлы и директории
git clean -fd
# Удалить включая файлы из .gitignore (бинарники, кэши)
git clean -fdx
git reflog: страховочная сеть
reflog - это журнал всех перемещений HEAD. Даже если ты сделал reset --hard или удалил ветку, коммиты какое-то время хранятся в reflog.
git reflog
# a1b2c3d HEAD@{0}: reset: moving to HEAD~3
# d4e5f6g HEAD@{1}: commit: feat: add rate limiting
# h7i8j9k HEAD@{2}: commit: fix: email validation
# j1k2l3m HEAD@{3}: checkout: moving from main to feature/auth
# Восстановить состояние до reset
git reset --hard d4e5f6g
Reflog хранит записи около 90 дней. Это не замена бэкапам, но спасает в большинстве случаев. Вместе с log, blame и bisect это твой основной набор для расследований.
git worktree: несколько рабочих директорий
worktree позволяет держать несколько веток checked out одновременно - каждую в своей директории. Не нужно stash/switch, чтобы посмотреть другую ветку.
# Создать worktree для ветки hotfix
git worktree add ../project-hotfix hotfix/fix-login
# Теперь у тебя две директории:
# project/ ← основная (feature/auth)
# project-hotfix/ ← hotfix ветка
# Поработать в hotfix
cd ../project-hotfix
vim backend/handler/auth.go
git commit -am "fix: login validation"
git push origin hotfix/fix-login
# Удалить worktree, когда не нужен
cd ../project
git worktree remove ../project-hotfix
Это удобнее stash, когда нужно параллельно работать над двумя задачами или постоянно переключаться между ветками.
Мини-задание
- Измени файл, спрячь через
git stash push -m "test", убедись чтоgit statusчистый, верни черезpop - Сделай два коммита, перенеси один в другую ветку через
cherry-pick - Сделай коммит и отмени его через
git revert HEAD, посмотри историю - Попробуй
git reset --soft HEAD~1- убедись, что изменения остались в staging - Посмотри свой reflog:
git reflog --onelineи найди предыдущие операции