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

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

…Git tracks your content. It never ever tracks a single file. You cannot track a file in Git. What you can do is you can track a project that has a single file, but if your project has a single file, sure do that and you can do it, but if you track 10,000 files, Git never ever sees those as individual files. Git thinks everything as the full content. All history in Git is based on the history of the whole project…

(Transcripts here.)

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

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

reddit.com/r/git/comments/5xmrkv/what_is_a_snapshot_in_git — «Для того, где вы находитесь в данный момент, я подозреваю что более важно понимать, так это то, что есть разница между тем, как Git представляет файлы пользователям, и тем, как он обрабатывает их внутри.. В представленном пользователю моментальном снимке содержатся полные файлы, а не просто различия. Но внутри Git да, использует различия для создания паковых файлов, которые эффективно хранят ревизии». (Это резко контрастирует, например, с Subversion.)
user2864740 10.04.2019 01:47

Git не отслеживает файлы, он отслеживает наборы изменений. Большинство систем контроля версий отслеживают файлы. В качестве примера того, как/почему это может иметь значение, попробуйте проверить пустой каталог в git (spolier: вы не можете, потому что это «пустой» набор изменений).

Elliott Frisch 10.04.2019 01:52

@ElliottFrisch Это звучит неправильно. Ваше описание ближе к тому, что, например. дарки делает. Git хранит снимки, а не наборы изменений.

melpomene 10.04.2019 02:02

@melpomene From здесь - Да, наборы изменений поддерживаются, и есть некоторая гибкость в их создании. Но, возможно, внутренности разные; вот почему это был комментарий.

Elliott Frisch 10.04.2019 02:05

@ElliottFrisch Да, я почти уверен, что это отличается от того, как на самом деле работает git. Git хранит коммиты в виде моментальных снимков; Затем наборы изменений / различия могут быть сгенерированы «на лету», когда пользователь их запрашивает (просматривая различия между содержимым файлов в двух моментальных снимках). Причина, по которой вы не можете проверить пустой каталог, заключается в том, что git не верит в каталоги; он касается только содержимого файлов (которые хранятся под определенным именем, что может подразумевать существование промежуточных каталогов).

melpomene 10.04.2019 02:12

Я думаю, он имеет в виду, что Git не отслеживает файл напрямую. Файл включает в себя свое имя и содержимое. Git отслеживает содержимое как большие двоичные объекты. Учитывая только большой двоичный объект, вы не можете сказать, каково соответствующее ему имя файла. Это может быть содержимое нескольких файлов с разными именами по разным путям. Связи между именем пути и большим двоичным объектом описываются в объекте дерева.

ElpieKay 10.04.2019 02:20

Связано: продолжение выступления Линуса Рэндала Шварца (также выступление Google Tech) - «... Что такое Git на самом деле ... Линус сказал, чем Git НЕ является».

Peter Mortensen 10.04.2019 04:07
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
289
8
29 556
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Ответ принят как подходящий

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

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

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

Вы можете добавить, что именно поэтому в CVS и Subversion вы можете использовать такие теги, как $Id$ в файле. То же самое не работает в git, потому что дизайн другой.

gerrit 10.04.2019 10:01

И содержимое не привязано к файлу, как можно было бы ожидать. Попробуйте переместить 80% кода одного файла в другой. Git автоматически определяет перемещение файла + 20% изменение, даже если вы только что переместили код в существующие файлы.

allo 10.04.2019 14:55

@allo В качестве побочного эффекта git может делать то, что другие не могут: когда два файла объединяются и вы используете «git виноват -C», git может просматривать обе истории. При отслеживании на основе файлов вы должны выбрать, какой из исходных файлов является настоящим оригиналом, а все остальные строки выглядят совершенно новыми.

Izkata 10.04.2019 23:05

@allo, Izkata - И именно запрашивающая сущность обрабатывает все это, анализируя содержимое репо во время запроса (истории фиксации и различия между ссылочными деревьями и большими двоичными объектами), вместо того, чтобы требовать от комитент и его пользователя-человека правильного указания или синтеза этой информации. во время фиксации — ни разработчику инструмента репо для разработки и реализации этой возможности и соответствующей схемы метаданных до развертывания инструмента. Торвальдс утверждал, что такой анализ со временем станет только лучше, и история все git-репозитория каждый с первого дня выиграет.

Jeremy 11.04.2019 14:47

@allo Да, и чтобы понять тот факт, что git не работает на уровне файлов, вам даже не нужно сразу фиксировать все изменения в файле; вы можете зафиксировать произвольные диапазоны строк, оставив другие изменения в файле за пределами фиксации. Конечно, пользовательский интерфейс для этого далеко не так прост, поэтому большинство не делают этого, но он редко используется.

Alvin Thompson 17.04.2019 04:37

Ну, я понимаю, что git делает снимок всего контента, используя sha1. Но как git замечает изменение файла?

