Отменить ранее поставленные изменения (или: отменить изменения в .git / index) - PullRequest
1 голос
/ 09 марта 2019

Когда я пытался понять способы отмены различных операций git, я придумал сценарий, в котором я не уверен, как с этим справиться.Отказ от ответственности: у меня не было такой ситуации, когда я фактически работал с git «на производстве», но я все еще думаю, что это не только академический вопрос.

Давайте рассмотрим следующий сценарий

  • манипулировать файлом, который был зафиксирован ранее: echo "some content" >> example.txt
  • установить изменения: git add example.txt
  • оформить изменения с момента последнего принятия: git checkout @ -- example.txt
  • понять, что вы выбралинеправильный файл, и вы хотите отменить последнюю команду, чтобы вернуть ваши изменения ("some content")

То, что я думаю, происходит под капотом

Каждый раз, когда постановка меняется сgit add объект blob создается в .git / objects / , а индексный файл ( .git / index ) обновляется.Если я изменю и добавлю что-то несколько раз, будет много блобов.Старые не сразу собирают мусор.

При запуске команды checkout сверху индекс получает обновление немедленно (также я бы предположил, что содержимое будет только в моем рабочем каталоге, но без буфера).Таким образом, ссылка исчезла, и я не могу использовать такие вещи, как git checkout-index, чтобы вернуть их.

За исключением случаев, когда сборка мусора в контенте все еще существует технически.Но я не знаю, как бы получить его обратно, кроме ручной попытки как-то найти хеш и чтения содержимого с помощью git cat-file.Например, то же самое можно сказать о запуске git add несколько раз, хотя здесь возвращение ранее подготовленных изменений, возможно, на самом деле не имеет смысла.(Или, может быть, когда появляются изменения из тайника? ...)


Так что все это сводится к следующим вопросам:

  • Есть ли что-тонапример, git reflog для индекса?
  • Считается ли git checkout @ -- file такой опасной командой, как git reset --hard, где вы потенциально можете потерять работу?

А если ответы"Нет" / "Да" (что я предполагаю до сих пор):

  • Существуют ли сантехнические команды для ручного изменения / перезаписи индекса?(см. вышеописанный случай, когда объекты все еще там)

Бонус: есть ли альтернативный способ извлечения одного файла без мгновенной его постановки?

1 Ответ

2 голосов
/ 09 марта 2019

Ваше описание под капюшоном в основном верно.Единственные вещи, которые не на 100% связаны с этой частью:

Каждый раз, когда этап изменяется с git add, объект BLOB создается в .git / objects / * 1006.*

Внутренне, git add хэширует содержимое данных в файле рабочего дерева, как git hash-object -w -t blob.Это не обязательно создает новый объект: если хешированное содержимое уже в хранилище , оно просто повторно использует существующий объект.Существующий объект может быть упакован , т. Е. В .git/objects/pack, а не Свободный в качестве отдельного большого двоичного объекта.

Кроме того, содержимое записывается в объект большого двоичного объекта может быть произвольно отличным от содержимого в рабочем дереве из-за чистого фильтра .Чаще всего CR-LF-конец строки отличается от содержимого в рабочем дереве из-за настроек окончания строки.Чистые фильтры и настройки конца строки контролируются частично (или в основном, в зависимости от того, как вы используете Git) через ваш файл .gitattributes, и частично (или в основном) через настройки в вашей конфигурации.

В любомДело в том, что вы получаете хеш-идентификатор для объекта BLOB-объекта.Объект BLOB-объектов определенно существует где-то - в каталоге .git/objects как свободный объект или в файле пакета.Теперь git add может записывать в .git/index (или любой другой файл, который указывает GIT_INDEX_FILE): он будет хранить в индексе на нулевом временном интервале запись для данного path, используяВычисляем blob-хэш и режим 100644 или 100755 в зависимости от того, должен ли файл рабочего дерева быть помечен как исполняемый позже.

Если вы его потеряли, вам в основном не повезло

[Сценарий пропущен, но он заканчивается git checkout HEAD -- <em>path</em>, перекрывающим запись индекса, с $path, представляющим $blobhash и режимом $mode информация, и , перекрывающимкопия файла рабочего дерева в path.)

Если технически все еще нет места для сбора мусора в контенте.Но я не знаю, как бы получить его обратно, кроме ручной попытки как-то найти хеш и чтения содержимого с помощью git cat-file.

