5 команд Git, выходящих за рамки commit и push
Пять команд Git beyond commit and push: stash, reflog, cherry-pick, rebase -i и bisect для восстановления, очистки истории и поиска багов.
Большинство разработчиков живёт в add, commit, push и pull, но настоящая сила Git — в командах восстановления, редактирования истории и отладки, до которых ежедневный цикл работы просто не доходит.
С остальными командами обычно знакомишься не самым приятным способом. reset --hard съедает результат работы за полдня, или где-то среди 200 коммитов затесался баг, и никто не может сказать, какой именно из них всё сломал. Пять команд ниже решают задачи, которые не по силам базовому набору: отложить незаконченную работу, восстановить коммиты, которые вы считали потерянными, перенести одно исправление между ветками, привести историю в порядок перед PR и точно определить коммит, породивший баг. Для каждой описан мысленный триггер, точный синтаксис и то единственное «но», на котором все спотыкаются.
Ключевые выводы
git stashоткладывает незаконченную работу и возвращает чистое рабочее дерево, позволяя переключить контекст без одноразового коммита.git reflogфиксирует каждую позицию, на которую указывал HEAD, поэтому после неудачного reset или rebase вы можете найти хеш потерянного коммита и восстановить его черезgit reset --hard <ref>.git cherry-pick <hash>копирует один коммит в текущую ветку — идеальный вариант, когда нужно перенести один hotfix в релизную ветку, не подтягивая всё остальное.- Команды, переписывающие историю, такие как
git rebase -iиgit reset --hard, безопасны для локальной, ещё не отправленной работы и опасны для общих веток;git reflog— ваш путь к восстановлению, если что-то пошло не так. git bisectвыполняет двоичный поиск по истории и находит коммит, породивший баг, за log₂(n) шагов.
Discover how at OpenReplay.com.
git stash: отложить работу, чтобы быстро переключить контекст
Используйте, когда прилетает баг-репорт, а ваше рабочее дерево — наполовину доделанный хаос, и вам нужно переключиться на другую ветку прямо сейчас. git stash откладывает незакоммиченные изменения в безопасное место и возвращает вам чистое рабочее дерево, соответствующее последнему коммиту. Никаких одноразовых коммитов «WIP» не требуется.
git stash # shelve tracked changes, clean the tree
git stash --include-untracked # also shelve new, untracked files
git stash list # see all stashes: stash@{0}, stash@{1}, ...
git stash pop # reapply the latest stash and delete it
git stash apply stash@{1} # reapply a specific stash, keep it in the list
Подвох: pop и apply — не взаимозаменяемые команды. git stash pop применяет отложенные изменения и удаляет stash; git stash apply применяет их, но сохраняет stash, что безопаснее, если при повторном применении возможен конфликт. Если при pop возникает конфликт слияния, stash не удаляется. Сначала разрешите конфликт, а затем проверьте — не считайте по умолчанию, что stash уже исчез.
git reflog: восстановить работу, которую вы считали потерянной
Используйте, когда неудачный reset --hard, сорвавшийся rebase или удалённая ветка заставили коммиты исчезнуть, и начинается паника. git reflog записывает каждую позицию, которую HEAD занимал в вашем локальном репозитории, поэтому «потерянный» коммит почти всегда всё ещё существует. Вам нужен лишь его хеш.
git reflog
# ab12cd3 HEAD@{0}: reset: moving to HEAD~1
# ef45gh6 HEAD@{1}: commit: the work you thought vanished
git reset --hard ef45gh6 # restore HEAD to that commit
# or, to inspect it on a new branch first:
git switch -c recovery ef45gh6
Найдите ссылку на нужное состояние, а затем выполните git reset --hard <ref>, чтобы вернуться к нему, либо переключитесь на новую ветку, чтобы сначала безопасно всё осмотреть. Это герой всего инструментария в ситуации «о нет, я потерял свою работу».
Подвох: reflog строго локален и существует отдельно для каждого клона. Он не передаётся на удалённый репозиторий и отсутствует в свежем клоне, поэтому повторное клонирование ради «возврата работы» не поможет. Записи со временем также удаляются сборщиком мусора, так что восстанавливайте раньше, а не позже.
git cherry-pick: перенести один коммит между ветками
Используйте, когда единственный коммит с hotfix должен попасть в релизную ветку, но вы не хотите вливать всю feature-ветку, из которой он пришёл. git cherry-pick <hash> копирует один конкретный коммит в текущую ветку, оставляя всё остальное за бортом.
git switch release-2.4
git cherry-pick 9f3c1a7 # apply one commit here
git cherry-pick 9f3c1a7 3b8e2d0 # apply several, in order
Подвох: cherry-pick создаёт новый коммит с новым хешем. Оригинал не сохраняется и не перемещается. Если тот же самый коммит позже попадёт в эту ветку через обычное слияние, в истории могут оказаться дублирующиеся изменения. Применяйте эту команду осознанно, а не как рутинную замену слиянию.
git rebase -i: привести историю в порядок перед PR
Используйте, когда в вашей feature-ветке накопилось пять коммитов вида «fix typo» и «wip», а вы хотите получить аккуратную, пригодную для ревью историю перед созданием pull request. git rebase -i <base-branch> открывает редактор, в котором можно переупорядочить, объединить, отредактировать или удалить коммиты.
git rebase -i main
# pick a1b2c3d Add feature scaffold
# squash e4f5g6h fix typo
# reword h7i8j9k Wire up handler
# drop k0l1m2n debug logging
Пометка squash объединяет коммит с расположенным выше, reword меняет только его сообщение, а drop полностью исключает его из результата. Если вам нужно лишь свернуть всю ветку в один набор проиндексированных изменений, родственный инструмент — git merge --squash <branch>. Он помещает объединённые изменения в индекс, не создавая коммит, так что git commit вы выполняете сами.
Подвох и золотое правило: переписывайте историю свободно, пока она локальна, но никогда не делайте rebase коммитов, уже отправленных в общую ветку. Переписывание общей истории вынуждает всех остальных заниматься мучительной синхронизацией — опасность, которую Pro Git называет «The Perils of Rebasing».
git bisect: найти коммит, породивший баг
Используйте, когда на прошлой неделе всё работало, а сейчас сломано, и вы понятия не имеете, какой из 200 коммитов виноват. git bisect выполняет двоичный поиск по истории: отметьте один плохой и один хороший коммит, протестируйте промежуточные состояния, которые Git будет выгружать, и он определит первый плохой коммит за log₂(n) шагов.
git bisect start
git bisect bad # current commit is broken
git bisect good v2.3.0 # this older tag worked
# Git checks out a midpoint — test it, then tell Git:
git bisect good # this one is fine
git bisect bad # this one is broken
# ...repeat until Git reports "<hash> is the first bad commit"
git bisect reset # return HEAD to where you started
Весь поиск можно автоматизировать: git bisect run <script> использует код возврата скрипта, чтобы помечать каждый коммит как хороший или плохой без ручного ввода. Завершайте каждую сессию командой git bisect reset, чтобы вернуть HEAD туда, где вы были до начала поиска.
Подвох: bisect предполагает, что каждый выгруженный коммит собирается и корректно проходит ваш тест. Коммит, не компилирующийся по постороннему поводу, исказит результат. Помечайте такие коммиты через git bisect skip, а не гадайте, хороший он или плохой.
Страховка для разрушительных команд
Две из этих команд переписывают историю, и правило для обеих одинаково: git rebase -i и git reset --hard безопасны для локальной, ещё не отправленной работы и опасны для общих веток. Когда нужна безопасная отмена в ветке, которую уже подтянули другие, используйте git revert — он создаёт новый коммит, отменяющий изменение, не затрагивая существующую историю. А если локальное переписывание пошло не так, git reflog станет тем аварийным выходом, который вернёт ваши коммиты.
Эти пять команд заслуживают своего места, потому что каждая отвечает на вопрос, недоступный commit и push: сохранить это, восстановить то, перенести одно, навести порядок, выследить баг. Свяжите каждую с её триггером и применяйте, как только возникает соответствующая ситуация.
Часто задаваемые вопросы
В чём разница между git reset --hard и git revert?
git reset --hard откатывает HEAD и отбрасывает коммиты, переписывая историю, что делает его опасным в любой ветке, которую уже подтянули другие. git revert вместо этого создаёт новый коммит, отменяющий целевое изменение, оставляя существующую историю нетронутой. Используйте reset --hard только для локальной, ещё не отправленной работы; используйте revert как безопасную отмену в общих ветках. Если reset пошёл не так, git reflog поможет восстановить отброшенные коммиты по хешу.
Как восстановить коммит после того, как git reset --hard его удалил?
Выполните git reflog — эта команда фиксирует каждую позицию, которую HEAD занимал в вашем локальном репозитории. Найдите запись для нужного состояния, запомните её хеш, затем выполните git reset --hard <hash>, чтобы восстановить его, либо git switch -c recovery <hash>, чтобы сначала осмотреть его в новой ветке. Reflog локален и существует отдельно для каждого клона, поэтому повторное клонирование работу не вернёт, а записи со временем удаляются сборщиком мусора, так что восстанавливайте раньше, а не позже.
Заменяет ли git switch команду git checkout при переключении веток?
Команды git switch и git restore появились в Git 2.23 (август 2019 года), чтобы разделить перегруженные функции git checkout: switch отвечает за переключение между ветками, а restore — за восстановление файлов. Обе man-страницы годами содержали предупреждение об экспериментальном статусе, но начиная с Git 2.55.0 это предупреждение убрано, так что теперь это просто рекомендуемые команды для соответствующих операций. git checkout продолжает работать и не считается устаревшим, поэтому существующие рабочие процессы остаются актуальными.
Когда стоит использовать git cherry-pick вместо слияния?
Используйте git cherry-pick, когда вам нужен ровно один коммит в другой ветке (например, при переносе одного hotfix в релизную ветку), без подтягивания остальной истории исходной ветки. Слияние переносит изменения всей ветки, что не подходит для изоляции одного исправления. Учтите, что cherry-pick создаёт новый коммит с новым хешем, поэтому если то же изменение позже придёт через обычное слияние, в истории могут оказаться дублирующиеся изменения.