Git stash, cherry-pick, revert

Git stash, cherry-pick, revert и другие инструменты

Git - это не только commit, push, pull. Есть набор команд, которые выручают в ситуациях, когда стандартный workflow не справляется: нужно срочно переключиться на другую ветку, перенести один коммит без мержа, отменить изменения в проде или восстановить то, что случайно удалил. Эти команды - не экзотика, а повседневные инструменты опытного разработчика.

Каждую из команд ниже ты будешь использовать регулярно. Разница между джуниором и мидлом часто именно в этом: мидл знает, что делать, когда что-то пошло не по плану.

git stash: спрятать изменения

git stash убирает незакоммиченные изменения во временное хранилище. Рабочая директория становится чистой, и ты можешь переключиться на другую ветку, не теряя работу.

Поток git stash: feature dirty, stash push, переключение на hotfix, потом stash pop возвращает изменения

Базовое использование

# Спрятать все изменения (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
Stash - это временное хранилище. Если изменения лежат в stash больше пары часов, лучше закоммитить их в рабочую ветку. Stash легко забыть, и через неделю `git stash list` покажет 15 записей, из которых ни одну ты не помнишь.

Типичный сценарий

Ты работаешь над фичей, и коллега просит срочно посмотреть баг на другой ветке:

# Спрятать текущую работу
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 без полного мержа
  • Потерянный коммит: случайно удалил ветку, но знаешь хеш коммита
  • Выборочный бэкпорт: перенести конкретную фичу в старую версию
Cherry-pick не перемещает коммит - он создаёт новый с тем же содержимым, но другим хешем. Если потом замержить ветку-источник, Git обычно справится. Но в сложных случаях могут появиться дубликаты изменений.

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    │ Удалён  │ Нет     │ Нет
Если ты не коммитил изменения и сделал `reset --hard` - они пропали навсегда. Для закоммиченных данных есть шанс восстановить через `reflog`, но для незакоммиченных - нет. Используй с осторожностью.

Когда что использовать

Ситуация                                       → Команда
───────────────────────────────────────────────────────────
Отменить коммит в общей ветке (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
Флаг `-x` удаляет даже то, что в [.gitignore](./10-ignore.md): `vendor/`, `node_modules/`, скомпилированные бинарники. Это может потребовать повторного `go mod download` или `npm install`. Используй `-n` для предварительного просмотра.

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 и найди предыдущие операции

Зарегистрируйтесь бесплатно, чтобы пройти квиз, решить задание с автопроверкой и вести прогресс.