На неделе с рабочим git-репозиторием случилась занимательная история.
В конце одного рабочего дня случилась обычная и вполне штатная ситуация: коллега запушила изменения в центральный репо, я попытался сделать то же самое, на что git меня справедливо послал куда подальше.
Я всегда стараюсь поддерживать простую историю коммитов, поэтому вместо обычного merge
решил сделать rebase
своего коммита на коммиты коллеги. И, как стало ясно из последующих событий, видимо совершил свою любимую ошибку - вместо наложения своего коммита поверх чужих, наложил чужие на свой. После этого я запушил результат в центральный репо, собрал вещи и ушел домой.
На следующее утро я пришел в офис и по привычке решил сделать pull
перед началом работы, но он почему-то выдал мне коммит далеко не первой свежести, причем с пометкой (forced update)
. Не помню, зачем, но я снова сделал pull
(или fetch
) - и каково же было мое удивление, когда второй раз он выдал правильный последний коммит, сделанный мной вчера вечером!
Последующие фетчи и пуллы работали аналогичным образом, через один - то выдается forced update
со старым коммитом, то правильный HEAD
. Попытки сделать новую локальную ветку, новую локальную копию, даже новый идентичный прежнему репозиторий на сервере - ничего не принесли.
Гугл поведал мне о том, что в git есть такое нехорошее состояние, как detached HEAD - это когда на определенный коммит, или даже целую ветку коммитов, не указывает ничего (как известно, в git ветка является указателем на последний из последовательности коммитов; в данной ситуации на последний из коммитов указывает только HEAD, и только до тех пор, пока мы не сделаем checkout
другой ветки). Такая ситуация случается, в частности, при rebase
- перемещаемые поверх других коммиты остаются в репозитории в двух местах - в новом, куда их переместил rebase
(поверх других коммитов), и в старом, где они были до rebase
; и на старое место более не указывает ни один ref (ни HEAD, ни ветки, ни теги).
Такая ситуация обычно не представляет опасности - но только если исходные коммиты до rebase
не были запушены в другой репозиторий, используемый другими разработчиками; если это случилось, пушить отrebase
-нные коммиты нельзя, во всяком случае если не хочется быстрой смерти от руки коллеги :) Если в общем репозитории появятся 2 версии одного и того же коммита, и каждый разработчик будет работать со своей версией, начнется трэшак.
Так вот, после некоторого изучения ситуации выяснилось, что в серверном репозитории HEAD
указывает на последний коммит в master
, а тот указывает на правильный последний коммит. Но при этом в .git/refs/heads
помимо всех наших веток был еще и файл HEAD
, указывающий уже на тот самый коммит, который выдавался fetch
-ем через раз с пометкой (forced update)
.
Не зная внутренностей гита, поначалу я решил, что HEAD
там и должен всегда находиться; но потом посмотрел внутренности нескольких других имеющихся репозиториев, и не нашел .git/refs/heads/HEAD
ни в одном из них.
После удаления этого файла все чудесным образом заработало. Честно говоря, полное понимание того, что случилось, так до сих пор не пришло; поэтому резервная копия того файла осталась на всякий случай лежать на сервере.
P.S. Пока гуглил, нашел достаточно качественный визуальный справочник по командам git.