Come leggere l’output di git diff?

La pagina man per git-diff è piuttosto lunga e spiega molti casi che non sembrano essere necessari per un principiante. Per esempio:

 git diff origin/master 

Diamo un’occhiata alla differenza avanzata di esempio dalla cronologia git (in commit 1088261f nel repository git.git ):

 diff --git a/builtin-http-fetch.cb/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] == '-') { 

Analizziamo questa patch riga per riga.

  • La prima linea

      diff --git a / builtin-http-fetch.cb / http-fetch.c 

    è un'intestazione "git diff" nella forma diff --git a/file1 b/file2 . I nomi di a/ e b/ file sono gli stessi a meno che non sia necessario rinominare / copiare (come nel nostro caso). --git significa che diff è nel formato diff di "git".

  • Successivamente ci sono una o più linee di intestazione estese. I primi tre

      indice di somiglianza del 95%
     rinomina da builtin-http-fetch.c
     rinominare in http-fetch.c 

    dicci che il file è stato rinominato da builtin-http-fetch.c a http-fetch.c e che quei due file sono identici al 95% (che è stato usato per rilevare questo rinominare).

    L'ultima riga nell'intestazione estesa di diff, che è

      indice f3e63d7..e8f44ba 100644 

    parlaci della modalità del file dato ( 100644 significa che è un file ordinario e non eg symlink, e che non ha bit di authorization eseguibile), e un accorciato di hash di preimage (la versione del file prima della modifica data) e postimage ( la versione del file dopo la modifica). Questa linea è usata da git am --3way per provare a fare git am --3way a 3 vie se la patch non può essere applicata da sola.

  • La prossima è un'intestazione di separazione unificata a due righe

      --- a / builtin-http-fetch.c
     +++ b / http-fetch.c 

    Rispetto al risultato diff -U non ha il tempo di file-modifica-tempo né to-file-modifica-tempo dopo il file sorgente (preimage) e di destinazione (postimmagine). Se il file è stato creato, la fonte è /dev/null ; se il file è stato cancellato, la destinazione è /dev/null .
    Se imposti la variabile di configurazione diff.mnemonicPrefix su true, al posto di a/ e b/ prefissi in questa intestazione a due righe puoi avere invece c/ , i/ , w/ e o/ come prefissi, rispettivamente a ciò che confronti; vedi git-config (1)

  • Poi vengono uno o più pezzi di differenze; ogni pezzo mostra un'area in cui i file differiscono. Gli hunks in formato unificato iniziano con linee simili

      @@ -1,8 +1,9 @@ 

    o

      @@ -18,6 +19,8 @@ int cmd_http_fetch (int argc, const char ** argv, ... 

    È nel formato @@ from-file-range to-file-range @@ [header] . Il campo da file è nel formato -, e to-file-range è +, . Sia la linea di partenza che il numero di linee si riferiscono rispettivamente alla posizione e alla lunghezza del pezzo in preimage e postimage. Se il numero di linee non mostrato significa che è 0.

    L'intestazione opzionale mostra la funzione C in cui si verifica ogni cambiamento, se si tratta di un file C (come l'opzione -p in GNU diff) o l'equivalente, se esiste, per altri tipi di file.

  • Segue la descrizione di dove i file differiscono. Le linee comuni a entrambi i file iniziano con un carattere di spazio. Le linee che in realtà differiscono tra i due file hanno uno dei seguenti caratteri indicatore nella colonna di stampa sinistra:

    • '+' - Una riga è stata aggiunta qui al primo file.
    • '-' - Una riga è stata rimossa qui dal primo file.

    Quindi, ad esempio, il primo pezzo

      #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; 

    significa che cmd_http_fetch stato sostituito da main e dal const char *prefix; linea è stata aggiunta.

    In altre parole, prima del cambiamento, il frammento appropriato del file "builtin-http-fetch.c" appariva così:

     #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; 

    Dopo la modifica, questo frammento del file "http-fetch.c" ora appare invece:

     #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; 
  • Ci potrebbe essere

      \ No newline alla fine del file 

    linea presente (non è in diff diff.).

Come ha detto Donal Fellows , è meglio fare pratica con la lettura delle differenze su esempi di vita reale, in cui sai cosa hai cambiato.

Riferimenti:

  • git-diff (1) manpage , sezione "Generazione di patch con -p"
  • (diff.info) Nodo unificato dettagliato , "Descrizione dettagliata del formato unificato".

@@ -1,2 +3,4 @@ parte del diff

Questa parte mi ha portato un po ‘a capire, quindi ho creato un esempio minimo.

Il formato è sostanzialmente lo stesso diff -u unified diff.

Per esempio:

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

Qui abbiamo rimosso le righe 2, 3, 14 e 15. Uscita:

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

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

  • -1,6 : questo pezzo corrisponde alle righe da 1 a 6 del primo file:

     1 2 3 4 5 6 

    - significa "vecchio", come di solito lo invochiamo come diff -u old new .

  • +1,4 dice che questo pezzo corrisponde alle righe da 1 a 4 del secondo file.

    + significa "nuovo".

    Abbiamo solo 4 righe anziché 6 perché sono state rimosse 2 linee! Il nuovo hunk è solo:

     1 4 5 6 

@@ -11,6 +9,4 @@ per il secondo hunk è analogo:

  • sul vecchio file, abbiamo 6 righe, a partire dalla riga 11 del vecchio file:

     11 12 13 14 15 16 
  • sul nuovo file, abbiamo 4 righe, a partire dalla riga 9 del nuovo file:

     11 12 13 16 

    Nota che la riga 11 è la nona riga del nuovo file perché abbiamo già rimosso 2 righe sul precedente pezzo: 2 e 3.

Intestazione Hunk

A seconda della versione e della configurazione di git, puoi anche ottenere una riga di codice accanto alla riga @@ , ad esempio func1() { in:

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

Questo può anche essere ottenuto con il flag -p di plain diff .

Esempio: vecchio file:

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

Se rimuoviamo la linea 6 , il diff mostra:

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

Si noti che questa non è la riga corretta per func1 : ha saltato le righe 1 e 2 .

Questa fantastica funzione spesso indica esattamente a quale funzione o class appartiene ciascun hunk, che è molto utile per interpretare il diff.

Come funziona esattamente l'algoritmo per la scelta dell'intestazione viene discusso in: Da dove proviene l'estratto nell'intestazione di ged diff hunk?

Ecco il semplice esempio.

 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 

Ecco una spiegazione (vedi dettagli qui ).

  • --git non è un comando, questo significa che è una versione git di diff (non unix)
  • a/ b/ sono directory, non sono reali. è solo una comodità quando gestiamo lo stesso file (nel mio caso a / è in index eb / è in working directory)
  • 10ff2df..84d4fa2 sono ID blob di questi 2 file
  • 100644 è il “bit di modalità”, che indica che questo è un file normale (non eseguibile e non un collegamento simbolico)
  • --- a/file +++ b/file meno segni mostra le linee nella versione / a ma mancante dalla versione / b; e più segni mostra linee mancanti in a / ma presenti in b / (nel mio caso — significa linee cancellate e +++ significa linee aggiunte in b / e questo il file nella directory di lavoro)
  • @@ -1,5 +1,5 @@ per capire questo è meglio lavorare con un grosso file; se hai due cambiamenti in luoghi diversi, riceverai due voci come @@ -1,5 +1,5 @@ ; supponiamo di avere file line1 … line100 e cancellati line10 e aggiungi new line100 – otterrai:
 @@ -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 

Il formato di output predefinito (che originariamente proviene da un programma noto come diff se si desidera cercare ulteriori informazioni) è noto come “diff unificato”. Contiene essenzialmente 4 diversi tipi di linee:

  • linee di contesto, che iniziano con un singolo spazio,
  • linee di inserimento che mostrano una linea che è stata inserita, che inizia con un + ,
  • linee di cancellazione, che iniziano con un - , e
  • linee di metadati che descrivono cose di livello superiore come il file di cui si parla, quali opzioni sono state usate per generare il diff, se il file ha cambiato i permessi, ecc.

Ti consiglio di esercitarti a leggere le differenze tra due versioni di un file in cui sai esattamente cosa hai cambiato. Così riconoscerai cosa sta succedendo quando lo vedi.

Sul mio Mac:

info diff quindi seleziona: Output formats -> Context -> Unified format -> Detailed Unified :

Oppure online man diff su gnu seguendo lo stesso percorso per la stessa sezione:

File: diff.info, Node: Dettagliato Unificato, Successivo: Esempio Unificato, Su: Unificato

Descrizione dettagliata del formato unificato ………………………………..

Il formato di output unificato inizia con un’intestazione a due righe, che assomiglia a questo:

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

L’indicazione data / ora è simile a “2002-02-21 23: 30: 39.942229878 -0800” per indicare la data, l’ora con i secondi frazionari e il fuso orario.

Puoi modificare il contenuto dell’intestazione con l’opzione `–label = LABEL ‘; vedi * Note Nomi alternativi ::.

Poi vengono uno o più pezzi di differenze; ogni pezzo mostra un’area in cui i file differiscono. Gli hunk in formato unificato sono simili a questo:

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

Le linee comuni a entrambi i file iniziano con un carattere di spazio. Le linee che in realtà differiscono tra i due file hanno uno dei seguenti caratteri indicatore nella colonna di stampa sinistra:

`+ ‘Qui è stata aggiunta una riga al primo file.

`- ‘Una riga è stata rimossa qui dal primo file.

Non è chiaro dalla tua domanda quale parte delle differenze trovi confusa: la diff vera o le informazioni extra sull’intestazione che git stampa. Nel caso, ecco una rapida panoramica dell’intestazione.

La prima linea è qualcosa come diff --git a/path/to/file b/path/to/file – ovviamente ti sta solo dicendo quale file è questa sezione del diff. Se si imposta il diff.mnemonic prefix variabile di configurazione booleana, a e b verranno modificati in lettere più descrittive come c e w (commit e work tree).

Successivamente, ci sono “righe di modalità”: linee che forniscono una descrizione di eventuali modifiche che non comportano la modifica del contenuto del file. Questo include i file nuovi / cancellati, i file rinominati / copiati e le modifiche alle autorizzazioni.

Infine, c’è una riga come l’ index 789bd4..0afb621 100644 . Probabilmente non ci penserai mai, ma quei numeri esadecimali a 6 cifre sono gli hash SHA1 abbreviati dei vecchi e nuovi blob per questo file (un blob è un object git che memorizza dati grezzi come il contenuto di un file). E, naturalmente, il 100644 è la modalità del file – le ultime tre cifre sono ovviamente permessi; i primi tre forniscono informazioni aggiuntive sui metadati del file ( post SO che lo descrive ).

Dopodiché, si passa all’output diff unificato standard (proprio come il diff -U classico). È diviso in hunk: un hunk è una sezione del file che contiene le modifiche e il loro contesto. Ogni hunk è preceduto da una coppia di righe --- e +++ che denotano il file in questione, quindi il diff reale è (di default) tre linee di contesto su entrambi i lati delle linee - e + che mostrano le linee rimosse / aggiunte .

Nel controllo della versione, le differenze tra due versioni sono presentate in quella che viene chiamata una “diff” (o, in alternativa, una “patch”). Diamo un’occhiata dettagliata a una tale differenza e impariamo a leggerla.

Guarda l’output di un diff. Basandoci su questa uscita, capiremo l’output diff di git.

inserisci la descrizione dell'immagine qui

File comparati a / b

Il nostro diff confronta due articoli l’uno con l’altro: articolo A e articolo B. Nella maggior parte dei casi, A e B saranno lo stesso file, ma in diverse versioni. Sebbene non sia usato molto spesso, un diff potrebbe anche confrontare due file completamente non correlati tra loro per mostrare come differiscono. Per chiarire cosa viene effettivamente confrontato, un’uscita diff inizia sempre dichiarando quali file sono rappresentati da “A” e “B”.

File metadati

I metadati del file mostrati qui sono informazioni molto tecniche che probabilmente non avrai mai bisogno nella pratica. I primi due numeri rappresentano gli hash (o, semplicemente, “ID”) dei nostri due file: Git salva ogni versione non solo del progetto ma anche di ogni file come object. Tale hash identifica un object file in una revisione specifica. L’ultimo numero è un identificatore di modalità file interno (100644 è solo un “file normale”, mentre 100755 specifica un file eseguibile e 120000 rappresenta un collegamento simbolico).

Indicatori per a / b

Più in basso nell’output, le modifiche effettive saranno contrassegnate come provenienti da A o B. Per distinguerle, A e B sono contrassegnati da un simbolo: per la versione A, questo è un segno meno (“-“) e per la versione B, viene utilizzato un segno più (“+”).

Pezzo

Un diff non mostra il file completo dall’inizio alla fine: non si vorrebbe vedere tutto in un file di 10.000 righe, quando sono state modificate solo 2 righe. Invece, mostra solo quelle parti che sono state effettivamente modificate. Tale porzione è chiamata “chunk” (o “hunk”). Oltre alle effettive linee modificate, un blocco contiene anche un po ‘di “contesto”: alcune linee (immutate) prima e dopo la modifica in modo da poter capire meglio in quale contesto si è verificato tale cambiamento.

Chunk Header

Ciascuno di questi blocchi è preceduto da un’intestazione. Incluso in due “@” segnali ciascuno, Git ti dice quali linee erano interessate. Nel nostro caso le seguenti linee sono rappresentate nel primo blocco:

  • Dal file A (rappresentato da un “-“), vengono estratte 6 linee, iniziando dalla riga n. 34

  • Dal file B (rappresentato da un “+”), vengono visualizzate 8 linee, anch’esse a partire dalla riga n. 34

Il testo dopo la coppia di chiusura di “@@” mira a chiarire nuovamente il contesto: Git prova a visualizzare un nome di metodo o altre informazioni contestuali su dove questo pezzo è stato preso nel file. Tuttavia, ciò dipende molto dal linguaggio di programmazione e non funziona in tutti gli scenari.

I cambiamenti

Ogni linea cambiata è preceduta da un simbolo “+” o “-“. Come spiegato, questi simboli ti aiutano a capire come appaiono esattamente la versione A e B: una linea che è preceduta da un segno “-” proviene da A, mentre una linea con un segno “+” viene da B. Nella maggior parte dei casi, Git picks A e B in modo tale da poter pensare ad A / – come “vecchio” contenuto e B / + come “nuovo” contenuto.

Diamo un’occhiata al nostro esempio:

  • Il cambiamento # 1 contiene due righe precedute da un “+”. Poiché per queste linee non esisteva alcuna controparte in A (nessuna riga con “-“), ciò significa che queste linee sono state aggiunte.

  • Il cambiamento n. 2 è esattamente l’opposto: in A, abbiamo due linee contrassegnate con i segni “-“. Tuttavia, B non ha un equivalente (nessuna “+” linea), il che significa che sono stati cancellati.

  • Con il cambiamento n. 3, infine, alcune linee sono state effettivamente modificate: le due linee “-” sono state cambiate per assomigliare alle due linee “+” sotto.

fonte