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


283

Цитуючи Лінуса Торвальда, коли його запитали, скільки файлів може працювати Git під час свого технічного спілкування в Google у 2007 році (43:09):

… Git відстежує ваш вміст. Він ніколи не відстежує жодного файлу. Ви не можете відстежувати файл у Git. Що ви можете зробити, це ви можете відстежувати проект, який має один файл, але якщо у вашого проекту є один файл, обов’язково зробіть це, і ви можете це зробити, але якщо ви відстежуєте 10000 файлів, Git ніколи не бачить їх як окремі файли. Гіт вважає все повноцінним змістом. Вся історія в Git заснована на історії всього проекту ...

(Стенограми тут .)

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

Як слід трактувати це твердження? Що стосується відстеження файлів, чим Git відрізняється від інших систем управління джерелами, таких як CVS?


20
reddit.com/r/git/comments/5xmrkv/what_is_a_snapshot_in_git - "Тому, де ти зараз перебуваєш, я підозрюю, що важливіше усвідомити - це різниця між тим, як Git представляє файли користувачам, і тим, як він працює з ними внутрішньо . Як представлено користувачеві, знімок містить повноцінні файли, а не просто розходження. Але всередині, так, Git використовує diff для створення пакетних файлів, які ефективно зберігають зміни. " (Це різко протиставляється, наприклад, Subversion.)
user2864740

5
Git не відстежує файли, він відстежує набори змін . Більшість систем контролю версій відслідковують файли. Як приклад того, як / чому це може мати значення, спробуйте перевірити в порожній директорії git (сполір: ви не можете, тому що це "порожній" набір змін).
Елліот Фріш

12
@ElliottFrisch Це не правильно. Ваш опис ближче до того, що, наприклад, робить darcs . Git зберігає знімки, а не набори змін.
Мельпомена

4
Я думаю, що він означає, що Git не відстежує файл безпосередньо. Файл включає його ім'я та вміст. Git відстежує вміст як краплі. Враховуючи лише крапку, ви не можете сказати, яке ім'я відповідного файлу. Це може бути вміст декількох файлів з різними іменами під різними шляхами. Прив'язки між назвою контуру та крапкою описані в дереві об'єкта.
ElpieKay

3
Пов’язано з цим: "Підтримка Рандала Шварца до розмови Лінуса (також розмова Google Tech) -" ... Про що Git насправді йдеться ... Лінус сказав, що Git НЕ ".
Пітер Мортенсен

Відповіді:


316

У CVS історію відстежували на основі файлів. Гілка може складатися з різних файлів з власними різними версіями, кожен з яких має свій номер версії. CVS базувався на RCS ( Revision Control System ), яка аналогічно відстежувала окремі файли.

З іншого боку, Git робить знімки стану всього проекту. Файли не відстежуються та не переглядаються незалежно; редакція у сховищі стосується стану всього проекту, а не одного файлу.

Коли Git посилається на відстеження файлу, це означає, що він повинен бути включений в історію проекту. Розмова Лінуса не стосувалася відстеження файлів у контексті Git, але контрастувала модель CVS та RCS із моделлю на основі знімків, що використовується в Git.


4
Ви можете додати, що саме тому в CVS та Subversion можна використовувати теги, як $Id$у файлі. Це ж не працює в git, тому що дизайн інший.
Герріт

58
І вміст не пов'язаний з файлом, як ви очікували. Спробуйте перемістити 80% коду одного файлу в інший. Git автоматично виявляє переміщення файлу + 20% змін, навіть коли ви просто перемістили код у існуючих файлах.
Алло

13
@allo Як побічний ефект цього, git може зробити одне, що інші не можуть: коли два файли об'єднані і ви використовуєте "git blama -C", git може переглянути вниз обидві історії. У відстеженні на основі файлів ви повинні вибрати, який з оригінальних файлів є справжнім оригіналом, а інші рядки виглядають абсолютно новими.
Ізката

1
@allo, Izkata - І саме запитуючий суб'єкт працює над цим, аналізуючи вміст репо в час запиту (фіксуючи історії та відмінності між посилаються на дерева та краплі), а не вимагаючи від суб'єкта, що здійснює зобов'язання, та його людського користувача правильно вказати чи синтезувати ця інформація під час фіксації - а також розробник інструмента репо для розробки та реалізації цієї можливості та відповідної схеми метаданих перед розгортанням інструменту. Торвальдс стверджував, що такий аналіз з часом лише покращиться, і вся історія кожного git repo з першого дня піде на користь.
Джеремі

