Что значит Линус Торвальдс, когда говорит, что Git "никогда не отслеживает файл"? - программирование
Подтвердить что ты не робот

Что значит Линус Торвальдс, когда говорит, что Git "никогда не отслеживает файл"?

Цитируя Линуса Торвальдса, когда его спросили, сколько файлов может обработать Git во время его Tech Talk в Google в 2007 году (43:09):

... Git отслеживает ваш контент. Он никогда не отслеживает ни одного файла. Вы не можете отслеживать файл в Git. Что вы можете сделать, так это то, что вы можете отслеживать проект с одним файлом, но если у вашего проекта есть один файл, обязательно сделайте это, и вы можете это сделать, но если вы отслеживаете 10 000 файлов, Git никогда не увидит их как отдельные файлы. Git думает все как полный контент. Вся история в Git основана на истории всего проекта...

(Стенограммы здесь.)

Тем не менее, когда вы погружаетесь в книгу Git, первое, что вам говорят, это то, что файл в Git может быть отслежен или не отслежен. Более того, мне кажется, что весь опыт работы с Git направлен на управление версиями файлов. При использовании git diff или git status выводится отдельно для каждого файла. При использовании git add вы также можете выбирать для каждого файла отдельно. Вы даже можете просмотреть историю на файловой основе и молниеносно.

Как следует толковать это утверждение? С точки зрения отслеживания файлов, чем Git отличается от других систем контроля версий, таких как CVS?

4b9b3361

Ответ 1

В CVS история отслеживалась отдельно для каждого файла. Ветвь может состоять из различных файлов со своими собственными различными ревизиями, каждый со своим собственным номером версии. CVS был основан на RCS (Revision Control System), которая аналогичным образом отслеживала отдельные файлы.

С другой стороны, Git делает снимки состояния всего проекта. Файлы не отслеживаются и не имеют версий независимо; ревизия в хранилище относится к состоянию всего проекта, а не к одному файлу.

Когда Git ссылается на отслеживание файла, это просто означает, что он должен быть включен в историю проекта. Лайнус говорит не об отслеживании файлов в контексте Git, но противопоставляет модели CVS и RCS модели на основе снимков, используемой в Git.

Ответ 2

Я согласен с Брайаном М. Ответ Карлсона: Линус действительно проводит различие, по крайней мере частично, между файлово -ориентированными и коммит-ориентированными системами контроля версий. Но я думаю, что это еще не все.

В моей книге, которая зашла в тупик и, возможно, никогда не закончится, я попытался придумать таксономию для систем контроля версий. В моей таксономии термин, который нас интересует, - это атомарность системы контроля версий. Посмотрите, что в настоящее время находится на странице 22. Когда VCS имеет атомарность на уровне файлов, фактически существует история для каждого файла. VCS должен помнить имя файла и то, что происходило с ним в каждой точке.

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

Обратите внимание, что VCS может быть ориентированным на принятие, но все же хранить данные файл за файлом. Эта деталь реализации, хотя иногда и важная, и Git этого тоже не делает. Вместо этого каждый коммит записывает дерево с именами файлов, кодирующими объекты дерева, режимы (т.е. Исполняемый файл или нет?) И указатель на фактическое содержимое файла. Сам контент хранится независимо, в объекте BLOB-объекта. Подобно объекту фиксации, BLOB-объект получает идентификатор хеша, уникальный для его содержимого, но в отличие от коммита, который может появиться только один раз, BLOB-объект может появляться во многих коммитах. Таким образом, основное содержимое файла в Git хранится непосредственно в виде большого двоичного объекта, а затем косвенно в объекте дерева, чей хэш-идентификатор записывается (прямо или косвенно) в объект фиксации.

Когда вы просите Git показать вам историю файлов, используя:

git log [--follow] [starting-point] [--] path/to/file

Git на самом деле просматривает историю коммитов, которая является единственной историей Git, но не показывает вам ни одного из этих коммитов, если только:

  • коммит является коммитом без слияния, и
  • родитель этого коммита также имеет файл, но содержание в родительском коммите отличается, или у родителя коммита нет файла вообще

(но некоторые из этих условий могут быть изменены с помощью дополнительных параметров git log, и очень сложно описать побочный эффект, называемый упрощением истории, который заставляет Git полностью пропустить некоторые коммиты из истории). История файлов, которую вы видите здесь, в определенном смысле не существует точно в репозитории: вместо этого это всего лишь синтетическое подмножество реальной истории. Вы получите другую "историю файлов", если будете использовать разные опции git log !

Ответ 3

Запутанный бит здесь:

Git никогда не видит их как отдельные файлы. Git думает все как полный контент.

Git часто использует 160-битные хэши вместо объектов в своем репо. Дерево файлов - это в основном список имен и хэшей, связанных с содержимым каждого (плюс некоторые метаданные).

