Jak odczytać dane wyjściowe z git diff?

270

Strona podręcznika dla użytkownika git-diffjest dość długa i wyjaśnia wiele przypadków, które nie wydają się konieczne dla początkującego. Na przykład:

git diff origin/master
poseid
źródło
1
dzięki zastosowaniu innego edytora tekstu notacje o zasięgu @ ... @ dla numerów linii stały się oczywiste.
poseid
Który edytor tekstu?
Jus12

Odpowiedzi:

488

Spójrzmy na przykład zaawansowanego porównania z historią git (w zatwierdzeniu 1088261f w repozytorium git.git ):

diff --git a/builtin-http-fetch.c b/http-fetch.c
similarity index 95%
rename from builtin-http-fetch.c
rename to http-fetch.c
index f3e63d7..e8f44ba 100644
--- a/builtin-http-fetch.c
+++ b/http-fetch.c
@@ -1,8 +1,9 @@
 #include "cache.h"
 #include "walker.h"

-int cmd_http_fetch(int argc, const char **argv, const char *prefix)
+int main(int argc, const char **argv)
 {
+       const char *prefix;
        struct walker *walker;
        int commits_on_stdin = 0;
        int commits;
@@ -18,6 +19,8 @@ int cmd_http_fetch(int argc, const char **argv, const char *prefix)
        int get_verbosely = 0;
        int get_recover = 0;

+       prefix = setup_git_directory();
+
        git_config(git_default_config, NULL);

        while (arg < argc && argv[arg][0] == '-') {

Przeanalizujmy tę łatkę linia po linii.

  • Pierwsza linia

    diff --git a / builtin-http-fetch.cb / http-fetch.c
    jest nagłówkiem „git diff” w formularzu diff --git a/file1 b/file2. Nazwy plików a/i b/są takie same, chyba że w grę wchodzi zmiana nazwy / kopii (jak w naszym przypadku). --gitTo oznaczać, że edycja jest w „git” formacie diff.

  • Dalej jest jedna lub więcej rozszerzonych linii nagłówka. Pierwsze trzy

    wskaźnik podobieństwa 95%
    zmień nazwę z wbudowanego-http-fetch.c
    zmień nazwę na http-fetch.c
    powiedz nam, że nazwa pliku została zmieniona z builtin-http-fetch.cna http-fetch.ci że te dwa pliki są w 95% identyczne (co zostało użyte do wykrycia tej zmiany).

    Ostatni wiersz w rozszerzonym nagłówku różnicowym, którym jest
    indeks f3e63d7..e8f44ba 100644
    powiedz nam o trybie danego pliku ( 100644oznacza, że ​​jest to zwykły plik, a nie np. dowiązanie symboliczne i że nie ma wykonywalnego bitu uprawnień), a także o skróconym skrócie preimage (wersja pliku przed daną zmianą) i postimage ( wersja pliku po zmianie). Ten wiersz jest używany przez git am --3waypróbę wykonania 3-kierunkowego scalenia, jeśli łata nie może być zastosowana sama.

  • Dalej jest dwuwierszowy zunifikowany nagłówek diff

    --- a / builtin-http-fetch.c
    +++ b / http-fetch.c
    W porównaniu do diff -Uwyniku nie ma on nazw plików od modyfikacji pliku ani czasu modyfikacji pliku po źródłowym (wstępnym obrazie) i docelowym (postimage) nazwach plików. Jeśli plik został utworzony, źródłem jest /dev/null; jeśli plik został usunięty, celem jest /dev/null.
    Po ustawieniu diff.mnemonicPrefixkonfiguracji zmiennej true, w miejscu a/i b/przedrostków w tym dwulinijkowego nagłówka można mieć zamiast c/, i/, w/i o/jako przedrostków, odpowiednio do czego porównać; patrz git-config (1)

  • Następnie przychodzi jeden lub więcej kawałków różnic; każdy przystojniak pokazuje jeden obszar, w którym różnią się pliki. Kawałki ujednoliconego formatu zaczynają się od linii

    @@ -1,8 +1,9 @@
    lub
    @@ -18,6 +19,8 @@ int cmd_http_fetch (int argc, const char ** argv, ...
    Jest w formacie @@ from-file-range to-file-range @@ [header]. Zakres od pliku ma postać -<start line>,<number of lines>, a zakres od pliku jest +<start line>,<number of lines>. Zarówno linia początkowa, jak i liczba linii odnoszą się odpowiednio do pozycji i długości przystawki w przedobrazie i po obrazie. Jeśli liczba linii nie jest pokazana, oznacza to, że jest to 0.

    Opcjonalny nagłówek pokazuje funkcję C, w której występuje każda zmiana, jeśli jest to plik C (jak -popcja w GNU diff) lub równoważny, jeśli taki istnieje, dla innych typów plików.

  • Następnie znajduje się opis różnic między plikami. Linie wspólne dla obu plików rozpoczynają się znakiem spacji. Linie, które faktycznie różnią się między dwoma plikami, mają jeden z następujących znaków wskaźnikowych w lewej kolumnie wydruku:

    • „+” - tutaj dodano wiersz do pierwszego pliku.
    • „-” - tutaj została usunięta linia z pierwszego pliku.


    Na przykład pierwsza porcja

     #include "cache.h"
     #include "walker.h"
    
    -int cmd_http_fetch(int argc, const char **argv, const char *prefix)
    +int main(int argc, const char **argv)
     {
    +       const char *prefix;
            struct walker *walker;
            int commits_on_stdin = 0;
            int commits;
    

    oznacza, że cmd_http_fetchzostał zastąpiony przez maini ta const char *prefix;linia została dodana.

    Innymi słowy, przed zmianą odpowiedni fragment pliku „builtin-http-fetch.c” wyglądał następująco:

    #include "cache.h"
    #include "walker.h"
    
    int cmd_http_fetch(int argc, const char **argv, const char *prefix)
    {
           struct walker *walker;
           int commits_on_stdin = 0;
           int commits;
    

    Po zmianie ten fragment pliku „http-fetch.c” wygląda następująco:

    #include "cache.h"
    #include "walker.h"
    
    int main(int argc, const char **argv)
    {
           const char *prefix;
           struct walker *walker;
           int commits_on_stdin = 0;
           int commits;
    
  • Może tam być

    \ Brak nowego wiersza na końcu pliku
    linia obecna (nie ma w tym przykładzie różnicy).

Jak powiedział Donal Fellows , najlepiej ćwiczyć czytanie różnic na prawdziwych przykładach, gdzie wiesz, co zmieniłeś.

Bibliografia:

Jakub Narębski
źródło
1
@Geremia: Git wykorzystuje heurystykę opartą na podobieństwie do wykrywania zmian nazw ... a także do wykrywania przenoszenia i kopiowania kodu git blame -C -C, tak to działa; to decyzja projektowa Git. Format git diff pokazuje tylko indeks podobieństwa (lub podobieństwa) do użytkownika.
Jakub Narębski
1
@Geremia: Mówiąc ściślej, [header]jest najbliższym poprzedzeniem, takim jak początek funkcji, która poprzedza przystojniak. W większości przypadków ten wiersz zawiera nazwę funkcji, w której znajduje się fragment różnicy. Jest to konfigurowalne z diffgitattribute ustawionym na sterownik diff i sterownik diff zawierający xfuncnamezmienną konfiguracyjną.
Jakub Narębski,
1
@AnthonyGeoghegan: linie mogą zostać usunięte (wtedy liczba linii na postimage wynosi 0) lub dodane (wtedy liczba linii na preimage wynosi 0).
Jakub Narębski
1
@KasunSiyambalapitiya: Zunifikowany format różnic, którego używa Git (w przeciwieństwie do kontekstowego formatu różnic ^ [1]), nie rozróżnia linii zmodyfikowanej oraz linii usuniętej i dodanej. [1]: gnu.org/software/diffutils/manual/html_node/Context-Format.html
Jakub Narębski
1
@ JakubNarębski: Domyślna liczba linii to 1, a nie 0. To takie proste. W praktyce pojawia się tylko jako „-1” i / lub „+1” dla plików jednowierszowych, ponieważ nie ma kontekstu do wyświetlenia.
Guido Flohr
68

@@ -1,2 +3,4 @@ część różnicy

Ta część zajęła mi trochę czasu, aby zrozumieć, więc stworzyłem minimalny przykład.

Format jest w zasadzie taki sam jak diff -uzunifikowany plik różnic.

Na przykład:

diff -u <(seq 16) <(seq 16 | grep -Ev '^(2|3|14|15)$')

Tutaj usunęliśmy wiersze 2, 3, 14 i 15. Dane wyjściowe:

@@ -1,6 +1,4 @@
 1
-2
-3
 4
 5
 6
@@ -11,6 +9,4 @@
 11
 12
 13
-14
-15
 16

@@ -1,6 +1,4 @@ znaczy:

  • -1,6oznacza, że ​​ten fragment pierwszego pliku zaczyna się od linii 1 i pokazuje w sumie 6 linii. Dlatego pokazuje linie od 1 do 6.

    1
    2
    3
    4
    5
    6
    

    -oznacza „stary”, jak zwykle przywołujemy go jako diff -u old new.

  • +1,4oznacza, że ​​ten fragment drugiego pliku zaczyna się od linii 1 i pokazuje w sumie 4 linie. Dlatego pokazuje linie od 1 do 4.

    + oznacza „nowy”.

    Mamy tylko 4 linie zamiast 6, ponieważ 2 linie zostały usunięte! Nowy przystojniak to po prostu:

    1
    4
    5
    6
    

@@ -11,6 +9,4 @@ dla drugiego przystojniaka jest analogiczne:

  • na starym pliku mamy 6 linii, zaczynając od linii 11 starego pliku:

    11
    12
    13
    14
    15
    16
    
  • w nowym pliku mamy 4 linie, zaczynając od linii 9 nowego pliku:

    11
    12
    13
    16
    

    Zauważ, że linia 11jest 9 linią nowego pliku, ponieważ już usunęliśmy 2 linie z poprzedniego przystanku: 2 i 3.

Hunk nagłówek

W zależności od wersji git i konfiguracji możesz także uzyskać wiersz kodu obok @@wiersza, np. func1() {W:

@@ -4,7 +4,6 @@ func1() {

Można to również uzyskać -pflagą równiny diff.

Przykład: stary plik:

func1() {
    1;
    2;
    3;
    4;
    5;
    6;
    7;
    8;
    9;
}

Jeśli usuniemy linię 6, diff pokaże:

@@ -4,7 +4,6 @@ func1() {
     3;
     4;
     5;
-    6;
     7;
     8;
     9;

Zauważ, że nie jest to poprawna linia dla func1: pominęła linie 1i 2.

Ta niesamowita funkcja często mówi dokładnie, do której funkcji lub klasy należy każdy przystojniak, co jest bardzo przydatne do interpretacji różnic.

Jak dokładnie działa algorytm wyboru nagłówka, omówiono na stronie: Skąd pochodzi fragment nagłówka przystawki git diff?

Ciro Santilli
źródło
11
To jest dla każdego, kto wciąż nie do końca rozumiał. W języku @@ -1,6 +1,4 @@PLS nie czytaj -1jako minus onelub +1jako plus onezamiast tego czytaj tak jak line 1 to 6w starym (pierwszym) pliku. Uwaga tutaj - implies "old"nie minus. BTW, dzięki za wyjaśnienie ... haash.
dkjain
z tego @@ -1,8 +1,9 @@ można interpretować to, co się faktycznie wydarzyło. na przykład 1) dodano jedną linię 2) jedna linia jest modyfikowana, jedna linia jest dodawana i tak dalej. A może jest to z innej strony, ponieważ powinien istnieć sposób ich uzyskania, ponieważ poprawność git diff określa, które wiersze zostały zmodyfikowane w kodzie. Proszę, pomóż mi, bo naprawdę muszę to załatwić
Kasun Siyambalapitiya,
Proszę zauważyć, że jest to nieprawidłowe i bardzo mylące stwierdzenie w powyższej odpowiedzi: „ +1,4mówi, że ten fragment odpowiada wierszom od 1 do 4 drugiego pliku ”. Wynika to z faktu, że +1,4mogą odnosić się do niepowiązanych linii kontekstowych. Raczej to, co w +1,4rzeczywistości oznacza „ ”, to że „ w tej„ wersji ”pliku 4wiersze (tj. Linie kontekstu) . Ważne jest, aby zrozumieć sens +, -i <whitespace>na początku tych linii, ponieważ odnosi się do interpretacji porcji. Bardziej wizualny przykład: youtube.com/watch?v=1tqMjJeyKpw
Damilola Olowookere
23

Oto prosty przykład.

diff --git a/file b/file 
index 10ff2df..84d4fa2 100644
--- a/file
+++ b/file
@@ -1,5 +1,5 @@
 line1
 line2
-this line will be deleted
 line4
 line5
+this line is added

Oto wyjaśnienie (zobacz szczegóły tutaj ).

  • --git nie jest poleceniem, oznacza to, że jest to wersja git diff (nie unix)
  • a/ b/są katalogami, nie są prawdziwe. to tylko wygoda, gdy mamy do czynienia z tym samym plikiem (w moim przypadku a / jest w indeksie, a b / jest w katalogu roboczym)
  • 10ff2df..84d4fa2 są identyfikatorami obiektów blob tych 2 plików
  • 100644 to „bity trybu”, wskazujące, że jest to zwykły plik (niewykonywalny i nie dowiązanie symboliczne)
  • --- a/file +++ b/fileznaki minus pokazują linie w wersji a /, ale brakuje ich w wersji b /; a znaki plus pokazują brakujące linie w /, ale obecne w b / (w moim przypadku --- oznacza usunięte linie, a +++ oznacza dodane linie wb / i ten plik w katalogu roboczym)
  • @@ -1,5 +1,5 @@aby to zrozumieć, lepiej pracować z dużym plikiem; jeśli masz dwie zmiany w różnych miejscach, otrzymasz dwa wpisy, takie jak @@ -1,5 +1,5 @@; załóżmy, że masz plik linia 1 ... linia 100 i usunięty wiersz 10 i dodajesz nową linię 100 - otrzymasz:
@@ -7,7 +7,6 @@ line6
 line7
 line8
 line9
-this line10 to be deleted
 line11
 line12
 line13
@@ -98,3 +97,4 @@ line97
 line98
 line99
 line100
+this is new line100
irudyak
źródło
Dzięki. „100644 to bity trybu, wskazujące, że jest to zwykły plik (nie jest wykonywalny i nie jest dowiązaniem symbolicznym)”. Czy „bity trybu” są koncepcją w Linuksie, czy tylko w Git?
Tim
@Tim Nie specyficzne dla git. Prawe 3 cyfry ( 644) należy odczytywać ósemkowo (wartości: odpowiednio 1, 2, 4 eXecute, uprawnienie Write i Read) i odpowiada w tej kolejności właścicielowi (użytkownikowi), następnie grupie, a następnie innym uprawnieniom. Krótko mówiąc, 644oznaczałoby to, jeśli napisane symbolicznie u=rw,og=r, jest czytelne dla wszystkich, ale zapisywane tylko przez właściciela. Pozostałe cyfry po lewej kodują inne informacje, np. Jeśli jest to dowiązanie symboliczne itp. Wartości można zobaczyć github.com/git/git/blob/... , pierwsza 1 w tej pozycji to „zwykły plik”.
Patrick Mevzek,
15

Domyślny format wyjściowy (który pierwotnie pochodzi z programu znanego tak diff, jakbyś chciał poszukać więcej informacji) jest znany jako „zunifikowany plik różnicowy”. Zawiera zasadniczo 4 różne rodzaje linii:

  • linie kontekstowe, które zaczynają się od pojedynczej spacji,
  • linie wstawiania, które pokazują wstawioną linię, które zaczynają się od +,
  • linie usuwania, które zaczynają się od -, i
  • wiersze metadanych opisujące rzeczy wyższego poziomu, takie jak plik, o którym mowa, jakie opcje zostały użyte do wygenerowania różnicy, czy plik zmienił uprawnienia itp.

Radzę ćwiczyć czytanie różnic między dwiema wersjami pliku, w których dokładnie wiesz, co zmieniłeś. W ten sposób rozpoznasz, co się dzieje, kiedy to zobaczysz.

Donal Fellows
źródło
5
+1: Sugestia dotycząca praktyki jest bardzo dobra - prawdopodobnie znacznie szybsza niż próba obsesyjnego czytania dokumentacji.
Cascabel
6

Na moim komputerze Mac:

info diffnastępnie wybierz: Output formats-> Context-> Unified format-> Detailed Unified:

Lub online man diff na GNU, podążając tą samą ścieżką do tej samej sekcji:

Plik: diff.info, węzeł: szczegółowy ujednolicony, następny: przykład ujednolicony, w górę: ujednolicony format

Szczegółowy opis ujednoliconego formatu ......................................

Ujednolicony format wyjściowy zaczyna się od dwuwierszowego nagłówka, który wygląda następująco:

 --- FROM-FILE FROM-FILE-MODIFICATION-TIME
 +++ TO-FILE TO-FILE-MODIFICATION-TIME

Znacznik czasu wygląda jak „2002-02-21 23: 30: 39.942229878 -0800”, aby wskazać datę, godzinę z ułamkami sekund i strefę czasową.

Możesz zmienić treść nagłówka za pomocą opcji `--label = LABEL '; patrz * Uwaga Alternatywne nazwy ::.

Następnie przychodzi jeden lub więcej kawałków różnic; każdy przystojniak pokazuje jeden obszar, w którym różnią się pliki. Kawałki ujednoliconego formatu wyglądają tak:

 @@ FROM-FILE-RANGE TO-FILE-RANGE @@
  LINE-FROM-EITHER-FILE
  LINE-FROM-EITHER-FILE...

Linie wspólne dla obu plików rozpoczynają się znakiem spacji. Linie, które faktycznie różnią się między dwoma plikami, mają jeden z następujących znaków wskaźnikowych w lewej kolumnie wydruku:

`+ 'Tutaj dodano linię do pierwszego pliku.

`- 'Linia została tutaj usunięta z pierwszego pliku.

stefanB
źródło
1
Zauważ, że git nie drukuje części „XXX-FILE-MODIFICATION-TIME”, ponieważ nie ma to sensu dla systemu kontroli wersji. Do porównywania plików w systemie plików znaczniki czasu mogą działać jako kontrola wersji „ubogich”.
Jakub Narębski
3

Z twojego pytania nie jest jasne, która część różnic jest dla Ciebie myląca: tak naprawdę różnica lub dodatkowe informacje nagłówka drukowane przez git. Na wszelki wypadek oto krótki przegląd nagłówka.

Pierwszy wiersz jest coś w stylu diff --git a/path/to/file b/path/to/file- oczywiście mówi tylko, dla którego pliku przeznaczona jest ta sekcja diff. Jeśli ustawisz zmienną logiczną config diff.mnemonic prefixThe aa bzostanie zmieniony na bardziej opisowych liter jak ci w(commit i drzewa pracy).

Następnie są „wiersze trybu” - wiersze opisujące wszelkie zmiany, które nie wymagają zmiany zawartości pliku. Dotyczy to nowych / usuniętych plików, plików o zmienionych nazwach / skopiowanych oraz zmian uprawnień.

Wreszcie jest taka linia index 789bd4..0afb621 100644. Prawdopodobnie nigdy nie będziesz się tym przejmować, ale te 6-cyfrowe liczby szesnastkowe to skróty skrótów SHA1 starych i nowych obiektów blob dla tego pliku (obiekt blob to obiekt git przechowujący surowe dane, takie jak zawartość pliku). I oczywiście 100644jest to tryb pliku - ostatnie trzy cyfry to oczywiście uprawnienia; pierwsze trzy zawierają dodatkowe informacje o metadanych pliku ( opis SO opisujący to ).

Następnie przejdziesz do standardowego zunifikowanego wyjścia różnicowego (podobnie jak klasyczny diff -U). Podzielony jest na porcje - porcja to sekcja pliku zawierająca zmiany i ich kontekst. Każdy przystojniak jest poprzedzony parą linii ---i +++oznaczających dany plik, a następnie rzeczywistą różnicą są (domyślnie) trzy linie kontekstu po obu stronach linii -i +linie pokazujące usunięte / dodane linie.

Cascabel
źródło
++ dla indexlinii. Potwierdzonygit hash-object ./file
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功