# Откат ошибочной команды `git push --force` Иногда при работе с несколькими удалёнными репозиториями в git, может произойти страшное: `git push --force` в не тот `remote` и/или не в ту ветку. Такое может случиться, например, если вы используете [Deis], в котором деплой запускается при `git push` нужного коммита в сборщик, когда при отладке деплоя после очередного `git commit --amend` по запарке вместо `git push deis master --force` делается просто `git push --force`. Упс. Как результат, последние коммиты коллег безвозвратно потеряны, и вы чувствуете неотвратимость их ярости… Но это git, а значит всё можно починить! # Как чинить ## Вариант самый простой и для ленивых Немедленно врывайтесь в рабочий чат с воплем «кто последний пушил в branchname? форс-пушните вашу версию ветки!». С какой-то долей вероятности последним сделает `git push --force` именно тот коллега, у которого самая свежая версия кода. ## Простой вариант: я последний пушил в ветку Этот вариант прост тем, что у вас есть всё, что нужно для восстановления за 1 минуту, не выходя из консоли. Перво-наперво: без паники. **Не закрывайте терминал!** Сожалея о содеянном зайдите в чат и покайтесь, в том, что вы наделали. В выводе команды `git push --force` найдите строчку, похожую на эту: ``` + deadbeef...f00f00ba master -> master (forced update) ``` Первая группа символов, подозрительно похожих на SHA коммита и есть наш ключ к спасению: `deadbeef` — это действительно коммит, который был в ветке `master` до того, как мы всё сломали. Теперь просто пушните этот коммит в ветку, которую вы поломали: ``` git push --force origin deadbeef:master ``` Всё, вы всех спасли. Но больше так не делайте. ## Сложный вариант: починить за кем-то Иногда бывает, что `git push --force` сделали либо не вы, либо кто-то принял пачку Pull Request'ов в то время, пока вы веселились со своими экспериментами. Тяжесть ситуации в том, что вы не можете просто сделать `git push --force sha1:master`, поскольку **у вас локально нет коммитов, которые нужно восстановить** (и у вас не получиться скачать их с помощью `git fetch`). Стоп! Без паники! Зайдите в чат, покайтесь, скажите, чтобы никто ничего не делал — вам понадобится время, чтобы всё вернуть. Здесь нам поможет тот факт, что GitHub не удаляет коммиты, которые больше не принадлежат ни к какой ветке. Но, что усложняет задачу, не даёт их и стянуть с командной строки. Если force-пуш сделали вы, то просто возьмите хэш коммита, который был в ветке до вас (как и в прошлом пункте). Если не вы, то можно зайти в ленту событий GitHub'а (на главной странице, в случае, если вы подписаны на репозиторий) и посмотреть, кто последний коммитил в эту ветку: ``` an hour ago Username pushed to master at org/repo - deadbeef Implement foo - deadf00d Fix bar ``` Теперь перейдите по ссылке `https://github.com/org/repo/tree/deadbeef` (где `deadbeef` — хэш последнего коммита в ветке, которую вы перетёрли), откройте переключатель веток и тэгов, введите имя для новой ветки (например `master-before-force-push`) и выберите пункт «Create branch». Теперь через консоль можно получить недостающие коммиты: ```sh $ git fetch From github.com:org/repo * [new branch] master-before-force-push -> origin/master-before-force-push ``` и теперь задача сводится к предыдущей: ``` git push --force origin origin/master-before-force-push:master ``` Если наработки в вашем `master`е ещё нужны, то лучше отребейзить свои коммиты поверх него: ``` git rebase origin/master ``` ## Как избежать такого в дальнейшем? 1. В GitHub и GitLab есть функциональность под названием «защищённые ветки» (protected branches). Отметьте `master`, `develop`, `stable` или какие ещё ветки важны для вас как защищённые и система не даст вам выстрелить себе в ногу. Если force push всё же понадобится, то защиту всегда можно на время снять. См. документацию: https://help.github.com/articles/defining-the-mergeability-of-pull-requests/ 2. Используйте вместо ключа `--force` ключ `--force-with-lease`, который отменит push, если кто-то другой уже успел опубликовать новые коммиты. Подробнее здесь: https://developer.atlassian.com/blog/2015/04/force-with-lease/ 3. Создайте алиасы для команд, которые должны делать `git push --force`, чтобы обезопасить себя от ошибок по неосторожности: ``` # ~/.gitconfig [alias] deploy = "!git push --force deis \"$(git rev-parse --abbrev-ref HEAD):master\"" ``` 4. Прекратите уже экспериментировать прямо в основной ветке! Команда `git checkout -b experiments` — ваш друг. **Pro Tip**: У команды `git push` также ещё очень хорошо сочетаются друг с другом ключи `--force` и `--all`, особенно, если вы отвлеклись от проекта на месяцок-другой. Попробуйте, если, конечно, вам всё ещё мало приключений… [Deis]: https://deis.com/