Co oznacza Linus Torvalds, gdy mówi, że Git „nigdy” nie śledzi pliku?

283

Cytując Linusa Torvaldsa, gdy zapytano go, ile plików Git może obsłużyć podczas swojej Tech Talk w Google w 2007 roku (43:09):

… Git śledzi Twoje treści. Nigdy nie śledzi ani jednego pliku. Nie można śledzić pliku w Git. Możesz śledzić projekt, który ma jeden plik, ale jeśli twój projekt ma jeden plik, z pewnością to zrobisz i możesz to zrobić, ale jeśli śledzisz 10 000 plików, Git nigdy nie widzi ich jako pojedynczych plików. Git uważa wszystko za pełną treść. Cała historia w Git oparta jest na historii całego projektu…

(Transkrypcje tutaj .)

Jednak podczas nurkowania w książce Git pierwszą rzeczą, o której się mówi, jest to, że plik w Git może być śledzony lub nieśledzony . Ponadto wydaje mi się, że całe doświadczenie Git jest ukierunkowane na wersjonowanie plików. Podczas używania git difflub git statusdane wyjściowe są prezentowane dla poszczególnych plików. Podczas korzystania git addmożesz także wybrać dla poszczególnych plików. Możesz nawet przeglądać historię na podstawie pliku i błyskawicznie.

Jak należy interpretować to stwierdzenie? Jeśli chodzi o śledzenie plików, czym różni się Git od innych systemów kontroli źródła, takich jak CVS?

Simón Ramírez Amaya
źródło
20
reddit.com/r/git/comments/5xmrkv/what_is_a_snapshot_in_git - „Podejrzewam, że w tej chwili ważniejsze jest to, że Git przedstawia użytkownikom pliki i to, jak sobie z nimi radzi . Jak przedstawiono użytkownikowi, migawka zawiera pełne pliki, a nie tylko pliki różnic. Ale wewnętrznie tak, Git używa plików różnic do generowania plików pakietów, które skutecznie przechowują wersje ”. (Jest to ostry kontrast np. Z Subversion.)
user2864740
5
Git nie śledzi plików, śledzi zestawy zmian . Większość systemów kontroli wersji śledzi pliki. Jako przykład tego, jak / dlaczego to może mieć znaczenie, spróbuj sprawdzić w pustym katalogu git (spolier: nie możesz, ponieważ jest to „pusty” zestaw zmian).
Elliott Frisch
12
@ElliottFrisch To nie brzmi dobrze. Twój opis jest bliższy temu, co robi np. Darcs . Git przechowuje migawki, a nie zestawy zmian.
melpomene
4
Myślę, że ma na myśli, że Git nie śledzi bezpośrednio pliku. Plik zawiera jego nazwę i treść. Git śledzi zawartość jako obiekty BLOB. Biorąc pod uwagę tylko obiekt blob, nie można powiedzieć, jaka jest jego odpowiednia nazwa pliku. Może to być zawartość wielu plików o różnych nazwach i różnych ścieżkach. Wiązania między nazwą ścieżki a obiektem blob są opisane w obiekcie drzewa.
ElpieKay
3
Powiązane: Randal Schwartz w następstwie przemówienia Linusa (również przemówienie Google Tech) - „... O co tak naprawdę chodzi w Git ... Linus powiedział, czym Git NIE jest”.
Peter Mortensen

Odpowiedzi:

316

W CVS historię śledzono na podstawie pliku. Oddział może składać się z różnych plików z różnymi wersjami, każdy z własnym numerem wersji. CVS został oparty na RCS ( Revision Control System ), który w podobny sposób śledził poszczególne pliki.

Z drugiej strony Git wykonuje migawki stanu całego projektu. Pliki nie są śledzone i wersjonowane niezależnie; wersja w repozytorium odnosi się do stanu całego projektu, a nie jednego pliku.

Kiedy Git odnosi się do śledzenia pliku, oznacza to po prostu, że należy go włączyć do historii projektu. Rozmowa Linusa nie odnosiła się do plików śledzenia w kontekście Gita, ale przeciwstawiała model CVS i RCS modelowi opartemu na migawkach używanym w Git.