Но 160-битный хеш однозначно идентифицирует контент (в юниверсе базы данных git). Таким образом, дерево с хешами в качестве содержимого включает содержимое в своем состоянии.

Если вы изменяете состояние содержимого файла, его хэш изменяется. Но если его хеш изменяется, хеш, связанный с содержимым имени файла, также изменяется. Что, в свою очередь, меняет хэш "дерева каталогов".

Когда база данных git хранит дерево каталогов, это дерево каталогов подразумевает и включает в себя все содержимое всех подкаталогов и все файлы в нем.

Он организован в виде древовидной структуры с (неизменяемыми, многократно используемыми) указателями на BLOB-объекты или другие деревья, но логически это единый снимок всего содержимого всего дерева. Представление в базе данных git не является простым содержимым данных, но логически это все его данные и ничего больше.

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

Это может помочь думать о хешах git как о подсчитанном указателе на неизменяемые данные.

Если вы построили приложение вокруг этого, документ представляет собой группу страниц, которые имеют слои, которые имеют группы, которые имеют объекты.

Когда вы хотите изменить объект, вы должны создать для него совершенно новую группу. Если вы хотите изменить группу, вам нужно создать новый слой, которому нужна новая страница, которой нужен новый документ.

Каждый раз, когда вы меняете один объект, он порождает новый документ. Старый документ продолжает существовать. Новый и старый документ разделяют большую часть их содержимого - они имеют одинаковые страницы (кроме 1). Эта страница имеет одинаковые слои (кроме 1). Этот слой имеет те же группы (кроме 1). Эта группа имеет те же объекты (кроме 1).

И под тем же самым я имею в виду логически копию, но с точки зрения реализации это просто еще один указатель с подсчетом ссылок на тот же неизменный объект.

Git РЕПО очень похоже на это.

Это означает, что данный набор изменений git содержит свое сообщение коммита (в виде хеш-кода), содержит свое рабочее дерево и содержит родительские изменения.

Эти родительские изменения содержат свои родительские изменения, все назад.

Часть git-репо, которая содержит историю, является той цепочкой изменений. Эта цепочка изменений находится на уровне выше дерева "каталогов" - из дерева "каталогов" вы не можете однозначно попасть в набор изменений и цепочку изменений.

Чтобы узнать, что происходит с файлом, вы начинаете с этого файла в наборе изменений. У этого набора изменений есть история. Часто в этой истории существует один и тот же именованный файл, иногда с одинаковым содержимым. Если содержимое совпадает, файл не изменился. Если это не так, то есть изменения, и нужно сделать работу, чтобы понять, что именно.

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

Таким образом, git может комбинировать "историю файлов".

Но эта история файлов происходит от эффективного анализа "всего набора изменений", а не от ссылки на одну версию файла на другую.

Ответ 4

"git не отслеживает файлы" в основном означает, что коммиты git состоят из снимка дерева файлов, соединяющего путь в дереве с "blob" и графом коммитов, отслеживающим историю коммитов. Все остальное восстанавливается на лету такими командами, как "git log" и "git blame". Эта реконструкция может быть объяснена с помощью различных опций, насколько сложно искать изменения на основе файлов. Эвристика по умолчанию может определять, когда большой двоичный объект изменяется в дереве файлов без изменений, или когда файл связан с другим большим двоичным объектом, чем раньше. Механизмы сжатия, используемые Git, не слишком заботятся о границах BLOB/файлов. Если содержимое уже где-то находится, это позволит сохранить небольшой размер хранилища, не связывая различные BLOB-объекты.

Теперь это хранилище. У Git также есть рабочее дерево, и в этом рабочем дереве есть отслеживаемые и неотслеживаемые файлы. Только индексированные файлы записываются в индекс (область подготовки "кэш"), и только то, что там отслеживается, попадает в хранилище.

Индекс ориентирован на файл, и есть некоторые ориентированные на файл команды для управления им. Но то, что заканчивается в репозитории, это просто коммиты в виде снимков дерева файлов и связанных с ними данных BLOB-объектов и предков коммитов.

Так как Git не отслеживает историю файлов и переименовывает и их эффективность не зависит от них, иногда вам приходится несколько раз пытаться использовать разные опции, пока Git не выдаст интересующую вас историю /diffs/blames для нетривиальных историй.

Это отличается от таких систем, как Subversion, которые записывают, а не восстанавливают истории. Если это не записано, вы не услышите об этом.

Я на самом деле создал разностный установщик, который сравнивал деревья релизов, проверяя их в Git, а затем создавал сценарий, дублирующий их эффект. Так как иногда целые деревья перемещались, это приводило к гораздо меньшим разностным установщикам, чем перезапись/удаление всего, что могло бы произойти.

Ответ 5

