Что значит Линус Торвальдс, когда говорит, что Git «никогда не отслеживает файл»? - PullRequest
280 голосов
/ 10 апреля 2019

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

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

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

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

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

Ответы [ 6 ]

311 голосов
/ 10 апреля 2019

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

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

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

103 голосов
/ 10 апреля 2019

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

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

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

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

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

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

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

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

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

15 голосов
/ 10 апреля 2019

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

12 голосов
/ 10 апреля 2019

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

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

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

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

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

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

7 голосов
/ 10 апреля 2019

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

Вот способ взглянуть на это.

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

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

3 голосов
/ 16 апреля 2019

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Это то, что сделало возможным git add --interactive с тем же Git 1.5.0 ( commit 5cde71d )

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

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

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

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

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

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

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

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