Есть ли разница между «git reset --hard hash» и «git checkout hash»?
-
23-09-2019 - |
Вопрос
Пока reset
и checkout
большую часть времени используются по-разному, я не вижу, какая разница между этими двумя.
Вероятно, есть один, иначе никто бы не удосужился добавить --hard
возможность сделать что-то основное checkout
сможет сделать.
Может быть, есть разница в том, как вы смотрите на историю?
Решение
Этот ответ в основном цитируется из моего ответа на предыдущий вопрос: git сброс на простом английском языке.
Эти двое очень разные.Они приводят к одному и тому же состоянию вашего индекса и рабочего дерева, но результирующая история и текущая ветвь не совпадают.
Предположим, ваша история выглядит так: в данный момент извлечена основная ветка:
- A - B - C (HEAD, master)
и ты бежишь git reset --hard B
.Вы получите это:
- A - B (HEAD, master) # - C is still here, but there's no
# branch pointing to it anymore
Вы действительно получите этот эффект, если используете --mixed
или --soft
тоже - единственная разница в том, что происходит с вашим рабочим деревом и индексом.в --hard
случае рабочее дерево и индекс совпадают B
.
Теперь предположим, что вы побежите git checkout B
вместо.Вы получите это:
- A - B (HEAD) - C (master)
Вы оказались в состоянии отсоединенного HEAD. HEAD
, рабочее дерево, индекс все совпадают B
, то же самое, что и при аппаратном сбросе, но главная ветка осталась C
.Если вы сделаете новый коммит D
на этом этапе вы получите это, что, вероятно, не то, что вы хотите:
- A - B - C (master)
\
D (HEAD)
Итак, вы используете checkout, чтобы проверить этот коммит.Вы можете возиться с ним, делать что хотите, но вы оставили свою ветку позади.Если вы хотите, чтобы ветка тоже была перемещена, используйте сброс.
Другие советы
Если документация, поставляемая с Git, вам не помогает, взгляните на Визуальный справочник по Git Марк Лодато.
В частности, если вы сравниваете git checkout <non-branch>
с git reset --hard <non-branch>
(ссылка):
(источник: github.com)
Обратите внимание, что в случае git reset --hard master~3
вы оставляете часть ревизий DAG — на некоторые коммиты не ссылается ни одна ветка.Они защищены (по умолчанию) 30 дней перезаписать;в конечном итоге они будут обрезаны (удалены).
git-reset hash
устанавливает ссылку на ветку для данного хеша и, при необходимости, проверяет ее с помощью--hard
.
git-checkout hash
устанавливает рабочее дерево по заданному хэшу;и если хеш не является именем ветки, вы получите оторванную голову.
в конечном счете, git имеет дело с тремя вещами:
working tree (your code)
-------------------------------------------------------------------------
index/staging-area
-------------------------------------------------------------------------
repository (bunch of commits, trees, branch names, etc)
git-checkout
по умолчанию просто обновляет индекс и рабочее дерево и при желании может обновить что-то в репозитории (с помощью -b
вариант)
git-reset
по умолчанию просто обновляет репозиторий и индекс и, при необходимости, рабочее дерево (с --hard
вариант)
Вы можете думать о репозитории следующим образом:
HEAD -> master
refs:
master -> sha_of_commit_X
dev -> sha_of_commit_Y
objects: (addressed by sha1)
sha_of_commit_X, sha_of_commit_Y, sha_of_commit_Z, sha_of_commit_A ....
git-reset
манипулирует тем, на что указывают ссылки на ветки.
Предположим, ваша история выглядит так:
T--S--R--Q [master][dev]
/
A--B--C--D--E--F--G [topic1]
\
Z--Y--X--W [topic2][topic3]
Имейте в виду, что ветки — это просто имена, которые автоматически перемещаются при фиксации.
Итак, у вас есть следующие ветки:
master -> Q
dev -> Q
topic1 -> G
topic2 -> W
topic3 -> W
И ваша текущая ветка topic2
, то есть HEAD указывает на theme2.
HEAD -> topic2
Затем, git reset X
сбросит имя topic2
указать на Х;то есть, если вы сделаете коммит P в ветке theme2, все будет выглядеть так:
T--S--R--Q [master][dev]
/
A--B--C--D--E--F--G [topic1]
\
Z--Y--X--W [topic3]
\
P [topic2]