bk2204
źródło
4
Możesz dodać, że właśnie dlatego w CVS i Subversion możesz używać znaczników jak $Id$w pliku. To samo nie działa w git, ponieważ konstrukcja jest inna.
gerrit
58
A zawartość nie jest związana z plikiem, jak można się spodziewać. Spróbuj przenieść 80% kodu z jednego pliku do drugiego. Git automatycznie wykrywa przeniesienie pliku + 20% zmiany, nawet jeśli właśnie przeniesiesz kod w istniejących plikach.
allo
13
@allo Efektem ubocznym tego jest to, że git może zrobić jedną rzecz, której inni nie potrafią: kiedy dwa pliki zostaną połączone i użyjesz „git blame -C”, git może przejrzeć obie historie. W śledzeniu opartym na plikach musisz wybrać, który z oryginalnych plików jest prawdziwym oryginałem, a wszystkie pozostałe wiersze wyglądają jak nowe.
Izkata,
1
@allo, Izkata - I jest to obiekt zapytania, który rozwiązuje to wszystko, analizując zawartość repo w czasie zapytania (zatwierdza historię i różnice między odnośnymi drzewami i obiektami blob), zamiast wymagać od podmiotu zatwierdzającego i jego ludzkiego użytkownika poprawnego określenia lub syntezy te informacje w czasie zatwierdzania - ani twórca narzędzia repo, aby zaprojektować i wdrożyć tę możliwość oraz odpowiedni schemat metadanych przed wdrożeniem narzędzia. Torvalds argumentował, że taka analiza poprawi się z czasem, a cała historia każdego repozytorium git od pierwszego dnia będzie korzystna.
Jeremy
1
@ allo Tak, i aby wyjaśnić fakt, że git nie działa na poziomie pliku, nie musisz nawet zatwierdzać wszystkich zmian w pliku na raz; możesz zatwierdzić dowolny zakres wierszy, pozostawiając inne zmiany w pliku poza zatwierdzeniem. Oczywiście interfejs użytkownika nie jest tak prosty, więc większość tego nie robi, ale rzadko ma swoje zastosowania.
Alvin Thompson,
103

Zgadzam się z Brianem M. odpowiedź Carlson : Linus rzeczywiście rozróżnia, przynajmniej częściowo, systemy kontroli wersji zorientowane na pliki i zorientowane na zatwierdzanie. Ale myślę, że jest w tym coś więcej.

W mojej książce , która utknęła w martwym punkcie i może się nie skończyć, próbowałem wymyślić systematykę dla systemów kontroli wersji. W mojej taksonomii terminem, który nas tutaj interesuje, jest atomowość systemu kontroli wersji. Zobacz, co aktualnie znajduje się na stronie 22. Kiedy VCS ma atomowość na poziomie pliku, w rzeczywistości dla każdego pliku istnieje historia. VCS musi zapamiętać nazwę pliku i co się z nim działo w każdym punkcie.

Git tego nie robi. Git ma tylko historię zatwierdzeń - zatwierdzenie jest jednostką atomowości, a historia jest zbiorem zatwierdzeń w repozytorium. Zapamiętywanie, które zapamiętuje, to dane - całe drzewo pełne nazw plików i treści, które towarzyszą każdemu z tych plików - oraz niektóre metadane: na przykład, kto dokonał zatwierdzenia, kiedy i dlaczego oraz wewnętrzny identyfikator hash Git zatwierdzenia nadrzędnego zatwierdzenia. (Jest to rodzic, a graf skierowany acycling utworzone przez przeczytaniu wszystkich zobowiązuje i ich rodziców, że to historia w repozytorium).

Zauważ, że VCS może być zorientowany na zatwierdzanie, ale nadal przechowuje dane plik po pliku. To szczegół implementacji, choć czasem ważny, a Git też tego nie robi. Zamiast tego każde zatwierdzenie rejestruje drzewo , przy czym obiekt drzewa koduje nazwy plików , tryby (tj. Czy ten plik jest wykonywalny czy nie?) Oraz wskaźnik do rzeczywistej zawartości pliku . Sama treść jest przechowywana niezależnie w obiekcie blob . Podobnie jak obiekt zatwierdzenia, obiekt blob otrzymuje identyfikator skrótu, który jest unikalny dla jego zawartości - ale w przeciwieństwie do zatwierdzenia, które może pojawić się tylko raz, obiekt blob może występować w wielu zatwierdzeniach. Tak więc podstawowa zawartość pliku w Git jest przechowywana bezpośrednio jako obiekt blob, a następnie pośrednio w obiekcie drzewa, którego identyfikator skrótu jest zapisany (bezpośrednio lub pośrednio) w obiekcie zatwierdzenia.