1
@allo Yep, і щоб перешкодити домашньому факту, що git не працює на рівні файлу, вам навіть не доведеться робити всі зміни у файлі відразу; ви можете зробити довільні діапазони рядків, залишаючи інші зміни у файлі поза комітом. Звичайно, користувальницький інтерфейс для цього не настільки простий, тому більшість не робить цього, але він рідко має свої можливості.
Елвін Томпсон,

103

Я згоден з Брайаном м. відповідь Карлсона : Linus дійсно розрізняє, принаймні частково, між файлами, орієнтованими на файли та орієнтованими на коміти системи управління версіями. Але я думаю, що в цьому є більше.

У своїй книзі , яка зупиняється і ніколи не закінчиться, я спробував придумати систематику для систем управління версіями. У моїй систематиці термін, що нас тут цікавить, - це атомність системи управління версіями. Подивіться, що наразі є на сторінці 22. Коли VCS має атомність на рівні файлу, насправді існує історія кожного файлу. VCS повинен запам'ятати ім'я файлу та те, що з ним траплялось у кожній точці.

Git цього не робить. У Git є лише історія комітетів - команда - це її одиниця атомності, а історія - це набір комісій у сховищі. Що запам'ятовує фіксація - це дані - ціле дерево, наповнене іменами файлів і вмістом, що постачаються з кожним із цих файлів, а також деякі метадані: наприклад, хто здійснив виконання, коли і чому, і внутрішній ідентифікаційний код хеша Git батьківського комітету . (Саме ця батьківська графіка та спрямована ациклічна графіка, сформована при читанні всіх комітетів та їх батьків, - це історія в сховищі.)

Зауважте, що VCS може бути орієнтований на коммісію, але все ще зберігає дані по файлу. Це деталізація реалізації, хоча іноді важлива, і Git теж цього не робить. Натомість, кожен фіксатор записує дерево , в якому об'єкт дерева кодує назви файлів , режими (тобто виконується цей файл чи ні?) Та вказівник на власне вміст файлу . Сам контент зберігається незалежно, в об'єкті, що випадає . Як і об’єкт фіксації, blob отримує ідентифікаційний хеш-код, який є унікальним за його вмістом, але на відміну від фіксації, яка може з’явитися лише один раз, blob може з’являтися у багатьох комітах. Тож базовий вміст файлу в Git зберігається безпосередньо як крапка, а потім опосередковано в об'єкті дерева, чий хеш-код зафіксовано (прямо чи опосередковано) в об'єкті фіксації

Коли ви попросите Git показати вам історію файлу, використовуючи:

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

то , що Git насправді робить це ходьба зробити історію, яка є тільки історія Git є, але не показуючи вам будь-який з цих фіксацій , якщо:

  • "виконувати" - це зобов'язання без злиття;
  • у батьків цього комітету також є файл, але вміст у батьківському файлі відрізняється, або у батьківського файлу файлу взагалі немає файлу

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


Ще одна річ, яку слід додати, це дозволяє Git робити такі речі, як дрібні клони. Це просто потрібно, щоб знайти голову і всі краплі, на які вона посилається. Не потрібно відтворювати файли, застосовуючи набори змін.
Уес

@WesToleman: це, безумовно, робить це простіше. Дельти магазинів Mercurial із випадковими скидами, і хоча люди з Меркуріалу мають намір додати туди неглибокі клони (що можливо завдяки ідеї "скидання"), вони насправді цього ще не зробили (тому що це більше технічна проблема).
Торека

@torek У мене є сумніви щодо вашого опису щодо відповіді на Git на запит історії файлів, але я вважаю, що він заслуговує власного власного питання: stackoverflow.com/questions/55616349/…
Simón Ramírez Amaya

@torek Дякую за посилання на вашу книгу, я нічого іншого подібного не бачив.
gnarledRoot

17

Конфузний біт тут:

Git ніколи не сприймає їх як окремі файли. Гіт вважає все повноцінним змістом.

Git часто використовує 160-бітові хеші на місці об'єктів у власній репо-репортажі. Дерево файлів - це в основному список імен та хешей, пов’язаних із вмістом кожного (плюс деякі метадані).

