См. Превосходные (и повторяющиеся) вопросы и ответы по адресу В чем разница между Git Revert, Checkout и Reset? Но мы должны начать с чего-то еще более простого.
Почему мы используемконтроль версий?
Цель системы контроля версий *1007* - сохранить все, что когда-либо было сделано, навсегда.Ну, кроме случаев, когда это не так: иногда целью является сохранение некоторых вещей, выполненных за все времени, некоторых вещей для некоторых время и некоторые вещи за очень короткое время.
способ , который Git сохраняет, это как полные снимки, которые мы называем коммитами , которые также несутнекоторая дополнительная информация о коммите, которую мы называем метаданными .Метаданные включают в себя имя и адрес электронной почты человека, который сделал коммит, чтобы мы могли спросить его почему они сделали это, плюс сообщение журнала, чтобы они могли сказать нам, почему они сделали это без наснеобходимость беспокоить их.Метаданные в Git также включают понятие предыдущего или родительского коммита.Сравнивая родительский снимок с этим конкретным снимком, Git может сказать нам, что человек, который сделал коммит изменил .
Имея это в виду, мы можем посмотреть на эти три глагола Git (Iя тоже добавлю git checkout
):
git checkout
- чтобы получить что-то, сделанное за некоторое время
Мы используем git checkout
, чтобы получить один конкретный коммит.Коммит - это моментальный снимок, сделанный кем-то в какое-то время.Предположительно, этот снимок был хорош для какой-то цели.Мы используем git checkout
, чтобы получить этот снимок, точно так, как он был сделан в то время, независимо от того, какой может быть наша следующая цель.
В Git, как побочный эффект использования git checkout
с имя филиала , теперь мы готовы сделать новую работу.Но мы также можем использовать git checkout
с необработанным хэшем коммита, после чего новые коммиты ... ну, немного сложно.(Они все еще возможны, но Git вызывает этот режим detached HEAD , и вы можете не захотеть использовать его, пока не узнаете больше о Git.)
Причина, по которой git checkout master
Например, чтобы получить последний коммит на master
, каждый раз, когда мы делаем новый коммит на master
, Git автоматически обновляет наше имя master
, так чтоэто означает новейший такой коммит.Новейший коммит запоминает своего родителя, который раньше был самым новым.Этот второй коммит с одним ответом запоминает его родителя, который был самым новым, когда не существовало коммит с одним ответом, и т. Д.
Что это означает, что name master
действительно просто найдите последний коммит, из которого мы находим каждый более ранний коммит:
... <-F <-G <-H <--master
, где каждая заглавная буква стоит для хеш-идентификатора коммита,Мы говорим, что каждый коммит указывает на своего родителя, а master
указывает на самого последнего коммита.
git revert
- отмена плохого коммита
Учитывая, что каждый коммит записывает своего родителя, и поэтому Git может сказать нам, что человек, который сделал этот коммит изменил , мы всегда можем сделать Git отменить чужое изменение (илидаже наш).Мы выбираем коммит, рассматриваем его как изменение - именно так Git показывает его нам, когда мы используем git log -p
или git show
, - и обнаруживаем, что, эй, это изменение было неправильным .Это изменение должно быть отклонено или «отменено». 1
1 Глагол revert здесь на самом деле плохой выбор.За наиболее распространенным определением в английском языке почти всегда следует вспомогательное слово от до , так как в возвращаются к , и это означает возврат в прежнее состояние.Но отказ от некоторых изменений не обязательно возвращает нас к старому состоянию!Мы вернемся в наше предыдущее состояние, только если откажемся от самого последнего изменения.
В других системах контроля версий лучше использовать глагол backout .В любом случае, когда мы используем этот глагол, Git делает новый коммит, сохраняя новый моментальный снимок, который похож на нашу предыдущую проверку, за исключением того, что чьи-то изменения были отменены.Ну, то есть Git делает этот коммит, если не существует конфликта слияния , но мы здесь проигнорируем эту возможность.
git reset
... ну, запутано,но мы можем использовать его для выбрасывания коммитов
Git's reset глагол чрезвычайно сложен.В одной конкретной форме он делает до трех вещей.С другими формами это делает другие вещи.В частности, тот, о котором вы спрашивали, git reset --hard HEAD~1
, говорит Git:
- Сделать текущую ветку name , что бы это ни было, указать на родителя текущегоcommit.
- Сотрите текущий index - который мы здесь не описали, но index , область подготовки и даже кеш - это всего лишь три имени для одной и той же вещи в Git, и заполните его из коммита, выбранного на шаге 1.
- Удалите все файлы work-tree , которые были в комплекте синдекс до того, как мы его сбросим, и заменим их копиями, извлеченными из фиксации, выбранной на шаге 1 и скопированной в индекс на шаге 2.
Так что если у нас было:
... <-F <-G <-H <--master
мы изменили имя master
, чтобы оно указывало на G
, что повлекло за собой H
:
H
/
... <-F <-G <-- master
Коммит, хэш которого H
теперь эффективно потеряно , как если бы оно никогда не было сделано.Это все еще в хранилище , просто стало трудно найти .Со временем, если мы не предпримем никаких других шагов для его сохранения, коммит H
действительно исчезнет.
Запомните нашу цель для коммитов
Мы хотим, чтобы коммиты сохраняли всекогда-либо сделано за все время.Но иногда то, что мы делали - например, возможно, выполняли коммит H
- было ошибкой:
...--F--G--H--I--J--K--L <-- master
Если мы сделали H
некоторое время назад, и все это встроено так, трудно удалитьпотому что каждый коммит полностью заморожен, поэтому удалить H
, мы должны скопировать I
в новый и другой коммит I'
с G
в качестве егоparent, затем скопируйте J
в новый коммит с I
в качестве родителя и т. д.:
H--I--J--K
/
...--F--G--I'-J'-K' <-- master
Здесь проще вернуть H
, добавивновый коммит, который отменяет все, что мы изменили в H
.Коммиты с I
по K
остаются прежними - возможно, немного сломанными, но так они и были на самом деле все время - и теперь у нас есть новый коммит L
, чтобы отменить то, что мы сделали в H
:
...--F--G--H--I--J--K--L <-- master
Но если H
был довольно недавним, мы можем просто полностью удалить его, используя git reset --hard
.Мы забудем, что когда-либо совершали эту ошибку.Больше не нужно никому рассказывать.