Отменить ранее внесенные изменения (или: отменить изменения в .git / index)

Пытаясь понять способы отмены различных операций 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, из-за которой вы потенциально можете потерять свою работу?

И если ответы будут «Нет» / «Да» (что я предполагаю до сих пор):

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

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


person mvo    schedule 09.03.2019    source источник
comment
Возможный дубликат Может git отменить извлечение неустановленных файлов   -  person phd    schedule 09.03.2019
comment
stackoverflow.com/search?q=%5Bgit%5D+undo+checkout   -  person phd    schedule 09.03.2019


Ответы (1)


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

Каждый раз при постановке изменений с git add объект blob создается в .git / objects /

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Обратите внимание, что 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, поместить информацию в обычный указатель. Конечно, поскольку это операция перезаписи индекса, вы можете сначала сделать еще одну резервную копию индекса.

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

Нет, но только из-за глагола 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 путь к каталогу, он сбрасывает все файлы, которые уже находятся в индексе и находятся в этом каталоге.

person torek    schedule 09.03.2019
comment
Большое спасибо за эту информативную и подробную статью! Это очень помогает понять, что происходит на самом деле. - person mvo; 09.03.2019