Git не отслеживает файл напрямую, но отслеживает снимки репозитория, и эти снимки состоят из файлов.

Вот способ посмотреть на это.

В других системах контроля версий (SVN, Rational ClearCase) вы можете щелкнуть правой кнопкой мыши файл и получить его историю изменений.

В Git нет прямой команды, которая делает это. Смотрите этот вопрос. Вы будете удивлены тем, как много разных ответов. Нет простого ответа, потому что Git не просто отслеживает файл, не так, как это делает SVN или ClearCase.

Ответ 6

Кстати, отслеживание "контента" привело к тому, что пустые каталоги не отслеживались.
Вот почему, если вы нажмете последний файл в папке, сама папка будет удалена.

Это не всегда так, и только Git 1.4 (май 2006 г.) применил эту политику "отслеживания контента" с коммитом 443f833:

git status: пропустить пустые каталоги и добавить -u, чтобы показать все неотслеживаемые файлы

По умолчанию мы используем --others --directory чтобы показывать неинтересные каталоги (чтобы привлечь внимание пользователя) без их содержимого (чтобы не перегружать вывод).
Показывать пустые каталоги не имеет смысла, поэтому передайте --no-empty-directory когда мы это сделаем.

Предоставление -u (или --untracked) отключает этот беспорядок, позволяя пользователю получить все неотслеживаемые файлы.

Это было отражено годами позже в январе 2011 года с коммитом 8fe533, Git v1.7.4:

Это соответствует общей философии пользовательского интерфейса: git отслеживает содержимое, а не пустые каталоги.

Тем временем, с Git 1.4.3 (сентябрь 2006 г.), Git начинает ограничивать неотслеживаемый контент непустыми папками с коммитом 2074cb0:

он не должен перечислять содержимое полностью неотслеживаемых каталогов, а только имя этого каталога (плюс завершающий символ ' / ').

Отслеживание контента - это то, что позволило git обвинить в самом начале (Git 1.4.4, октябрь 2006, commit cee7f24) быть более производительным:

Что еще более важно, его внутренняя структура предназначена для более легкой поддержки перемещения контента (он же вырезать -a nd-paste), позволяя использовать несколько путей из одного и того же коммита.

Это (отслеживание содержимого) - это то, что добавило git add в Git API с Git 1.5.0 (декабрь 2006, commit 366bfcb)

сделать 'git add' первоклассным удобным интерфейсом для индекса

Это раскрывает всю мощь индекса, используя правильную ментальную модель, вообще не говоря об индексе.
Посмотрите, например, как все технические обсуждения были удалены из справочной страницы git -a dd.

Любой контент, который будет зафиксирован, должен быть добавлен вместе.
Приходит ли этот контент из новых файлов или измененных файлов, не имеет значения.
Вам просто нужно "добавить" его, либо с помощью git -a dd, либо предоставив git-commit с -a (конечно, только для уже известных файлов).

Именно это и позволило сделать git add --interactive с тем же Git 1.5.0 (commit 5cde71d)

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

Вот почему для рекурсивного удаления всего содержимого из каталога вам нужно передать параметр -r, а не просто имя каталога в качестве <path> (все еще Git 1.5.0, commit 9f95069).

Просмотр содержимого файла вместо самого файла позволяет сценарию слияния, подобному сценарию, описанному в коммите 1de70db (Git v2.18.0 -r c0, апрель 2018 г.)

Рассмотрим следующее слияние с конфликтом переименования/добавления:

  • сторона A: изменить foo, добавить несвязанный bar
  • сторона B: переименуйте foo->bar (но не изменяйте режим или содержимое)

В этом случае трехстороннее слияние оригинальных foo, A foo и B bar приведет к желаемому пути path для bar с тем же режимом/содержимым, что и A для foo.
Таким образом, у A был правильный режим и содержимое для файла, и у него был правильный путь (а именно, bar).

Зафиксировать 37b65ce, Git v2.21.0 -r c0, декабрь 2018 года, недавно улучшенные разрешения конфликтующих конфликтов.
И фиксация bbafc9c еще раз иллюстрирует важность рассмотрения содержимого файла, улучшая обработку конфликтов переименования/переименования (2to1):

  • Вместо хранения файлов в collide_path~HEAD и collide_path~MERGE, файлы объединяются в двух направлениях и записываются в collide_path.
  • Вместо записи версии переименованного файла, которая существовала на переименованной стороне в индексе (игнорируя, таким образом, любые изменения, внесенные в файл на стороне истории без переименования), мы выполняем трехстороннее объединение контента с переименованным путь, затем сохраните его на этапе 2 или 3.
  • Обратите внимание, что, поскольку слияние содержимого для каждого переименования может иметь конфликты, и тогда мы должны объединить два переименованных файла, мы можем получить вложенные маркеры конфликта.