Але 160-бітний хеш однозначно ідентифікує вміст (всередині бази даних git). Отже дерево з хешами як вміст включає вміст у своєму стані.

Якщо ви зміните стан вмісту файлу, його хеш зміниться. Але якщо його хеш змінюється, змінюється і хеш, пов’язаний із вмістом імені файлу. Що в свою чергу змінює хеш "дерева каталогів".

Коли база даних git зберігає дерево каталогів, це дерево каталогів має на увазі і включає весь вміст усіх підкаталогів та всіх файлів у ньому .

Він організований у структурі дерева з (непорушними, багаторазовими) покажчиками на цвітіння чи інші дерева, але логічно це єдиний знімок всього вмісту всього дерева. Подання в базі даних мерзотника не плоский зміст даних, але логічно це все його дані і нічого іншого.

Якщо ви серіалізували дерево у файловій системі, видалили всі .git папки та наказали git додати дерево назад у свою базу даних, ви б у кінцевому підсумку нічого не додавали до бази даних - елемент уже був би там.

Це може допомогти розглянути хитові хеші як посилання, що зараховується до незмінних даних.

Якщо ви створили додаток навколо цього, документ являє собою купу сторінок, які мають шари, групи, які мають об'єкти.

Коли ви хочете змінити об'єкт, ви повинні створити для нього абсолютно нову групу. Якщо ви хочете змінити групу, вам потрібно створити новий шар, для чого потрібна нова сторінка, для чого потрібен новий документ.

Кожен раз, коли ви змінюєте один об'єкт, він породжує новий документ. Старий документ продовжує існувати. Новий і старий документ поділяють більшу частину свого вмісту - вони мають однакові сторінки (крім 1). На одній сторінці є однакові шари (крім 1). Цей шар має ті самі групи (крім 1). Ця група має однакові об'єкти (крім 1).

І тим самим я маю на увазі логічно копію, але в міру реалізації це просто ще один посилання, що зараховується вказівником на той самий незмінний об'єкт.

Git repo - багато подібного.

Це означає, що заданий набір змін git містить своє повідомлення фіксації (як хеш-код), воно містить його робоче дерево, а також його батьківські зміни.

Ці батьківські зміни містять зміни батьківських змін, все назад.

Частина git repo, яка містить історію, - це ланцюжок змін. Цей ланцюжок змінює його на рівні вище дерева "каталогу" - з дерева "каталогу" ви не можете однозначно дістатися до набору змін та ланцюга змін.

Щоб дізнатися, що відбувається з файлом, ви починаєте з цього файлу в наборі змін. Цей набір змін має свою історію. Часто в цій історії існує один і той же названий файл, іноді з тим самим вмістом. Якщо вміст однаковий, файл не змінився. Якщо це інакше, є зміни, і потрібно зробити роботу, щоб вирішити саме те, що.

Іноді файл відсутній; але у дерева "каталогу" може бути інший файл з тим самим вмістом (той самий хеш-код), тому ми можемо відстежувати його таким чином (зверніть увагу; саме тому ви хочете перенести файл окремо від "виконувати" -edit). Або те саме ім'я файлу, і після перевірки файл досить схожий.

Таким чином, git може скріплювати "історію файлів".

Але ця історія файлів походить від ефективного розбору "всього набору змін", а не від посилання однієї версії файлу на іншу.


12

"git не відслідковує файли" в основному означає, що коміти git складаються із знімка дерева файлів, що з'єднує шлях у дереві до "blob" та графа фіксації, що відстежує історію комітетів . Все інше реконструюється на ходу за допомогою таких команд, як "git log" та "git blama". Цю реконструкцію можна пояснити за допомогою різних варіантів того, наскільки важко слід шукати зміни на основі файлів. Евристика за замовчуванням може визначати, коли крапка змінює місце в дереві файлів без змін або коли файл асоціюється з іншим крапом, ніж раніше. Механізми стиснення, які використовує Git, не переймаються великою кількістю меж файлів / файлів. Якщо вміст вже є десь, це дозволить утримати зростання сховища невеликим, не пов’язуючи різні краплі.

Тепер це сховище. У Git також є робоче дерево, і в цьому робочому дереві є відстежені та не відстежені файли. У покажчик записуються лише відстежені файли (область постановки? Кеш?), І лише те, що відстежується там, перетворює його в сховище.