Gdy poprosisz Gita o pokazanie historii pliku, używając:

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

to, co naprawdę robi Git, to przeglądanie historii zatwierdzeń , która jest jedyną historią, którą ma Git, ale nie pokazywanie żadnego z tych zatwierdzeń, chyba że:

  • zatwierdzenie jest zatwierdzeniem innym niż scalanie, oraz
  • element nadrzędny tego zatwierdzenia również ma plik, ale zawartość elementu nadrzędnego jest różna lub element nadrzędny zatwierdzenia w ogóle nie ma pliku

(ale niektóre z tych warunków można modyfikować za pomocą dodatkowych git logopcji, a bardzo trudno jest opisać efekt uboczny o nazwie History Simplification, który powoduje, że Git całkowicie pomija niektóre zatwierdzenia w historii). Historia plików, którą tu widzisz, w pewnym sensie nie istnieje w repozytorium: jest to tylko syntetyczny podzbiór prawdziwej historii. Otrzymasz inną „historię plików”, jeśli użyjesz różnych git logopcji!

Trek
źródło
Inną rzeczą do dodania jest to, że pozwala Gitowi robić takie rzeczy jak płytkie klony. Musi tylko pobrać zatwierdzenie głowy i wszystkie obiekty BLOB, do których się odnosi. Nie trzeba ponownie tworzyć plików, stosując zestawy zmian.
Wes Toleman,
@WesToleman: zdecydowanie to ułatwia. Mercurial przechowuje delty, z okazjonalnymi resetami, i chociaż ludzie Mercurial zamierzają dodać płytkie klony (co jest możliwe dzięki pomysłowi „resetu”), to jeszcze tego nie zrobili (ponieważ jest to raczej wyzwanie techniczne).
torek
@torek Mam wątpliwości co do twojego opisu Gita odpowiadającego na prośbę o historię plików, ale myślę, że zasługuje na własne właściwe pytanie: stackoverflow.com/questions/55616349/…
Simón Ramírez Amaya
@torek Dzięki za link do twojej książki, nie widziałem nic podobnego.
gnarledRoot
17

Zagmatwany bit jest tutaj:

Git nigdy nie postrzega tych plików jako pojedynczych plików. Git uważa wszystko za pełną treść.

Git często używa 160-bitowych skrótów zamiast obiektów we własnym repozytorium. Drzewo plików to w zasadzie lista nazw i skrótów powiązanych z zawartością każdego z nich (plus niektóre metadane).

Ale 160-bitowy skrót jednoznacznie identyfikuje treść (we wszechświecie bazy danych git). Zatem drzewo z skrótami jako treść zawiera zawartość w jej stanie.

Jeśli zmienisz stan zawartości pliku, zmieni się jego skrót. Ale jeśli zmieni się jego skrót, zmieni się również skrót związany z zawartością nazwy pliku. Co z kolei zmienia skrót „drzewa katalogów”.

Gdy baza danych git przechowuje drzewo katalogów, drzewo to implikuje i zawiera całą zawartość wszystkich podkatalogów i wszystkich plików w nim zawartych .

Jest on zorganizowany w strukturę drzewa z (niezmiennymi, wielokrotnego użytku) wskaźnikami do obiektów blob lub innych drzew, ale logicznie jest to pojedyncza migawka całej zawartości całego drzewa. Reprezentacja w bazie git nie jest płaskie treść danych, ale logicznie to wszystko jego dane i nic innego.

Jeśli serializujesz drzewo do systemu plików, usuwasz wszystkie foldery .git i każesz gitowi dodać drzewo z powrotem do swojej bazy danych, skończysz na tym, że nic nie dodasz do bazy danych - element już tam będzie.

