Как git знает об изменении имени файла с одного большого двоичного объекта? - PullRequest
1 голос
/ 08 апреля 2020

Я создал чистый каталог и начал отслеживать его с помощью git, и я отслеживаю .git каталог с помощью этой команды:

tree -C -I 'info|pack' .git/objects

Я создаю файл с именем readme.md с содержимым hello world и установил это, используя git add . git, создающий BLOB-объект:

.git/objects
└── 3b
    └── 18e512dba79e4c8300dd08aeb37f8e728b8dad

Насколько я знаю, BLOB-объект знает об имени файла, и в каталоге .git нет другого объекта git

├── objects
│   ├── 3b
│   │   └── 18e512dba79e4c8300dd08aeb37f8e728b8dad
│   ├── info
│   └── pack
└── refs
    ├── heads
    └── tags

но когда я переименовываю файл в readyou.md и запускаю git status


On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
    new file:   readme.md

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
    deleted:    readme.md

Untracked files:
  (use "git add <file>..." to include in what will be committed)
    readyou.md

git полностью осведомлен об изменениях, поэтому он должен где-то отслеживать имя файла если это не капля, то где она?

1 Ответ

1 голос
/ 09 апреля 2020

Это не имеет ничего общего с внутренним форматом хранения больших двоичных объектов, а также с фактическими командами, которые вы выполняли.

В частности, вы сделали:

git init
echo hello world > readme.md
git add readme.md

Пока что хорошо: вы Git скопировали файл readme.md в индекс Git. Поскольку индекс на самом деле не содержит копий файловых данных, Git должен был создать внутренний объект blob, который вы упомянули; что теперь в индексе, если вы выгрузите его с помощью git ls-files --stage или git ls-files --debug, это одна запись с режимом 100644 (обычный файл), именем readme.md и BLOB-объектом ha sh.

Но теперь вместо git mv readme.md readyou.md или какой-либо другой эквивалентной серии команд вы запустили:

mv readme.md readyou.md

Это переименовало вашу рабочую елку копию файла. Файл в вашем рабочем дереве управляется вашей операционной системой, а не Git. Копия в вашем рабочем дереве - это не то, что go войдет в следующий коммит: то, что go войдет в следующий коммит, это то, что содержится в индексе Git. Это совершенно без изменений.

Ваша команда git status работает следующим образом:

  1. Вывод информации о текущей ветви и подобных вещах. В настоящее время вы используете ветку нерожденный или ветку orphan (Git использует для этого оба термина): имя master хранится в .git/HEAD, но ни одна ветка не называется master на самом деле существует, потому что никаких коммитов еще не существует. Итак, первая строка git status говорит об этом.

  2. Сравните текущий коммит с индексом. Пока нет текущего коммита, поэтому Git использует вместо Git пустое дерево в качестве левой части этого сравнения. Результатом сравнения является объявление о появлении нового файла с именем readme.me, который будет go в следующем коммите. Это ваши изменения, которые нужно зафиксировать .

  3. Сравните индекс с рабочим деревом. Индекс содержит файл с именем readme.md, а рабочее дерево - нет. Так что readme.md удалено. Рабочее дерево содержит файл с именем readyou.md, а индекс - нет; Git держит это на мгновение. В результате этого сравнения объявляется, что файл readme.md удален, но это ваши изменения, не подготовленные для фиксации .

  4. Наконец, поскольку есть Файлы рабочего дерева, которых нет в индексе, Git фильтрует этот список по записям .gitignore. (Технически это действительно происходит при сканировании рабочего дерева: нет смысла помещать файлы в список, а затем выбрасывать их, когда Git может вообще не включать их в список.) Поскольку в .gitignore записей нет *, 1231 * не не замолкает об этом: он печатает имена этих файлов в виде неотслеживаемых файлов .

Ключ ко всем это означает, что Git имеет эту дополнительную копию 1 каждого файла в индексе Git, и что git commit создаст новый коммит из индекса. Git даже не будет смотреть на ваше дерево работы здесь. Это то, что в индексе имеет значение. Команда git status не показывает, что находится в индексе; вместо этого он показывает, что отличается в индексе, дважды: один раз по сравнению с текущим коммитом (или пустым деревом в этом специальном состоянии) и один раз по сравнению с вашим рабочим деревом, если предположить что вы действительно можете видеть, что находится в вашем рабочем дереве.

Если индекс соответствует рабочему дереву, вы ничего не сможете скопировать из рабочего дерева, в индекс, чтобы изменить его. Так что это неинтересно, и последние два git status выходных раздела - изменения не подготовлены и неотслеживаемые файлы - пусты. Если индекс соответствует текущему commit , нет никакой причины делать новый коммит, так что это не интересно, и раздел выходных данных изменений, подготовленных для фиксации, пуст.