Індекс орієнтований на файл і є кілька команд, орієнтованих на файли, для управління ними. Але те, що потрапляє у сховище, - це лише фіксація у вигляді знімків дерев файлів і пов'язаних з ними даних про блоб та предків комітів.

Оскільки Git не відслідковує історії файлів та перейменування, а ефективність їх не залежить від них, іноді вам доведеться кілька разів спробувати різні варіанти, поки Git не видасть історію / різницю / звинувачення, яка вас цікавить у нетривіальних історіях.

Це відрізняється від таких систем, як Subversion, які записують, а не реконструюють історії. Якщо це не записується, ви не чуєте про це.

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


7

Git не відстежує файл безпосередньо, але відстежує знімки сховища, і ці знімки трапляються з файлів.

Ось спосіб подивитися на це.

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

У Git немає прямої команди, яка це робить. Дивіться це питання . Ви здивуєтеся, скільки існує різних відповідей. Немає однієї простої відповіді, тому що Git не просто відстежує файл , не так, як це робить SVN або ClearCase.


5
Я думаю, що я отримую те, що ти намагаєшся сказати, але "У Git немає прямої команди, яка це робить", прямо суперечить відповідям на питання, з яким ви пов’язані. Незважаючи на те, що версія відбувається на рівні всього сховища, у Git зазвичай існує безліч способів досягти чого-небудь , тому наявність декількох команд для відображення історії файлу не є великим свідченням.
Джо Лі-

Я прокинув перші кілька відповідей на питання, яке ви пов’язали, і всі вони використовують git logабо якусь програму, побудовану на цьому (або якийсь псевдонім, який робить те саме). Але навіть якби було багато різних способів, як каже Джо, це також справедливо для показу історії філій. (також git log -p <file>вбудований і робить саме це)
Voo

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

3

До речі, відстеження "вмісту" - це те, що призвело до відстеження порожніх каталогів.
Ось чому, якщо ви git rm останній файл папки, сама папка видаляється .

Це не завжди було так, і лише Git 1.4 (травень 2006 р.) Застосував політику "відстеження вмісту" з використанням комісії 443f833 :

статус git: пропустіть порожні каталоги та додайте -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, фіксувати cee7f24 ) бути більш ефективним:

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

Це (відстеження контенту) - це також те, що додає git add в Git API, з Git 1.5.0 (грудень 2006 р., 366bfcb )

зробити «git add» першокласним інтерфейсом для користувача індексом

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

Будь-який вміст, який потрібно здійснити, потрібно додавати разом.
Не має значення цей вміст із нових файлів чи модифікованих файлів.
Вам просто потрібно "додати" це або за допомогою git-add, або за допомогою git-commit з -a(для вже відомих файлів, звичайно,).

Це те, що стало git add --interactiveможливим, з тим же Git 1.5.0 ( commit 5cde71d )

Зробивши вибір, дайте відповідь порожнім рядком, щоб поетапно містити вміст робочих файлів дерев для вибраних шляхів в індексі.

Ось чому, для рекурсивного видалення всього вмісту з каталогу вам потрібно пропустити -rпараметр, а не лише ім'я каталогу як <path>(все-таки Git 1.5.0, виконувати 9f95069 ).

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

Розглянемо наступне об’єднання з конфліктом перейменування / додавання:

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

В цьому випадку триходовий злиття оригінального Foo, Foo, і Б barпризведе до бажаного імені шляху barз тим же режимом / вмістом , що А мав для foo.
Таким чином, A мав правильний режим і вміст для файлу, і він мав правильне ім'я шляху (а саме bar).

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

  • Замість того, щоб зберігати файли в collide_path~HEADі collide_path~MERGE, вони двосторонньо об'єднуються та записуються в collide_path.
  • Замість того, щоб записувати версію перейменованого файлу, яка існувала на перейменованій стороні в індексі (таким чином ігноруючи будь-які зміни, які були внесені до файлу на стороні історії без перейменування), ми робимо тристороннє об’єднання вмісту на перейменований шлях, а потім зберігати це на 2-й або 3-й стадії.
  • Зауважте, що оскільки злиття вмісту для кожного перейменування може мати конфлікти, і тоді нам доведеться об'єднати два перейменовані файли, ми можемо виявити вкладені маркери конфлікту.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.