MyTetra Share
Делитесь знаниями!
Качественное ТО Genesis - диагностика и замена фильтров.
Git: как переключиться на нужный коммит и вернуться обратно? Понимание, что такое ветка
Время создания: 22.06.2014 15:41
Автор: Xintrea
Текстовые метки: git, commit, коммит, checkout, переключиться, перейти, вернуться, head, ошибка, ветка, ветвление, понимание, объяснение
Раздел: Компьютер - Программирование - Системы контроля версий (VCS) - Git
Запись: xintrea/mytetra_syncro/master/base/1403437294hkfzh1wflf/text.html на raw.github.com

Как переключиться на нужный коммит?


Для переключения на нужный коммит используется действие checkout. После переключения, все файлы в проекте станут такими, какими они были в данном коммите. Переключение на коммит происходит командой:



git checkout commit



Где commit - это хеш (обозначение, имя) коммита, причем можно указывать не весь хеш, а несколько начальных символов хеша.



Примечание: переключение на интересующий пользователя коммит всегда будет приводить репозитарий в состояние detached HEAD. Это нормально, не стоит этого бояться.


Нахождение в состоянии "detached HEAD" можно проверить командой git status:



$ git checkout 3c53ec44

...

$ git status

HEAD отделён на 3c53ec44



Состояние "detached HEAD" означает, что текущий рабочий указатель не находится ни на какой ветке. Но как же так? Ведь коммит всегда принадлежит какой-то ветке! Это можно увидеть в том же gitk... А разгадка в том, что коммиты в Git - это, конечно, "снимки состояния репозитария, которые связаны между собой, образуя цепочки". Но нужно понимать, что цепочки коммитов в Git не считаются ветками! Вот это поворот!



Как работают ветки в Git?


Веткой в Git называется указатель на коммит, и этому указателю задано определенное имя. Если такой указатель указывает на последний коммит в цепочке, то в момент добавления нового коммита (у которого текущий является родителем) этот именованный указатель автоматически "переместится": то есть он начнет указывать на новый коммит. А имя указателя останется прежним. Таким образом, создается иллюзия, что программист работает в именованной ветке, и при добавлении коммитов ветка прирастает новыми коммитами. Хотя, на деле, система Git оперирует не ветками, а именно указателями на коммиты, не более того.


Нужно запомнить: никаких веток, в естественном человеческом понимании, в Git нет.



Вышеуказанные размышления можно продемонстрировать и по-другому. Нахождение в состоянии "detached HEAD" можно проверить командой git branch:



$ git branch

* (HEAD отделён на 3c53ec44)

control_panel

master

new_engine



Здесь видно, что пользователь переключился на какой-то коммит в истории. Но не видно на какой ветке находится пользователь, и, тем более, не видно на какой ветке он находился ранее. (Перед переключением, в gitk, было видно, что коммит 3c53ec44 находится в ветке control_panel, но он не является в ней последним). А так как коммит не самый последний в ветке, то после переключения рабочий указатель не начинает указывать ни на какую ветку (то есть рабочий указатель не совпадает с каким-либо именованным указателем, находящимся на конце какой-либо ветки). А такое состояние, на первый взгляд, и считается "detached HEAD".



Другими словами, указатель HEAD может указывать и на имя ветки, и просто прямо на коммит. В случае, если HEAD указывает на имя ветки, то через имя ветки получается коммит, на который указывает ветка. То есть, возможны два состояния:


HEAD -> Имя_ветки -> Коммит (т. е. хеш коммита)


или


HEAD -> Коммит (т. е. хеш коммита)


Когда HEAD указывает прямо на коммит, то такое состояние называется "detached HEAD".



Но тогда возникает вопрос: а что произойдет, если с помощью хеша коммита переключиться на коммит, который является последним в какой-нибудь цепочке, и в gitk видно, что на данном коммите завершается ветка control_panel? Поймет ли Git, что произошло переключение на коммит, на который указывает именованный указатель с именем control_panel? То есть, поймет ли Git, что происходит переключение на ветку control_panel?


Следующие команды показывают, что произошло переключение на коммит, на который так же указывает указатель ветки control_panel. И видно, что хеш коммита (на который переключились) 4c171d82 и хеш ветки 4c171d82 - совпадают. Но состояние "detached HEAD" все равно возникло:



$ git checkout 4c171d82


$ git branch -v

* (HEAD отделён на 4c171d82) 4c171d82 Manager и регулятор громкости

control_panel 4c171d82 Manager и регулятор громкости

master c4bfddfd ALS перенесен в разел коммутаторов

new_engine b9bcf376 Merge branch 'new_engine' of ...



То есть, даже если пользователь переключился на конечный коммит какой-либо ветки, состояние считается все равно как "detached HEAD". Это происходит просто в зависимости от того, на что указывает указатель HEAD - на конкретный коммит (тогда имеется состояние "detached HEAD") или на ветку (тогда состояние обычной работы с веткой).