Pomóc może myśleć o skrótach gita jako o wskaźniku liczonym jako odniesienie do niezmiennych danych.

Jeśli stworzysz wokół niego aplikację, dokument to wiązka stron, które mają warstwy, grupy, obiekty.

Kiedy chcesz zmienić obiekt, musisz utworzyć dla niego zupełnie nową grupę. Jeśli chcesz zmienić grupę, musisz utworzyć nową warstwę, która potrzebuje nowej strony, która potrzebuje nowego dokumentu.

Za każdym razem, gdy zmieniasz pojedynczy obiekt, odradza się nowy dokument. Stary dokument nadal istnieje. Nowy i stary dokument udostępniają większość swoich treści - mają te same strony (oprócz 1). Ta jedna strona ma te same warstwy (oprócz 1). Ta warstwa ma te same grupy (oprócz 1). Ta grupa ma te same obiekty (oprócz 1).

I przez to rozumiem logicznie kopię, ale pod względem implementacji jest to po prostu kolejny wskaźnik zliczany referencją do tego samego niezmiennego obiektu.

Git repo jest bardzo podobny.

Oznacza to, że dany zestaw zmian git zawiera komunikat zatwierdzenia (jako kod skrótu), zawiera drzewo pracy i zawiera zmiany nadrzędne.

Te zmiany nadrzędne zawierają zmiany nadrzędne od samego początku.

Częścią repozytorium git, która zawiera historię, jest łańcuch zmian. Ten łańcuch zmian zmienia go na poziomie powyżej drzewa „katalogu” - z drzewa „katalogu” nie można jednoznacznie dostać się do zestawu zmian i łańcucha zmian.

Aby dowiedzieć się, co dzieje się z plikiem, zaczynasz od tego pliku w zestawie zmian. Ten zestaw zmian ma swoją historię. Często w tej historii istnieje ten sam nazwany plik, czasem o tej samej treści. Jeśli treść jest taka sama, nie było zmian w pliku. Jeśli jest inaczej, następuje zmiana i należy wykonać pracę, aby dokładnie ustalić, co.

Czasami plik zniknął; ale drzewo „katalogu” może mieć inny plik z tą samą zawartością (ten sam kod skrótu), więc możemy to prześledzić w ten sposób (uwaga; właśnie dlatego chcesz, aby zatwierdzenie przesunęło plik osobno od zatwierdzenia do -edytować). Lub ta sama nazwa pliku, a po sprawdzeniu plik jest wystarczająco podobny.

Git może więc łączyć „historię plików”.

Ale ta historia plików pochodzi z wydajnego analizowania „całego zestawu zmian”, a nie z łącza z jednej wersji pliku do innej.

Jak - Adam Nevraumont
źródło
12

„git nie śledzi plików” zasadniczo oznacza, że ​​zatwierdzenia git składają się z migawki drzewa plików łączącej ścieżkę drzewa z „obiektu blob” i wykresu zatwierdzania śledzącego historię zatwierdzeń . Wszystko inne jest rekonstruowane w locie za pomocą poleceń takich jak „git log” i „git blame”. Tę rekonstrukcję można powiedzieć za pomocą różnych opcji, jak mocno powinna ona szukać zmian opartych na plikach. Domyślna heurystyka może określić, kiedy obiekt blob zmienia miejsce w drzewie plików bez zmian lub kiedy plik jest powiązany z innym obiektem blob niż wcześniej. Mechanizmy kompresji stosowane przez Git nie dbają zbytnio o granice obiektów blob / plików. Jeśli treść już gdzieś jest, spowoduje to niewielki wzrost repozytorium bez kojarzenia różnych obiektów blob.

To jest repozytorium. Git ma także działające drzewo, aw tym pracującym drzewie znajdują się pliki śledzone i nieśledzone. Tylko śledzone pliki są zapisywane w indeksie (obszar przejściowy? Pamięć podręczna?) I tylko to, co jest tam śledzone, trafia do repozytorium.