1 Опять же, индекс действительно содержит кортежи (mode, name, blob-ha sh). Он также имеет кучу данных кеша, поэтому его иногда называют кеш , хотя в наши дни кеш должен быть зарезервирован для структуры данных в памяти, которая Git строит, читая файл .git/index. Поскольку вы используете индекс для "подготовки" обновленных файлов для фиксации, индекс также называется промежуточной областью .


Обратите внимание, что git mv ничем не особенным

Если вы используете git mv readme.md readyou.md, Git:

  • переименовывает файл рабочего дерева, а
  • удаляет старый readme.md индексировать запись и вставить вместо нее readyou.md запись.

git status теперь просто скажет, что существует новый файл readyou.md, готовый для фиксации. Но предположим, что вы сначала фиксируете файл readme.md. Теперь git status скажет, что переименовано готово к фиксации.

Технически, все, что произойдет, если вы сделаете второй коммит, это то, что у второго коммита будет файл с именем readyou.md и не имеют файл с именем readme.md. Это механизм различий Git - программа, которую вы можете вызвать с помощью git diff, которая также вызывает git status и другие команды Git, - решающие, что этот файл был «переименован».

Если вместо git mv вы запустите:

mv readme.md readyou.md
git add readme.md readyou.md

, отдельный шаг git add здесь удалит readme.md из индекса и скопирует readyou.md в индекс ( снова см. сноску 1 - она ​​действительно просто сохранит существующий объект BLOB-объекта). Как ни странно, git add <em>path/to/file</em> удаляет path/to/file из индекса, если он есть в индексе и , которого нет в рабочем дереве. Чтобы понять это, нужно понять, что git add означает не просто копировать файл из рабочего дерева в индекс , но вместо этого означает нечто более простое: сделать индексное соответствие рабочему дереву . «Совпадение» в случае переименованного или удаленного файла означает удаление старого имени.

Когда Git сравнивает два коммита, или один коммит и индекс, или индекс и ваше рабочее дерево или что-то еще, Git обнаружит переименование только при определенных условиях. К ним относятся:

  • Детектор переименования должен быть включен . Когда вы запускаете git diff, вы можете включить его вручную с помощью -M или --find-renames. По умолчанию он был выключен в старых версиях Git и переключен на на по умолчанию в Git 2.9. Вы можете настроить собственное частное значение по умолчанию с помощью ручки настройки diff.renames.

  • Файл content должен либо точно соответствовать - точное совпадение для Git - либо быть "достаточно похожим". Это сходство выражается в процентах: точное совпадение на 100% похоже, а файл без общих байтов похож на 0%. Файлы, которые не идентичны, но имеют несколько общих байтов, что определяется быстрым и грязным сканером файлов, в некоторой степени похожи: больше нуля, меньше 100. При использовании --find-renames или -M вы можете установить сходство порог. По умолчанию 50%. Файлы, которые по крайней мере не равны 50 или любому другому процентному значению, считаются «не переименованными».

  • Чтобы Git обнаружил переименование, должен быть файл на «левая сторона», которой просто нет на «правой стороне», и наоборот. Например, в этом случае левая сторона - первый коммит - будет иметь файл с именем readme.md, а правая сторона - текущее содержимое индекса - не будет. Правая сторона имеет readyou.md, а левая сторона - нет. Git объединяет файлов левой и правой сторон с одинаковым именем . Любые оставшиеся файлы, то есть не спаренные на данный момент, являются кандидатами для обнаружения переименования.

  • При использовании git diff есть еще кое-что, что вы можете сделать с помощью -B (перерыв -пары) вариант. Вы не можете сделать это с помощью git status, поэтому мы не будем вдаваться в подробности go.

В команде git status детектор переименования включен и установлен на 50 % по умолчанию. В более старых версиях Git изменить это невозможно, кроме Git 2. 18 добавил status.renames в качестве опции и заставил ее следовать diff.renames по умолчанию, если она не установлена. Так что теперь diff.renames также контролирует git status, если только вы не установили diff.renames и status.renames.

Смысл всего этого словоблудия в том, что git mv не совсем специальный . Он просто заботится о двух операциях переименования одновременно: одна в индексе и одна в вашем рабочем дереве. Если вы забыли, просто используйте git add, чтобы удалить старое имя и добавить новое. Эффект точно такой же: сделанный вами следующий коммит, который хранится сейчас в индексе Git, теперь корректно обновляется.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...