alpham8 24.04.2019 20:21

@allo, «Попробуйте переместить 80% кода из одного файла в другой. Git автоматически определяет перемещение файла + изменение на 20%, даже если вы только что переместили код в существующих файлах». Что вы имеете в виду, что он обнаруживает перемещение файла? Я думал, что это не связывает содержимое с файлом?

doobeedoobee 12.06.2020 09:52

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

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

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

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

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

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

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

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

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

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

Wes Toleman 10.04.2019 05:42

@WesToleman: это определенно упрощает задачу. Mercurial хранит дельты со случайными сбросами, и хотя разработчики Mercurial намерены добавить туда неглубокие клоны (что возможно благодаря идее «сброса»), на самом деле они еще этого не сделали (потому что это скорее техническая проблема).

torek 10.04.2019 07:32

@torek У меня есть сомнения относительно вашего описания того, как Git отвечает на запрос истории файлов, но я думаю, что это заслуживает отдельного вопроса: stackoverflow.com/questions/55616349/…

Simón Ramírez Amaya 10.04.2019 17:34

@torek Спасибо за ссылку на вашу книгу, больше ничего подобного не видел.

gnarledRoot 17.04.2020 15:21

@Паоло Нет; Мне нужно вернуться к работе над ним в какой-то момент.

torek 04.07.2020 23:12

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

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

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

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

Я думаю, что понимаю, что вы пытаетесь сказать, но «в Git нет прямой команды, которая делает это» прямо противоречит ответам на вопрос, на который вы ссылаетесь. Хотя верно то, что управление версиями происходит на уровне всего репозитория, обычно в Git есть множество способов добиться что-нибудь, поэтому наличие нескольких команд для отображения истории файла не является доказательством многого.

Joe Lee-Moyet 10.04.2019 11:55

Я просмотрел первые несколько ответов на вопрос, который вы связали, и все они используют git log или какую-то программу, построенную поверх этого (или какой-то псевдоним, который делает то же самое). Но даже если бы было много разных способов, как говорит Джо, это также верно для отображения истории филиалов. (также встроен git log -p <file> и делает именно это)

Voo 10.04.2019 13:21

Вы уверены, что SVN внутренне сохраняет изменения для каждого файла? Я уже не использовал его некоторое время, но смутно помню, что у меня были файлы с именами, похожими на идентификаторы версий, а не на отражение файловой структуры проекта.

Artur Biesiadowski 11.04.2019 11:36

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

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

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

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

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

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

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

Git never ever sees those as individual files. Git thinks everything as the full content.

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

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

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

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

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

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

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

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

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

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

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

Репозиторий git очень похож на этот.

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

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

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

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

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

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

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

Кстати, отслеживание «контента» привело к тому, что пустые каталоги не отслеживались. Вот почему, если вы git rm последний файл папки, сама папка удаляется.

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

git status: skip empty directories, and add -u to show all untracked files

By default, we use --others --directory to show uninteresting directories (to get user's attention) without their contents (to unclutter output).
Showing empty directories do not make sense, so pass --no-empty-directory when we do so.

Giving -u (or --untracked) disables this uncluttering to let the user get all untracked files.

Спустя годы, в январе 2011 года, это повторилось в совершить 8fe533, Git v1.7.4:

This is in keeping with the general UI philosophy: git tracks content, not empty directories.

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

it should not list the contents of completely untracked directories, but only the name of that directory (plus a trailing '/').

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

More importantly, its internal structure is designed to support content movement (aka cut-and-paste) more easily by allowing more than one paths to be taken from the same commit.

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

make 'git add' a first class user friendly interface to the index

This brings the power of the index up front using a proper mental model without talking about the index at all.
See for example how all the technical discussion has been evacuated from the git-add man page.

Any content to be committed must be added together.
Whether that content comes from new files or modified files doesn't matter.
You just need to "add" it, either with git-add, or by providing git-commit with -a (for already known files only of course).

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

After making the selection, answer with an empty line to stage the contents of working tree files for selected paths in the index.

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

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

Consider the following merge with a rename/add conflict:

  • side A: modify foo, add unrelated bar
  • side B: rename foo->bar (but don't modify the mode or contents)

In this case, the three-way merge of original foo, A's foo, and B's bar will result in a desired pathname of bar with the same mode/contents that A had for foo.
Thus, A had the right mode and contents for the file, and it had the right pathname present (namely, bar).

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

  • Instead of storing files at collide_path~HEAD and collide_path~MERGE, the files are two-way merged and recorded at collide_path.
  • Instead of recording the version of the renamed file that existed on the renamed side in the index (thus ignoring any changes that were made to the file on the side of history without the rename), we do a three-way content merge on the renamed path, then store that at either stage 2 or stage 3.
  • Note that since the content merge for each rename may have conflicts, and then we have to merge the two renamed files, we can end up with nested conflict markers.

Другие вопросы по теме