Indeks jest zorientowany na pliki i istnieje kilka poleceń zorientowanych na pliki do manipulowania nim. Ale to, co kończy się w repozytorium, to po prostu zatwierdzenia w postaci migawek drzewa plików i powiązanych danych obiektów blob oraz przodków zatwierdzenia.

Ponieważ Git nie śledzi historii plików i nazw, a ich wydajność nie zależy od nich, czasami musisz spróbować kilka razy z różnymi opcjami, dopóki Git nie stworzy historii / różnic / obwiniania, które Cię interesują dla nietrywialnych historii.

Inaczej jest w przypadku systemów takich jak Subversion, które rejestrują, a nie odtwarzają historie. Jeśli nie jest nagrywany, nie słyszysz o tym.

Zrobiłem kiedyś instalator różnicowy, który po prostu porównał drzewa wydań, sprawdzając je w Git, a następnie tworząc skrypt powielający ich działanie. Ponieważ czasami całe drzewa były przenoszone, produkowało to znacznie mniejszych instalatorów różnicowych niż nadpisywanie / usuwanie wszystkiego, co by wyprodukowało.


źródło
7

Git nie śledzi bezpośrednio pliku, ale śledzi migawki repozytorium, a te migawki składają się z plików.

Oto sposób, aby na to spojrzeć.

W innych systemach kontroli wersji (SVN, Rational ClearCase) można kliknąć plik prawym przyciskiem myszy i uzyskać historię zmian .

W Git nie ma bezpośredniego polecenia, które to robi. Zobacz to pytanie . Będziesz zaskoczony, ile jest różnych odpowiedzi. Nie ma jednej prostej odpowiedzi, ponieważ Git nie śledzi po prostu pliku , nie w sposób, w jaki robi to SVN lub ClearCase.

Grubowarstwowy gruby Double Vision
źródło
5
Wydaje mi się, że rozumiem to, co próbujesz powiedzieć, ale „W Git nie ma bezpośredniego polecenia, które by to robiło”, odpowiedzi na pytanie, z którymi się połączyłeś, są bezpośrednio sprzeczne. Chociaż prawdą jest, że przechowywanie wersji odbywa się na poziomie całego repozytorium, zwykle istnieje wiele sposobów na osiągnięcie czegokolwiek w Git, więc posiadanie wielu poleceń pokazujących historię pliku nie jest dowodem na wiele.
Joe Lee-Moyet,
Przejrzałem kilka pierwszych odpowiedzi na pytanie, które podłączyłeś i wszystkie z nich korzystają git loglub jakiś program zbudowany na tym (lub jakiś alias, który robi to samo). Ale nawet jeśli było wiele różnych sposobów, jak mówi Joe, dotyczy to również historii branży. (również git log -p <file>jest wbudowany i robi dokładnie to)
Voo
Czy jesteś pewien, że SVN wewnętrznie przechowuje zmiany w pliku? Nie używałem go już od jakiegoś czasu, ale niejasno pamiętam, że miałem pliki o nazwach takich jak identyfikatory wersji, a nie odzwierciedlenie struktury pliku projektu.
Artur Biesiadowski
3

Nawiasem mówiąc, śledzenie „treści” doprowadziło do nie śledzenia pustych katalogów.
Dlatego jeśli otrzymasz ostatni plik folderu, sam folder zostanie usunięty .

Nie zawsze tak było i tylko Git 1.4 (maj 2006 r.) Egzekwował tę zasadę „śledzenia treści” przy pomocy commit 443f833 :

status git: pomiń puste katalogi i dodaj -u, aby wyświetlić wszystkie nieśledzone pliki

Domyślnie --others --directorywyświetlamy nieciekawe katalogi (aby zwrócić uwagę użytkownika) bez ich zawartości (w celu uporządkowania danych wyjściowych).
Wyświetlanie pustych katalogów nie ma sensu, więc podaj--no-empty-directory to, co robimy.

Nadanie -u(lub --untracked) wyłącza to uporządkowanie, aby umożliwić użytkownikowi pobranie wszystkich nieśledzonych plików.

Zostało to powtórzone wiele lat później, w styczniu 2011 r., Z zatwierdzeniem 8fe533 , Git v1.7.4:

Jest to zgodne z ogólną filozofią interfejsu użytkownika: git śledzi zawartość, a nie puste katalogi.

