Иногда при работе с несколькими удалёнными репозиториями в 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».
Теперь через консоль можно получить недостающие коммиты:
$ 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
-
В GitHub и GitLab есть функциональность под названием «защищённые ветки» (protected branches). Отметьте
master
,develop
,stable
или какие ещё ветки важны для вас как защищённые и система не даст вам выстрелить себе в ногу. Если force push всё же понадобится, то защиту всегда можно на время снять. См. документацию: https://help.github.com/articles/defining-the-mergeability-of-pull-requests/ -
Используйте вместо ключа
--force
ключ--force-with-lease
, который отменит push, если кто-то другой уже успел опубликовать новые коммиты. Подробнее здесь: https://developer.atlassian.com/blog/2015/04/force-with-lease/ -
Создайте алиасы для команд, которые должны делать
git push --force
, чтобы обезопасить себя от ошибок по неосторожности:# ~/.gitconfig [alias] deploy = "!git push --force deis \"$(git rev-parse --abbrev-ref HEAD):master\""
-
Прекратите уже экспериментировать прямо в основной ветке! Команда
git checkout -b experiments
— ваш друг.
Pro Tip: У команды git push
также ещё очень хорошо сочетаются друг с другом ключи --force
и --all
, особенно, если вы отвлеклись от проекта на месяцок-другой. Попробуйте, если, конечно, вам всё ещё мало приключений…
@gorkunov, по умолчанию в git начиная с версии 2.0 (т.е. уже довольно давно у тех, кто ставит git из Homebrew на OS X и из коробки у тех, кто не сидит на очень старых дистрибутивах Linux) используется даже ещё более безопасное поведение
simple
. См. https://stackoverflow.com/a/23918418/338859Но ключик
--all
однозначно решает.