Como ler a saída do git diff?

271

A página de manual git-diffé bastante longa e explica muitos casos que não parecem necessários para um iniciante. Por exemplo:

git diff origin/master
poseid
fonte
1
usando um editor de texto diferente, as notações de intervalo @ ... @ para números de linha se tornaram óbvias.
poseid 7/10/10
Qual editor de texto?
Jus12 26/04

Respostas:

489

Vamos dar uma olhada no exemplo diff avançado do histórico do git (no commit 1088261f no repositório 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] == '-') {

Vamos analisar esse patch linha por linha.

  • A primeira linha

    diff --git a / builtin-http-fetch.cb / http-fetch.c
    é um cabeçalho "git diff" no formulário diff --git a/file1 b/file2. Os nomes de arquivo a/e b/são os mesmos, a menos que renomear / copiar esteja envolvido (como no nosso caso). A --gité para significar que diff está no formato diff "git".

  • A seguir, estão uma ou mais linhas de cabeçalho estendidas. Os três primeiros

    índice de similaridade 95%
    renomeie de builtin-http-fetch.c
    renomeie para http-fetch.c
    diga-nos que o arquivo foi renomeado de builtin-http-fetch.cpara http-fetch.ce que esses dois arquivos são 95% idênticos (que foram usados ​​para detectar essa renomeação).

    A última linha no cabeçalho diff estendido, que é
    índice f3e63d7..e8f44ba 100644
    informe-nos sobre o modo de um determinado arquivo ( 100644significa que ele é um arquivo comum e não, por exemplo, link simbólico, e que não possui um bit de permissão executável) e sobre um hash reduzido de pré-imagem (a versão do arquivo antes da alteração) e a pós-imagem (a versão do arquivo após a alteração). Essa linha é usada git am --3waypara tentar fazer uma mesclagem de 3 vias se o patch não puder ser aplicado.

  • Em seguida é o cabeçalho diff unificado de duas linhas

    --- a / builtin-http-fetch.c
    +++ b / http-fetch.c
    Comparado ao diff -Uresultado, ele não possui nomes de arquivo de tempo de modificação de arquivo nem de tempo de modificação de arquivo após os nomes dos arquivos de origem (pré-imagem) e de destino (pós-imagem). Se o arquivo foi criado, a fonte é /dev/null; se o arquivo foi excluído, o destino é /dev/null.
    Se você definir diff.mnemonicPrefixvariável de configuração para true, no lugar de a/e b/prefixos neste cabeçalho de duas linhas que você pode ter ao invés c/, i/, w/e o/como prefixos, respectivamente para o que você comparar; veja git-config (1)

  • Em seguida, vêm um ou mais pedaços de diferenças; cada pedaço mostra uma área em que os arquivos diferem. Pedaços de formato unificado começam com linhas como

    @@ -1,8 +1,9 @@
    ou
    @@ -18,6 +19,8 @@ int cmd_http_fetch (int argc, const char ** argv, ...
    É no formato @@ from-file-range to-file-range @@ [header]. O intervalo from-file está no formato -<start line>,<number of lines>e to-file-range +<start line>,<number of lines>. Tanto a linha inicial quanto o número de linhas referem-se à posição e comprimento do pedaço na pré-imagem e pós-imagem, respectivamente. Se o número de linhas não mostrado, significa que é 0.

    O cabeçalho opcional mostra a função C em que cada alteração ocorre, se for um arquivo C (como -popção no GNU diff) ou o equivalente, se houver, para outros tipos de arquivos.

  • Em seguida, vem a descrição de onde os arquivos diferem. As linhas comuns aos dois arquivos começam com um caractere de espaço. As linhas que realmente diferem entre os dois arquivos têm um dos seguintes caracteres indicadores na coluna de impressão esquerda:

    • '+' - Uma linha foi adicionada aqui ao primeiro arquivo.
    • '-' - Uma linha foi removida aqui do primeiro arquivo.


    Então, por exemplo, primeiro pedaço

     #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 que cmd_http_fetchfoi substituído por maine essa const char *prefix;linha foi adicionada.

    Em outras palavras, antes da mudança, o fragmento apropriado do arquivo 'builtin-http-fetch.c' era assim:

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

    Após a alteração, este fragmento do arquivo 'http-fetch.c' se parece com o seguinte:

    #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;
    
  • Pode haver

    \ Nenhuma nova linha no final do arquivo
    linha presente (não é no exemplo diff).

Como Donal Fellows disse , é melhor praticar a leitura de diferenças em exemplos da vida real, onde você sabe o que mudou.

Referências:

Jakub Narębski
fonte
1
@ Geremia: O Git usa heurísticas baseadas em similaridade para renomear a detecção ... e também para mover código e detectar cópias git blame -C -C, é assim que funciona; é uma decisão de design do Git. O formato git diff mostra apenas o índice de semelhança (ou dissimilaridade) com o usuário.
Jakub Narębski
1
@ Geremia: Para ser mais exato, [header]é o precedente mais próximo, como no início da função que precede um pedaço. Na maioria dos casos, essa linha inclui o nome da função na qual está o pedaço de diff. Isso é configurável com o diffgitattribute definido como driver diff, e driver diff incluindo a xfuncnamevariável de configuração.
Jakub Narębski
1
@AnthonyGeoghegan: as linhas podem ser excluídas (o número de linhas na pós-imagem é 0) ou adicionadas (o número de linhas na pré-imagem é 0).
Jakub Narębski
1
@KasunSiyambalapitiya: O formato diff unificado que o Git usa (em oposição ao formato diff de contexto ^ [1]) não distingue entre linha modificada e linha removida e adicionada. [1]: gnu.org/software/diffutils/manual/html_node/Context-Format.html
Jakub Narębski
1
@ JakubNarębski: O número de linhas padrão é 1, e não 0. É tão simples quanto isso. Na prática, ele aparece apenas como "-1" e / ou "+1" para arquivos de linha única porque não há contexto para exibição.
precisa
68

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

Essa parte demorou um pouco para entender, então criei um exemplo mínimo.

O formato é basicamente o mesmo que o diff -udiff unificado.

Por exemplo:

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

Aqui removemos as linhas 2, 3, 14 e 15. Saída:

@@ -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,6significa que esta parte do primeiro arquivo começa na linha 1 e mostra um total de 6 linhas. Portanto, mostra as linhas 1 a 6.

    1
    2
    3
    4
    5
    6
    

    -significa "antigo", como normalmente chamamos diff -u old new.

  • +1,4significa que esta parte do segundo arquivo começa na linha 1 e mostra um total de 4 linhas. Portanto, mostra as linhas 1 a 4.

    + significa "novo".

    Temos apenas 4 linhas em vez de 6 porque 2 linhas foram removidas! O novo pedaço é apenas:

    1
    4
    5
    6
    

@@ -11,6 +9,4 @@ para o segundo pedaço é análogo:

  • no arquivo antigo, temos 6 linhas, começando na linha 11 do arquivo antigo:

    11
    12
    13
    14
    15
    16
    
  • no novo arquivo, temos 4 linhas, começando na linha 9 do novo arquivo:

    11
    12
    13
    16
    

    Observe que a linha 11é a 9a linha do novo arquivo, porque já removemos duas linhas no pedaço anterior: 2 e 3.

Cabeçalho do pedaço

Dependendo da versão e configuração do git, você também pode obter uma linha de código próxima à @@linha, por exemplo, func1() {in:

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

Isso também pode ser obtido com a -pbandeira da planície diff.

Exemplo: arquivo antigo:

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

Se removermos a linha 6, o diff mostrará:

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

Observe que esta não é a linha correta para func1: pulou as linhas 1e 2.

Esse recurso impressionante geralmente informa exatamente a qual função ou classe cada pedaço pertence, o que é muito útil para interpretar o diff.

Como o algoritmo para escolher o cabeçalho funciona exatamente é discutido em: De onde vem o trecho do cabeçalho do git diff hunk?

Ciro Santilli adicionou uma nova foto
fonte
11
Isso é para quem ainda não entendeu direito. Em @@ -1,6 +1,4 @@pls, não leia -1como minus oneou, +1em plus onevez disso, leia isso como line 1 to 6no antigo (primeiro) arquivo. Observe aqui - implies "old"não menos. BTW, obrigado pelo esclarecimento ... haash.
Dkjain 2/07
a partir disso @@ -1,8 +1,9 @@ é possível interpretar o que realmente aconteceu. por exemplo 1) uma linha foi adicionada 2) uma linha está sendo modificada e uma linha está sendo adicionada e assim por diante. Ou é de outra maneira, como deve haver uma maneira de obtê-los, já que o git diff corretamente identifica quais linhas foram modificadas no código. Por favor me ajude como eu realmente preciso resolver isso
Kasun Siyambalapitiya
Observe que esta declaração é incorreta e muito enganadora na resposta acima: " +1,4diz que esta peça corresponde à linha 1 a 4 do segundo arquivo ". Isso ocorre porque +1,4pode se referir a linhas de contexto não contingentes. Pelo contrário, o que " +1,4" realmente significa é que " existem 4linhas (isto é, linhas de contexto) nessa 'versão' do arquivo ". É importante compreender o significado do +, -e <whitespace>no início dessas linhas, que se aplica à interpretação dos pedaços. Um exemplo mais visual: youtube.com/watch?v=1tqMjJeyKpw
Damilola Olowookere
23

Aqui está o exemplo simples.

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

Aqui está uma explicação (veja detalhes aqui ).

  • --git não é um comando, isso significa que é uma versão git do diff (não unix)
  • a/ b/são diretórios, eles não são reais. é apenas uma conveniência quando lidamos com o mesmo arquivo (no meu caso, a / está no índice eb / está no diretório de trabalho)
  • 10ff2df..84d4fa2 são IDs de blob desses 2 arquivos
  • 100644 são os "bits de modo", indicando que este é um arquivo comum (não executável e não um link simbólico)
  • --- a/file +++ b/filesinais de menos mostra linhas na versão a / mas ausentes na versão b /; e sinais de mais mostram linhas ausentes em a / mas presentes em b / (no meu caso --- significa linhas excluídas e +++ significa linhas adicionadas em b / e este é o arquivo no diretório de trabalho)
  • @@ -1,5 +1,5 @@para entender isso, é melhor trabalhar com um arquivo grande; se você tiver duas alterações em lugares diferentes, receberá duas entradas como @@ -1,5 +1,5 @@; suponha que você tenha o arquivo line1 ... line100 e excluiu a line10 e adicione a nova line100 - você receberá:
@@ -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
fonte
Obrigado. "100644 são os bits de modo, indicando que este é um arquivo regular (não executável e não um link simbólico)". "Mode bits" é um conceito no Linux ou apenas no Git?
Tim
@ Tim Não é específico para o git. Os 3 dígitos corretos ( 644) devem ser lidos em octal (valores: 1, 2, 4 respectivamente, permissão eXecute, Write e Read) e correspondem nessa ordem a Proprietário (Usuário), Grupo e Outras permissões. Então, resumindo 644, se escrito simbolicamente u=rw,og=r, é legível para todos, mas é gravável apenas pelo proprietário. Os outros dígitos à esquerda codificam outras informações, como se fosse um link simbólico, etc. Os valores podem ser vistos em github.com/git/git/blob/… , o primeiro 1 nesta posição é "arquivo regular".
Patrick Mevzek 30/08/19
15

O formato de saída padrão (originalmente proveniente de um programa conhecido como diffse você deseja procurar mais informações) é conhecido como "diff unificado". Ele contém essencialmente 4 tipos diferentes de linhas:

  • linhas de contexto, que começam com um único espaço,
  • linhas de inserção que mostram uma linha que foi inserida, que começam com um +,
  • linhas de deleção, que começam com um -, e
  • linhas de metadados que descrevem coisas de nível superior, como qual arquivo está falando, quais opções foram usadas para gerar o diff, se o arquivo alterou suas permissões etc.

Aconselho que você pratique a leitura de diferenças entre duas versões de um arquivo em que você sabe exatamente o que mudou. Assim, você reconhecerá exatamente o que está acontecendo quando o vir.

Donal Fellows
fonte
5
+1: a sugestão sobre a prática é muito boa - provavelmente muito mais rápida do que tentar ler obsessivamente a documentação.
Cascabel 27/03
6

No meu mac:

info diffdepois selecione: Output formats-> Context-> Unified format-> Detailed Unified:

Ou o homem online difere no gnu seguindo o mesmo caminho para a mesma seção:

Arquivo: diff.info, Nó: Unificado detalhado, Próximo: Exemplo unificado, Acima: Formato unificado

Descrição detalhada do formato unificado ......................................

O formato de saída unificado começa com um cabeçalho de duas linhas, parecido com este:

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

O registro de data e hora parece "2002-02-21 23: 30: 39.942229878 -0800" para indicar a data, a hora com segundos fracionários e o fuso horário.

Você pode alterar o conteúdo do cabeçalho com a opção `--label = LABEL '; veja * Nota Nomes alternativos ::.

Em seguida, vêm um ou mais pedaços de diferenças; cada pedaço mostra uma área em que os arquivos diferem. Pedaços de formato unificado são assim:

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

As linhas comuns aos dois arquivos começam com um caractere de espaço. As linhas que realmente diferem entre os dois arquivos têm um dos seguintes caracteres indicadores na coluna de impressão esquerda:

`+ 'Uma linha foi adicionada aqui ao primeiro arquivo.

`- 'Uma linha foi removida aqui do primeiro arquivo.

stefanB
fonte
1
Observe que o git não imprime a parte 'XXX-FILE-MODIFICATION-TIME', pois não faz sentido para o sistema de controle de versão. Para comparar arquivos nos arquivos de data e hora do sistema de arquivos, pode funcionar como controle de versão "pobre homem".
Jakub Narębski 27/03
3

Não está claro em sua pergunta qual parte das diferenças você considera confusa: as informações realmente diff ou extra, que o git imprime. Apenas no caso, aqui está uma rápida visão geral do cabeçalho.

A primeira linha é algo como diff --git a/path/to/file b/path/to/file- obviamente, está apenas dizendo a você para qual arquivo esta seção do diff é. Se você definir a variável de configuração booleana diff.mnemonic prefix, o ae bserá alterado para letras mais descritivas como ce w(confirmação e árvore de trabalho).

Em seguida, existem "linhas de modo" - linhas que fornecem uma descrição de quaisquer alterações que não envolvam a alteração do conteúdo do arquivo. Isso inclui arquivos novos / excluídos, arquivos renomeados / copiados e alterações nas permissões.

Finalmente, há uma linha como index 789bd4..0afb621 100644. Você provavelmente nunca se importará com isso, mas esses números hexadecimais de 6 dígitos são os hashes SHA1 abreviados dos blobs antigos e novos desse arquivo (um blob é um objeto git que armazena dados brutos como o conteúdo de um arquivo). E, claro, esse 100644é o modo do arquivo - os três últimos dígitos são obviamente permissões; os três primeiros fornecem informações extras sobre metadados de arquivos ( publicação SO descrevendo isso ).

Depois disso, você estará na saída diff unificada padrão (como no clássico diff -U). Ele é dividido em pedaços - um pedaço é uma seção do arquivo que contém alterações e seu contexto. Cada pedaço é precedido por um par de ---e +++linhas que indicam o arquivo em questão; o diff real é (por padrão) três linhas de contexto em ambos os lados das linhas -e +mostrando as linhas removidas / adicionadas.

Cascabel
fonte
++ para a indexlinha. Confirmado comgit hash-object ./file
Ciro Santilli,