W międzyczasie, w Git 1.4.3 (wrzesień 2006), Git zaczyna ograniczać nieśledzoną zawartość do niepustych folderów, z zatwierdzeniem 2074cb0 :

nie powinien wypisywać zawartości całkowicie nieśledzonych katalogów, a jedynie nazwę tego katalogu (plus końcowe „ /”).

Śledzenie treści jest tym, co pozwoliło winić gitowi bardzo wcześnie (Git 1.4.4, październik 2006, zatwierdzanie cee7f24 ):

Co ważniejsze, jego wewnętrzna struktura jest zaprojektowana tak, aby łatwiej obsługiwać ruch zawartości (inaczej wycinanie i wklejanie), umożliwiając pobieranie więcej niż jednej ścieżki z tego samego zatwierdzenia.

To (śledzenie zawartości) jest również tym, co umieściło git add w Git API, z Git 1.5.0 (grudzień 2006, zatwierdzenie 366bfcb )

uczyń „git add” pierwszorzędnym, przyjaznym dla użytkownika interfejsem do indeksu

Daje to siłę indeksu z góry przy użyciu właściwego modelu mentalnego bez mówienia o indeksie w ogóle.
Zobacz na przykład, jak cała dyskusja techniczna została ewakuowana ze strony man git-add.

Wszelkie treści, które mają zostać zatwierdzone, należy dodać razem.
To, czy treść pochodzi z nowych plików, czy zmodyfikowanych plików, nie ma znaczenia.
Musisz go tylko „dodać” albo za pomocą git-add, albo przez podanie git-commit z -a(oczywiście tylko dla znanych plików).

To stało się git add --interactivemożliwe dzięki temu samemu Git 1.5.0 ( commit 5cde71d )

Po dokonaniu wyboru odpowiedz pustą linią, aby wyrównać zawartość działających plików drzewa dla wybranych ścieżek w indeksie.

Dlatego też, aby rekurencyjnie usunąć całą zawartość z katalogu, musisz przekazać -ropcję, a nie tylko nazwę katalogu jako <path>(wciąż Git 1.5.0, zatwierdzenie 9f95069 ).

Oglądanie zawartości pliku zamiast samego pliku pozwala na scenariusz scalania, taki jak ten opisany w zatwierdzeniu 1de70db (Git v2.18.0-rc0, kwiecień 2018)

Rozważ następujące połączenie z konfliktem zmiany nazwy / dodania:

  • strona A: zmodyfikuj foo, dodaj niepowiązanebar
  • strona B: zmień nazwę foo->bar(ale nie modyfikuj trybu ani zawartości)

W tym przypadku, trójdrożny seryjnej pierwotnego foo A w foo i B barbędzie powodować pożądaną ścieżkę w bartych samych trybów / treści, że A miał na foo.
Zatem A miał odpowiedni tryb i zawartość dla pliku, i miał właściwą nazwę ścieżki (czyli, bar).

Commit 37b65ce , Git v2.21.0-rc0, grudzień 2018 r., Ostatnio poprawiono rozwiązania konfliktów kolizji.
A zatwierdzenie bbafc9c i inne ilustruje znaczenie rozważenia zawartości pliku , poprzez poprawę obsługi konfliktów zmiany nazwy / zmiany nazwy (2to1):

  • Zamiast przechowywać pliki w collide_path~HEADi collide_path~MERGE, pliki są dwustronnie scalane i zapisywane wcollide_path .
  • Zamiast rejestrować wersję pliku o zmienionej nazwie, która istniała po stronie o zmienionej nazwie w indeksie (ignorując w ten sposób wszelkie zmiany, które zostały wprowadzone w pliku po stronie historii bez zmiany nazwy), wykonujemy trzykrotne scalanie zawartości o zmienionej nazwie ścieżkę, a następnie zapisz ją na etapie 2 lub 3.
  • Pamiętaj, że ponieważ scalanie treści dla każdej zmiany nazwy może powodować konflikty, a następnie musimy scalić dwa pliki o zmienionej nazwie, możemy uzyskać zagnieżdżone znaczniki konfliktu.
VonC
źródło