TL; DR
Что вы, вероятно, хотите сделать, - это создать новое имя ветки для вашего последнего коммита, например, git checkout -b <em>newname</em>
. Затем вы можете использовать это новое имя для всей своей работы или использовать его для запоминания ваших коммитов, когда вы возвращаетесь к старым именам и выполняете работу.
Long
Я проверил свой первый коммит, используя
git checkout a31e310304404eb4452e6465e0a92dd472c5329a
Выполнение этого приводит к тому, что Git называет обособленным HEAD
. Хотя это может показаться страшным, но на самом деле это не смертельно, это просто означает, что HEAD
не присоединен.
Обычно специальное имя HEAD
(записанное во всех заглавных буквах, вот так 1 ) прикрепляется к какому-либо существующему имени ветви. Название ветки в Git - это то, как Git отслеживает, кто и что делает. Более конкретно, само имя хранит необработанный хэш-идентификатор этих коммитов. Таким образом, имя ветки, такое как master
или develop
, будет хранить этот большой уродливый хэш-идентификатор, вещь a31e310304404eb4452e6465e0a92dd472c5329a
. Это позволяет вам, человеку, присвоить значимое имя этой, казалось бы, случайной вещи.
Вы не делаете обычные вещи, а это значит, что вам действительно нужно знать, что вы делаете. В противном случае вы потеряете контроль над своими коммитами, и все будет больно.
1 В Windows и MacOS вы можете использовать head
в нижнем регистре, но это что-то вроде аварии / странности системы. Лучше придерживаться варианта с прописными буквами, поскольку строчный не будет работать в Linux. Если вам не нравится вводить HEAD
заглавными буквами, вместо него можно использовать @
, что означает то же самое.
Фиксирует и названия филиалов
Давайте еще немного поговорим о том, как Git обычно работает с именами веток, потому что эта часть немного сложна и, тем не менее, является одним из ключей к успешному использованию Git. Первое, что нужно понять, это то, что сам Git не очень заботится об этих именах: они в основном предназначены для использования людьми. Git в основном заботится о хеш-идентификаторах, этих больших уродливых цепочках букв и цифр. Они уникально идентифицируют каждый коммит, и коммиты являются причиной того, что Git существует вообще. Так вот о чем Git заботится.
Давайте посмотрим на действительно крошечный, почти новый репозиторий, в котором всего три коммита. Каждый из этих трех коммитов будет иметь свой собственный уникальный хэш-идентификатор, но чтобы избавить нас от головной боли, вместо этого я буду использовать для них одну заглавную букву. Я назову первый сделанный коммит A
, второй коммит B
, а третий C
.
Каждый коммит хранит полный снимок дерева исходных текстов вместе с некоторыми метаданными , которые являются просто причудливым словом для материала git log
может напечатать о коммите: who сделал это и когда, и какое сообщение журнала они ввели для этого. Метаданные также включают необработанный хэш-идентификатор коммита parent commit, который является коммитом, который был вместо , когда человек сделал этот коммит.
Другими словами, каждый коммит указывает на свой предыдущий коммит. Итак, давайте нарисуем это:
A <-B <-C
Commit A
, будучи самым первым коммитом, имеет no parent, потому что не может. Но коммит B
помнит, что A
был коммитом, когда было сделано B
, поэтому B
указывает на A
. Аналогично, commit C
помнит для нас, что B
был его родителем, поэтому C
указывает на B
.
На данный момент нам и Git нужно найти способ найти большой некрасивый хеш-идентификатор для коммита C
. Вот где появляются имена ветвей: имя ветви master
может содержать большой уродливый уникальный идентификатор хеша для C
, например:
A--B--C <-- master
Мы говорим, что имя ветви master
, указывает на последний коммит в цепочке. Мы используем имя ветви для получения идентификатора хеша, и это позволяет нам (через Git) совершать коммит C
. Commit содержит хэш B
, поэтому из C
мы можем найти B
, а из B
мы можем найти A
. A
не имеет родителя, поэтому мы можем остановиться на этом.
Теперь, если у нас есть только одно имя ветви, это довольно просто. Мы git checkout master
, что заставляет нас совершать C
. Затем мы вносим некоторые изменения, git add
наши измененные файлы, и запускаем git commit
. Это упаковывает новый снимок, добавляет наше собственное имя и текущее время, добавляет наше лог-сообщение, сохраняет хэш-идентификатор C
и делает новый коммит, который теперь получает новый, уникальный, большой уродливый хеш-идентификатор, и теперь происходит ключевой шаг: Git записывает новый хэш-идентификатор в имя master
:
A--B--C--D <-- master
Но что, если у нас есть два имени ветви? Давайте создадим новое имя ветки сейчас:
A--B--C--D <-- master, develop
В настоящее время оба имени хранят один и тот же хэш-идентификатор для фиксации D
. Теперь Git должен знать , какое имя ветки обновлять . Поэтому для этого Git прикрепляет HEAD
к одному из них, который мы даем git checkout
. Например:
git checkout master
A--B--C--D <-- master (HEAD), develop
но:
git checkout develop
A--B--C--D <-- master, develop (HEAD)
В любом случае, мы начинаем с коммита D
. Разница в том, какая ветвь name изменяется, когда мы делаем новый коммит E
:
A--B--C--D <-- master
\
E <-- develop (HEAD)
Здесь следует помнить следующее: Всякий раз, когда мы добавляем новый коммит, Git заставляет новый коммит иметь предыдущий текущий коммит в качестве своего родителя. Затем Git записывает новый идентификатор коммита в имя ветви , к которому прикреплен HEAD
.
Обратите внимание, что шаг git checkout
делает две вещи:
- выбирает коммит, на который указывает имя ветки; и
- присоединяет
HEAD
к имени этой ветви.
Здесь первый пункт не имел значения только потому, что и master
, и develop
указывали на одинаковый коммит.
Отсоединенный HEAD почти такой же, но имя ветки не задействовано
Если вы git checkout
делаете коммит по его необработанному хеш-идентификатору, то Git по-прежнему выбирает тот коммит как текущий коммит, но на этот раз он не присоединяет HEAD
к имени ветки , Допустим, например, что вы выбрали хеш-идентификатор commit C
. Нет имени ветки, указывающего на C
, поэтому Git должен сделать это вместо:
E <-- develop
/
D <-- master
/
A--B--C <-- HEAD
Все, что мы здесь сделали, - это отогнали D
и E
с пути, чтобы мы могли нарисовать HEAD
, указывая прямо на фиксацию C
. Git сделал здесь, чтобы записать необработанный хэш-идентификатор в имя HEAD
вместо того, чтобы иметь имя ветви в HEAD
. (Если хотите, вы можете посмотреть файл .git/HEAD
: он либо содержит необработанный хэш-идентификатор, подобный этому, либо, если он присоединен к ветви, содержит текст ref: refs/heads/<em>branch</em>
.)
Теперь мы можем сделать новый коммит как обычно, отредактировав файлы, запустив git add
при необходимости и запустив git commit
. Git собирает наше сообщение о коммите как обычно и делает новый коммит как обычно. Родительский коммит нового коммита - это текущий коммит - коммит C
, чей идентификатор хеша находится в файле HEAD
. Затем вместо записи хеш-идентификатора нового коммита в ветвь с именем в файле HEAD
Git просто записывает ID нового коммита в HEAD
напрямую:
E <-- develop
/
D <-- master
/
A--B--C--F <-- HEAD
Если вы продолжите делать еще два коммита, процесс просто повторяется:
E <-- develop
/
D <-- master
/
A--B--C--F--G--H <-- HEAD
Обратите внимание, что Git может легко найти коммит H
прямо сейчас, потому что его хэш-идентификатор находится в файле HEAD
. Начиная с H
, Git может найти G
и F
(а затем C
и так далее). Но если вы, например, git checkout master
или git checkout develop
, Git сделает коммит D
или E
текущим, а запишет имя ветви в файл HEAD
, потеряв H
. ID хэша .
Если вы сохраните хеш-код где-нибудь, вы все равно сможете поработать с ним некоторое время. Git какое-то время пытается не выбрасывать не обнаруживаемые коммиты, подобные этой. 2 Но его становится трудно найти, затерянное в море почти идентичных коммитов, отличающихся главным образом своими, казалось бы, случайными хэш-идентификаторами. Лучше всего дать ему имя.
2 По умолчанию это длится не менее 30 дней, используя reflog для HEAD
. Reflog содержит все хеш-идентификаторы, которые HEAD
названы за последние 30–90 дней или дольше, в зависимости от ряда деталей, которые я здесь не буду раскрывать, поскольку это уже слишком долго. : -)
Задание имени для хэша коммита
То, что вы, вероятно, хотите сделать сейчас, - это присвоить имя для запоминания хеш-идентификатора H
. Это то, для чего нужны имена ветвей - и имена тегов. Если вы создадите новое имя ветки прямо сейчас, по умолчанию оно будет указывать на текущий коммит :
git branch xyzzy
E <-- develop
/
D <-- master
/
A--B--C--F--G--H <-- HEAD, xyzzy
Обратите внимание, что не прикрепил HEAD
к имени xyzzy
. После создания имени вы можете git checkout xyzzy
сказать Git переключиться с коммита H
, где HEAD
указывает, на коммит H
, где xyzzy
указывает. Этот переключатель ничего не делает, но теперь применяется вторая часть git checkout
, и Git присоединяет HEAD
к имени xyzzy
:
E <-- develop
/
D <-- master
/
A--B--C--F--G--H <-- xyzzy (HEAD)
Вы можете сделать оба этих действия одновременно, используя git checkout -b xyzzy
: это создает xyzzy
, указывая на текущий коммит, и в то же время присоединяет HEAD
к новому имени xyzzy
.
Как только вы это сделали, у вас теперь есть имя для ваших коммитов. Теперь можно использовать git push
для отправки коммитов в другой Git-репозиторий, сообщая, что другой Git-репозиторий должен установить свой xyzzy
, чтобы он указывал на коммит H
(независимо от фактического хэша H
). Идентификатор есть).
Когда другой репозиторий Git получает H
, он также получает G
и F
и, при необходимости, C
и B
и даже A
(при условии, что этот конкретный чертеж графика корректен). По сути, git push
отправляет все коммиты, которые содержатся в - или достижимы из (см. Думайте как (а) Git ) - ветви, если только другой Git уже имеет их. Затем ваш Git установил в Git какое-то имя, обычно такое же имя, чтобы запомнить последний коммит, как это делают ветви.
Если вы не хотите сохранять два снимка F
и G
и их метаданные фиксации, теперь пришло время использовать git rebase
или аналогичный для перезаписи некоторых коммитов, но это тема для другого вопроса (и на StackOverflow уже есть много ответов по этому поводу).