Фундаментальная проблема здесь заключается в том, что Git делает коммиты не из рабочего дерева, а из индекса, поэтому вам нужно сначала git add
файлы, но индекс является своего рода ложь, потому что может быть больше индексных файлов, чем только один стандартный. (Индекс также называется промежуточной областью или кешем , в зависимости от того, какая часть Git выполняет вызов.)
Индекс , под которым я имею в виду один стандартный, - это файл в .git
с именем index
. Если вы проверите свой каталог .git
, вы найдете такой файл. В прошлом действительно был только этот файл. В современном Git (версии 2.5 и выше) картина значительно более мутная из-за добавленных рабочих деревьев: на каждое рабочее дерево фактически существует один индексный файл, так что .git/index
- это всего лишь индекс для main work-tree. Для каждого рабочего дерева есть вспомогательный индекс , но это не совсем то, к чему я стремлюсь, здесь, это просто случай, показывающий, как предположение, что существует один единственный индекс, уже изнашивается по краям. , По общему признанию, вы используете Git 1.8.3.1 (который на самом деле довольно старый), но он также является более сложным, чем простая простая настройка «один индекс».
Когда вы используете git commit -a
, Git создает новый дополнительный индекс. Когда вы используете git commit .
, вы вызываете git commit --only .
( см. Подробности в документации ), и Git создает два новых дополнительных индекса (индекса?).
Все части Git имеют возможность перенаправить rest Git для использования другого, нестандартного индекса, и эти различные опции для git commit
используют эту функцию. Обратите внимание, что git commit -a
эквивалентно git commit --include
, за которым следуют имена любых файлов, которые необходимо добавить. Действительно сложный случай - это тот, который вы используете, git commit --only
.
Как только вы начинаете умножать индексные файлы, все становится запутанным!
Помните, что индекс, по сути, предлагает следующий коммит . Если существует только один индекс (для этого рабочего дерева, если мы говорим о Git 2.5 или более поздней версии), будет предложен только один следующий коммит. Это не так уж сложно, мы просто должны учесть, что три копии каждого файла. Давайте выберем файл, такой как README.md
:
HEAD:README.md
является текущей версией README.md
. Вы не можете это изменить. (Вы можете переместить HEAD
сам, но зафиксированная копия README.md
находится внутри фиксации, как обнаружено по хеш-идентификатору фиксации, и не изменится.)
Имя HEAD:README.md
работает только внутри Git. Это имя обращается к этой замороженной Git-ified, лиофилизированной копии файла; эта копия никогда не изменится. Вы можете видеть это, например, git show HEAD:README.md
.
:README.md
является копией README.md
в индексе. Первоначально он был таким же, как HEAD:README.md
, но если вы запустили git add README.md
, теперь он может отличаться.
Имя :README.md
также работает только внутри Git. Это имя обращается к этой заменяемой, но Git-ified (замораживаемой форме) копии файла, хранящейся в индексе. Вы можете заменить это в любое время на git add
.
Наконец, README.md
- это обычный (не Git-ified) файл. Это не в Git! Это не в индексе! Он находится в вашем рабочем дереве , где вы можете видеть его и работать над ним, используя все ваши обычные компьютерные инструменты. Git действительно не использует этот файл ни для чего, он просто перезаписывает его или удаляет его, когда вы проверяете какой-то другой коммит. Единственное, что Git делает с ним, кроме проверки с помощью git status
и т. Д., Это позволяет вам использовать git add
до копировать обратно в индекс, перезаписывая то, что было раньше (и сублимационной сушкой). это в процессе).
Бег git status
Бег два git diff
с:
Первый сравнивает коммит HEAD
с индексом, то есть то, что находится в текущем коммите, с тем, что находится в предлагаемом следующем коммите. Все, что отличается здесь, указано как для коммита . Что-нибудь такое же, Гит просто тихо ничего не говорит.
Второй git diff
сравнивает индекс с рабочим деревом, т. Е. Что находится в предлагаемом коммите, с тем, что вы можете скопировать в индекс.Все, что отличается здесь указано как , не предназначенное для фиксации .Все то же самое, опять же, Git тихо ничего не говорит.
(Затем есть последний проход для проверки файлов в рабочем дереве, которых вообще нет в индексе. Gitбудет скулить по этому поводу, говоря, что они не отслежены, если только вы не перечислите их в .gitignore
. Присутствие в списке в .gitignore
не меняет, есть ли копия файла в индексе, оно просто меняет, скулит ли Git.)
Когда вы запускаете git commit
, Git упаковывает все, что находится в индексе, и использует его для создания нового коммита ... , если вы не используете --only
, --include
или -a
.
С git commit --only
Git создает три индексных файла:
- Один из них стандартный.Это нетронутым в начале.Это обычный
.git/index
. - Один - копия этого с
--only
файлами git add
, отредактированными к нему.Это в .git/index.lock
в какой-то момент. Может быть это всегда здесь!Если это так, то это даст способ разобраться с делом, которое я изложу ниже.Но нет документации, которая обещает это. - Третий - свежий, сделанный сначала извлечением
HEAD
, а затем git add
добавлением в него --only
файлов.
Если вы не сделали git add
что-либо до , вы запустили git commit -a
, первый и третий индексные файлы совпадают, потому что добавление файлов --only
в обычный индекс имеет тот же эффект, что и создание нового временного индексаиз HEAD
и добавив в него файлы --only
.Но в противном случае все три файла могут отличаться!
Затем Git делает новый коммит из третьего индекса.Если новая фиксация завершается успешно, Git заменяет обычный индекс на second index (эта замена происходит через системный вызов rename
).В противном случае Git возвращается к нормальному индексу.(Обратите внимание, что с рабочим деревом вообще ничего не происходит.)
Если вы используете git commit --include
или git commit -a
, Git создает только один дополнительный индекс, поэтому у вас есть:
- стандартный индекс в
.git/index
со всем, что вы добавили до сих пор;и - дополнительный индекс во временном файле: он начинается как копия стандартного индекса, но затем Git добавляет перечисленные файлы или другие измененные файлы к этому индексу.
Затем Git запускает процесс фиксации.Если все идет хорошо, когда Git готов, Git переименовывает временный индекс, чтобы он стал стандартным индексом.Если дела идут плохо, Git удаляет временный индекс, а стандартный индекс остается неизменным.Опять же, ничего не происходит с рабочим деревом.
Введение хуков предварительной фиксации
Git запускает хук предварительной фиксации после подготовки любых дополнительных индексных файлов.Специальная переменная окружения $GIT_INDEX_FILE
называет индекс, который Git будет использовать для создания нового коммита.Итак, есть три случая, два из которых не так уж плохи, а другой ужасен:
- Вы делаете нормальный коммит.
GIT_INDEX_FILE
называет нормальный индекс, и все нормально. - Вы делаете
git commit --include
или git commit -a
и GIT_INDEX_FILE
, называет второй индекс;третьего индекса нет;если фиксация завершится, Git переименует второй индекс. - Вы делаете
git commit --only
и GIT_INDEX_FILE
имена третьего индекса.Не существует простого способа найти второго индекса, который будет существовать после фиксации, если фиксация будет успешной!
Ваша работа, если вы решите внести измененияк файлам, хранящимся в индексе, это сделать их индексом, который Git будет использовать для фиксации.Чтобы сделать это, вы можете использовать git add
, если хотите, так как это скопирует файлы из рабочего дерева в индекс, указанный в $GIT_INDEX_FILE
.
Первая проблема, однако, заключается в том, что не должен просматривать файлы в рабочем дереве .Они не имеют значения!Они могут содержать нечто совершенно отличное от того, что в индексе.Это особенно верно во время git commit --only
.
Вторая и более серьезная проблема заключается в том, что если вы обновили третий индекс, который использует git commit --only
, вам также следует обновить second index, который используется git commit --only
.Эта часть хитрая, потому что нет простого способа найти ее, кроме как предположить, что она в .git/index.lock
.Хотя это может сработать, я не буду советовать это здесь.
У меня действительно нет никаких предложений для этого - любой хитрый метод, который вы найдете, может сломаться, так как код для работы с этим третьим индексом (который является текущим 2.21-ишем)Git называет «фальшивый индекс») сильно изменился между 1.8 и современным Git.Обычная рекомендация - , а не , чтобы вообще выполнять какое-либо специальное форматирование в Git-хуке.Вместо этого, пусть Git hook просто проверяет , правильно ли индексная копия файла отформатирована: если это так, продолжите фиксацию, а если нет, отмените фиксацию.Оставьте остальное пользователю.
Еще одна альтернатива
Альтернатива, которую я видел и использовал, заключается в проверке фактической настройки $GIT_INDEX_FILE
.Если установлено значение .git/index
, пользователь использует git commit
без каких-либо специальных настроек.Еще одна хитрость в этой самой ловушке предварительной фиксации (которая вызывает clang-format и autopep8) - сравнить индекс и рабочее дерево для файлов, которые будут отформатированы, и отказаться от запуска, если они не совпадают.