Действительно, вы не можете: вычисление хеш-идентификатораэто функция trapdoor , и только если у вас do есть хеш, вы можете заставить Git выдавать контент, но вам нужно иметь контент, если у вас нет хеша.Это ваша ситуация Catch-22 .

Если - это довольно важное «если» - контент был уникальным, так что git add действительно создал новый объект BLOB, и , вы только что переписали ссылку BLOB-объекта, которая была в индексе, на этот объект BLOB действительно больше нигде не ссылаются.С другой стороны, если git hash-object -w повторно использует существующий BLOB-объект, объект BLOB-объекта все еще ссылается на то, на что он ссылался ранее.Итак, теперь есть два интересных случая: BLOB-объект был уникальным и теперь пригоден для сбора мусора, или BLOB-объект не уникален и не является.

Использованиеgit fsck --lost-found или git fsck --unreachable или git fsck --dangling (по умолчанию), вы можете заставить Git обходить всю базу данных объектов, определять, какие объекты достижимы , а какие нет, и сообщать вам о некоторых или всехнедоступные и / или скопировать информацию из или о них в .git/lost-found.Если объект BLOB был недоступен, он будет включен в список как один из этих недоступных или висячих объектов или его содержимое будет восстановлено в .git/lost-found.

Недостатокздесь могут быть десятки или даже сотни объектов с висящими каплями.Ваша задача теперь переключилась с «угадать хеш» (практически невозможно) на «найти иголку в стоге сена» (не так сложно, но утомительно, и вы вполне могли бы найти неправильную иглу - это не совсем такстог сена, это в конце концов стека игл).И, конечно же, это работает только для случая "blob был уникальным".

Ответы на конкретные вопросы

(Tон, кстати, где этот вопрос не действительно дубликат Может git отменить извлечение неподготовленных файлов .Но этот по-прежнему полезен, так что посмотрите и его.)

Есть ли что-то вроде git reflog для индекса?

Нет.Вы можете создавать свои собственные резервные копии: просто cp .git/index где-нибудь.Но Git не делает это самостоятельно.Вы можете сделать его непосредственно перед операцией git checkout HEAD -- <em>path</em>, используя псевдоним или функцию оболочки, которую вы используете для выполнения этой опасной операции.

Обратите внимание, что Git не знает об этих резервных копиях, поэтомуgit gc не считает ссылочные объекты защищенными.Чтобы использовать резервные копии с сантехническими командами, такими как git ls-files, поместите имя пути в GIT_INDEX_FILE на время выполнения этой команды.

Является ли файл git checkout @ -- опасной командой, такой как git reset --hard где вы можете потерять свою работу?

Ответ на этот вопрос зависит от того, кто рассматривает вопрос.Я бы посоветовал считать это опасным для себя, так как вы задаете вопрос вообще.: -)

Существуют ли сантехнические команды для ручного изменения / перезаписи индекса?(см. вышеописанный случай, когда объекты все еще там)

Да: git update-index - это средство обновления по одной записи за раз (используйте --cacheinfo или --stdin для предоставления необработанных данныхданные для ввода в индекс, вместо того, чтобы дублировать большую часть git add работы).Многие другие команды также обновляют индекс частично или в массовом порядке.

Если у вас есть процесс, с помощью которого вы создаете резервную копию индекса перед операцией git checkout HEAD -- ..., вы можете прочитать записи из индекса резервного копирования (используя GIT_INDEX_FILE=... git ls-files, например), а затем используйте git update-index, без с установленным GIT_INDEX_FILE, чтобы поместить информацию в обычный индекс.Конечно, это операция перезаписи индекса y, вы можете сначала сделать еще одну резервную копию индекса.

Есть ли альтернативный способ извлечения отдельного файла без мгновенной его постановки?

Нет, но только из-за глагола checkout здесь.Чтобы просмотреть содержимое файла в индексе или в любом коммите, чтобы содержимое имело имя, понятное git rev-parse, используйте git show:

git show :file          # file in index at stage zero
git show :3:file        # file in index at stage three, during merge conflict
git show HEAD:file      # file in current commit
git show master~7:file  # file in commit 7 first-parent hops back from master

Обратите также внимание, что git reset может перезаписать один или несколько файлов в индексе, не касаясь файлов в рабочем дереве:

git reset HEAD -- file  # copy HEAD:file to :file leaving work-tree file undisturbed

Если вы указали git reset путь к каталогу, онсбрасывает все файлы, которые уже есть в индексе и находятся в каталоге.

...