Ответ 1
Во-первых, позвольте просто обратиться к двойному дефису или двойной тире, чтобы избавиться от него (особенно, поскольку этот вопрос больше не имеет отмеченного дубликата).
Git в основном использует это в одобренный POSIX способ (см. Руководство 10), чтобы указать разделительную линию между аргументами опций и необязательные аргументы. Поскольку git checkout
принимает имена ветвей, как в git checkout master
, а также имена файлов (пути), как в git checkout README.txt
, вы можете использовать --
, чтобы заставить Git интерпретировать все, что приходит после --
, как имя файла, даже если оно в противном случае было бы допустимым именем ветки. То есть, если у вас есть ветка и файл с именем master
:
git checkout master
проверит ветку, но:
git checkout -- master
проверит файл (сбивчиво, с текущего индекса).
Ветви, индекс и файлы, о мой
Далее нам нужно обратиться к quirk git checkout
. Как видно из документации, существует множество "режимов" git checkout
(в документации перечислены шесть отдельных вызовов в synposis!), Существуют различные тирады (различного качества: Стив Беннет действительно полезен, на мой взгляд, хотя, естественно, я не согласен с ним на 100%: -)) о Git плохой "пользовательской" модели, включая тот факт, что git checkout
имеет слишком много режимов работы.
В частности, вы можете git checkout
ветку (для переключения ветвей) или git checkout
один или несколько файлов. Последний извлекает файлы из определенного фиксатора или из индекса. Когда Git извлекает файлы из фиксации, он сначала копирует их в индекс, а затем копирует их из индекса, в дерево работы.
Для этой последовательности существует основная причина реализации, но тот факт, что она вообще отображается, является ключевым элементом. Нам нужно много знать о индексе Git, потому что оба git checkout
и git reset
используют его, а иногда и разными способами.
Это хорошая идея, я думаю, сделать трехстороннюю диаграмму или таблицу, иллюстрирующую текущий или HEAD
-коммит, индекс и дерево работы. Предположим, что:
- есть два обычных, преданных файла
README.md
иfile.txt
; - существует новый,
git add
-ed, но незафиксированныйnew.txt
; - есть файл с именем
rmd.txt
, который былgit rm
-ed, но не зафиксирован; - и есть файл без следа с именем
untr.txt
.
Каждая сущность - фиксация HEAD
, индекс и дерево работы - содержит три файла прямо сейчас, но каждый содержит другой набор файлов. Затем таблица всего состояния выглядит следующим образом:
HEAD index work-tree
-------------------------------
README.md README.md README.md
file.txt file.txt file.txt
new.txt new.txt
rmd.txt
untr.txt
Есть гораздо больше возможных состояний, чем только эти: на самом деле для каждого имени файла существует семь возможных комбинаций "in/not-in" HEAD, index и work-tree (восьмая комбинация "нет" во всех трех ", и в каком случае, о каком файле мы говорим в первую очередь?!).
Команды checkout
и reset
Две команды, о которых вы спрашиваете, git checkout
и git reset
, могут выполнять многие вещи. Конкретные призывы каждого, однако, уменьшают "дела" до одного из двух, к которому я добавлю еще несколько:
-
git checkout -- .
: копии из индекса, в дерево работы, только -
git checkout HEAD -- .
: копии из HEAD, для индексации, а затем для рабочего дерева -
git reset --mixed
: сбрасывает индекс из HEAD (а затем оставляет только дерево) -
git reset --hard
: сбрасывает индекс из HEAD, затем сбрасывает дерево работы из индекса
Они много перекрываются, но есть несколько принципиально разных частей.
Рассмотрим, в частности, файл с именем new.txt
выше. Это в индексе прямо сейчас, поэтому, если мы скопируем из индекса, в дерево работы, мы заменим копию рабочего дерева на копию индекса. Это то, что делает git checkout -- new.txt
, например.
Если вместо этого мы начнем с копирования из HEAD
в индекс, ничего не произойдет с new.txt
в индексе: new.txt
не существует в HEAD
. Следовательно, явный git checkout HEAD -- new.txt
просто терпит неудачу, а git checkout HEAD -- .
копирует файлы, находящиеся в HEAD
, и оставляет две существующие версии new.txt
невозмутимыми.
Файл rmd.txt
ушел из индекса, поэтому, если мы git checkout -- .
, Git не увидим его и ничего не делает. Но если мы git checkout HEAD -- .
, Git копируем rmd.txt
из HEAD
в индекс (теперь он обратно), а затем из индекса в дерево работы (а теперь и обратно туда).
Команда git reset
имеет ключевое различие при использовании без аргументов имени пути. Здесь он буквально переустанавливает индекс в соответствии с фиксацией. Это означает, что для new.txt
он замечает, что файл не находится в HEAD
, поэтому он удаляет запись индекса. Если используется с --hard
, он также удаляет запись рабочего дерева. Между тем rmd.txt
находится в HEAD
, поэтому он копирует его обратно в индекс и с помощью --hard
в дерево работы.
Если есть неустановленный, т.е. только дерево работы, изменяется на два других файла README.md
и file.txt
, обе формы git checkout
и --hard
формы git reset
уничтожают эти изменения.
Если в эти файлы внесены изменения, которые были скопированы в дерево работы, а затем git reset
, они будут деактивированы. Также существует вариант git checkout
, где вы указываете ему имя HEAD
. Однако вариант git checkout
, где вы копируете индексные файлы обратно в дерево работы, сохраняет эти поэтапные изменения!
Верхний уровень и текущий каталог
Наконец, стоит отметить, что .
, то есть текущий каталог, может в любое время отличаться от "top of Git repository":
$ git rev-parse --show-toplevel
/home/torek/src/kernel.org/git
$ pwd
/home/torek/src/kernel.org/git/Documentation
$ git rev-parse --show-cdup
../
Здесь я находится в подкаталоге Documentation
каталога верхнего уровня git
, поэтому .
означает все в Documentation
и его подкаталогах. Использование git checkout -- .
будет проверять (из индекса) все файлы Documentation
и Documentation/RelNotes
, но не любые файлы ../builtin
, например. Но git reset
при использовании без имен путей будет reset всех записей, в том числе для ..
и ../builtin
.