Объяснение тут очень простое: на один и тот же коммит может указывать несколько указателей веток. Поэтому при прямом переключении HEAD на данный коммит, становится непонятно, какую ветку выбрать. Поэтому при перемещении HEAD на коммит, совпадающий с головой ветки, никакого автоматического переключения на ветку не происходит.



Важно для понимания: возможно ли в Git ветвление без веток?


Да, если над проектом работает несколько человек, или если один человек работает над проектом из разных локаций (на работе, дома, в дороге), или при различных комбинациях такой организации работы. Во всех этих случаях, даже в пределах одной ветки, могут образовываться различные ветвления.


Например, если человек делал несколько коммитов, находясь в дороге (и у него небыло интернета), то после того как интернет появится, эти коммиты заливаются на сервер, и они видны как одна цепочка коммитов. А другая цепочка коммитов, которые делались другими сотрудниками в этой ветке, пока человек был в дороге, будет тоже видна в этой же ветке. И выглядеть эти две цепочки будут в логе как две цепочки, которые разветвились в момент, когда человек поехал в командировку. И самое главное, что эти две цепочки не являются отдельными ветками. Это просто цепочки коммитов, различным образом разветвляющиеся и сливающиеся.



Дописать...



А как вернуться обратно после просмотра коммита?


Ответ. Можно воспользоваться командой:



git checkout -


(да, в конце команды ставится символ тире "-")



Эта команда вернет состояние репозитария в ту точку, где пользователь Git был до перехода. Даже если переход производился на коммит в другой ветке, возврат будет происходить с перепрыгиванием на ту ветку, где пользователь находился изначально.



Внимание! Данная команда производит переключение только между двумя коммитами, назовем их 1 и 2. При переходе с 1 на 2, git помнит что переход был сделан, с коммита 1. И команда git checkout - переместит пользователя обратно с коммита 2 на "запомненный" 1. Но в момент этого переключения git начнет помнить, что переключение производилось с коммита 2. И при очередной команде git checkout - снова произойдет переключение на коммит 2. И так по циклу.


Другими словами это значит, что команда git checkout - не возвращает пользователя по шагам по переключенным коммитам. Она способна вернуть пользователя только на один предыдущий коммит, а дальше она "зацикливается".



Вообще, в Git нет прямой команды, которая покажет список всех коммитов, на которые переключался пользователь. Однако, Git хранит информацию о последовательном изменений состояния указателя HEAD, что по своей сути и означает те места (коммиты/ветки/состояния) на которые переключался пользователь. Чтобы посмотреть эту информацию, используется команда:



git reflog



Команда git reflog отображает список всех изменений, которые происходили с HEAD, например:



  • Переключения между ветками (если выполнялась команда git checkout branch_name).
  • Переключения на конкретные коммиты (например, git checkout abc123).
  • Переключения на другие состояния (например, была команда git reset, git pull и т.д.).



Вот как выглядит вывод этой команды:



9ea9d6278 (HEAD) HEAD@{22}: checkout: moving from 05bfb81ab to 9ea9d6278


05bfb81ab (tag: v20.08.3, origin/release/20.08) HEAD@{23}: checkout: moving from 9fa8c11d1 to v20.08.3


9fa8c11d1 (tag: v18.04.0) HEAD@{24}: checkout: moving from 05bfb81ab to v18.04.0


05bfb81ab (tag: v20.08.3, origin/release/20.08) HEAD@{25}: checkout: moving from master to v20.08.3


83186b7ba (origin/master, origin/HEAD, master) HEAD@{26}: clone: from https://invent.kde.org/utilities/konsole.git



Самое "старое" состояние написано внизу вывода, более свежие - ближе к верху списка. В начале каждой строки написан хеш коммита, переключившись на который пользователь попадет в нужно место, где он ранее находился.


У команды git reflog есть ограничения. Git хранит историю переключений в reflog только определённое время. По умолчанию, это:



  • 90 дней для "перемещения" HEAD.
  • 30 дней для удалённых записей.



Если необходимо, эти значения можно изменить в конфигурации Git.



А как перейти в самое свежее состояние?


Самое свежее состояние внутри ветки? Тогда используется команда:



git checkout имя_ветки



В git, как и в языках C и C++, команды контекстно-зависимые. Поэтому команда checkout используется для различных действий - для переключения на коммит, для переключения между ветками, для перемещения на голову текущей ветки, для создания веток, идаже для восстановления файлов из истории. И еще для кучи всего другого. А само выполняемое действие определяется из того, какого типа параметры, в какой последовательности и с какими флагами прописываются справа от команды checkout.


Начиная с Git 2.23 разработчики решили, что это перебор, и вместо повсеместного использования checkout, сделали команды git switch и git restore, чтобы облегчить жизнь пользователей. Но куча литературы все так же осталась прежней, и там большой пласт действий по-старинке выполняется через checkout, хотя есть более адекватные инструменты.


Так же в этом разделе:
 
MyTetra Share v.0.67
Яндекс индекс цитирования