Skip to content

Instantly share code, notes, and snippets.

@Envek
Last active August 18, 2024 10:26
Show Gist options
  • Save Envek/13d9e406bb2af23f739197e3934ad4f0 to your computer and use it in GitHub Desktop.
Save Envek/13d9e406bb2af23f739197e3934ad4f0 to your computer and use it in GitHub Desktop.
Откат ошибочной команды git push --force

Откат ошибочной команды 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».

Теперь через консоль можно получить недостающие коммиты:

$ 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, особенно, если вы отвлеклись от проекта на месяцок-другой. Попробуйте, если, конечно, вам всё ещё мало приключений…

@gorkunov
Copy link

gorkunov commented Aug 4, 2017

возможно еще может git config --global push.default current. Чтобы по дефолту пушить только свою ветку а не все.

@Envek
Copy link
Author

Envek commented Aug 7, 2017

@gorkunov, по умолчанию в git начиная с версии 2.0 (т.е. уже довольно давно у тех, кто ставит git из Homebrew на OS X и из коробки у тех, кто не сидит на очень старых дистрибутивах Linux) используется даже ещё более безопасное поведение simple. См. https://stackoverflow.com/a/23918418/338859

Но ключик --all однозначно решает.

@RomanSolodukhin
Copy link

Случайно снёс ветку через Atom. Спасло то, что в jenkins была информация обо всех коммитах

@vbifonixor
Copy link

Только что снёс ветку и поседел. Спас тимсити, который выдал хоть и корявенькие, но более-менее выпрямляемые руками патчи изменений для ветки.

@athlonUA
Copy link

athlonUA commented Nov 6, 2020

You saved my life

@rsitdiko1
Copy link

Ты просто спаситель!!

@Us3rL0sT
Copy link

Уже второй раз за год спасаешь!!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment