¿Cómo leer la salida de git diff?

271

La página de manual git-diffes bastante larga y explica muchos casos que no parecen ser necesarios para un principiante. Por ejemplo:

git diff origin/master
poseid
fuente
1
Al usar un editor de texto diferente, las anotaciones de rango @ ... @ para los números de línea se hicieron evidentes.
poseid
¿Qué editor de texto?
Jus12

Respuestas:

489

Veamos un ejemplo de diferencia avanzada del historial de git (en commit 1088261f en el repositorio de 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] == '-') {

Analicemos este parche línea por línea.

  • La primera línea

    diff --git a / builtin-http-fetch.cb / http-fetch.c
    es un encabezado "git diff" en el formulario diff --git a/file1 b/file2. Los nombres de archivo a/y b/son los mismos a menos que se trate de renombrar / copiar (como en nuestro caso). Esto --gitsignifica que diff está en el formato diff "git".

  • A continuación hay una o más líneas de encabezado extendidas. Los primeros tres

    índice de similitud 95%
    cambiar el nombre de builtin-http-fetch.c
    renombrar a http-fetch.c
    díganos que se cambió el nombre del archivo builtin-http-fetch.ca http-fetch.cy que esos dos archivos son 95% idénticos (que se utilizó para detectar este cambio de nombre).

    La última línea en el encabezado diff extendido, que es
    índice f3e63d7..e8f44ba 100644
    cuéntenos sobre el modo del archivo dado ( 100644significa que es un archivo ordinario y no, por ejemplo, un enlace simbólico, y que no tiene un bit de permiso ejecutable), y sobre hash acortado de preimagen (la versión del archivo antes del cambio dado) y postimagen (el versión del archivo después del cambio). Esta línea se utiliza git am --3waypara intentar hacer una fusión de 3 vías si el parche no se puede aplicar por sí mismo.

  • El siguiente es el encabezado diff unificado de dos líneas

    --- a / builtin-http-fetch.c
    +++ b / http-fetch.c
    En comparación con el diff -Uresultado, no tiene el tiempo de modificación de archivo ni el tiempo de modificación de archivo después de los nombres de los archivos de origen (imagen previa) y destino (imagen posterior). Si se creó el archivo, la fuente es /dev/null; si el archivo fue eliminado, el objetivo es /dev/null.
    Si se establece diff.mnemonicPrefixla variable de configuración de verdad, en lugar de a/y b/prefijos en esta cabecera de dos líneas que puede tener lugar c/, i/, w/y o/como prefijos, respectivamente, a lo que se compara; ver git-config (1)

  • Luego vienen uno o más trozos de diferencias; cada trozo muestra un área donde los archivos difieren. Los formatos unificados comienzan con líneas como

    @@ -1,8 +1,9 @@
    o
    @@ -18,6 +19,8 @@ int cmd_http_fetch (int argc, const char ** argv, ...
    Está en el formato @@ from-file-range to-file-range @@ [header]. From-file-range tiene el formato -<start line>,<number of lines>y to-file-range es +<start line>,<number of lines>. Tanto la línea de inicio como el número de líneas se refieren a la posición y la longitud del trozo en preimagen y postimagen, respectivamente. Si no se muestra el número de líneas, significa que es 0.

    El encabezado opcional muestra la función C donde se produce cada cambio, si es un archivo C (como la -popción en GNU diff), o el equivalente, si lo hay, para otros tipos de archivos.

  • Luego viene la descripción de dónde difieren los archivos. Las líneas comunes a ambos archivos comienzan con un carácter de espacio. Las líneas que realmente difieren entre los dos archivos tienen uno de los siguientes caracteres indicadores en la columna de impresión izquierda:

    • '+': Aquí se agregó una línea al primer archivo.
    • '-' - Aquí se eliminó una línea del primer archivo.


    Entonces, por ejemplo, primer trozo

     #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_fetchfue reemplazado por mainy que const char *prefix;se agregó esa línea.

    En otras palabras, antes del cambio, el fragmento apropiado del archivo 'builtin-http-fetch.c' se veía así:

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

    Después del cambio, este fragmento del ahora archivo 'http-fetch.c' se ve así en su lugar:

    #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;
    
  • Podría haber

    \ No hay nueva línea al final del archivo
    línea presente (no es, por ejemplo, diff).

Como dijo Donal Fellows , es mejor practicar diferencias de lectura en ejemplos de la vida real, donde sabes lo que has cambiado.

Referencias

Jakub Narębski
fuente
1
@Geremia: Git utiliza heurísticas basadas en similitudes para la detección de cambio de nombre ... y también para el movimiento de código y la detección de copia git blame -C -C, así es como funciona; Es la decisión de diseño de Git. El formato git diff solo muestra el índice de similitud (o disimilitud) para el usuario.
Jakub Narębski
1
@Geremia: Para ser más exactos, [header]es el precedente más cercano, como el comienzo de la función que precede a un trozo. En la mayoría de los casos, esta línea incluye el nombre de la función en la que se encuentra el fragmento de diff. Esto es configurable con diffgitattribute establecido en el controlador diff, y el controlador diff incluyendo la xfuncnamevariable de configuración.
Jakub Narębski
1
@AnthonyGeoghegan: las líneas pueden eliminarse (el número de líneas en la imagen posterior es 0) o agregarse (el número de líneas en la imagen previa es 0).
Jakub Narębski
1
@KasunSiyambalapitiya: El formato de diferencias unificadas que utiliza Git (en oposición al formato de diferencias de contexto ^ [1]) no distingue entre la línea modificada y la línea eliminada y agregada. [1]: gnu.org/software/diffutils/manual/html_node/Context-Format.html
Jakub Narębski
1
@ JakubNarębski: El número de líneas predeterminado es 1, no 0. Es tan simple como eso. En la práctica, solo aparece como "-1" y / o "+1" para archivos de una sola línea porque no hay contexto para mostrar.
Guido Flohr
68

@@ -1,2 +3,4 @@ parte de la diferencia

Esta parte me llevó un tiempo comprender, así que he creado un ejemplo mínimo.

El formato es básicamente el mismo que el diff -udiff unificado.

Por ejemplo:

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

Aquí eliminamos las líneas 2, 3, 14 y 15. Salida:

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

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

  • -1,6significa que esta parte del primer archivo comienza en la línea 1 y muestra un total de 6 líneas. Por lo tanto, muestra las líneas 1 a 6.

    1
    2
    3
    4
    5
    6
    

    -significa "viejo", como solemos invocarlo como diff -u old new.

  • +1,4significa que esta parte del segundo archivo comienza en la línea 1 y muestra un total de 4 líneas. Por lo tanto, muestra las líneas 1 a 4.

    + significa "nuevo".

    ¡Solo tenemos 4 líneas en lugar de 6 porque se eliminaron 2 líneas! El nuevo trozo es solo:

    1
    4
    5
    6
    

@@ -11,6 +9,4 @@ para el segundo trozo es análogo:

  • en el archivo anterior, tenemos 6 líneas, comenzando en la línea 11 del archivo anterior:

    11
    12
    13
    14
    15
    16
    
  • en el nuevo archivo, tenemos 4 líneas, comenzando en la línea 9 del nuevo archivo:

    11
    12
    13
    16
    

    Tenga en cuenta que la línea 11es la novena línea del nuevo archivo porque ya hemos eliminado 2 líneas en el trozo anterior: 2 y 3.

Encabezado Hunk

Dependiendo de su versión y configuración de git, también puede obtener una línea de código junto a la @@línea, por ejemplo, func1() {en:

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

Esto también se puede obtener con la -pbandera de la llanura diff.

Ejemplo: archivo antiguo:

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

Si eliminamos la línea 6, la diferencia muestra:

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

Tenga en cuenta que esta no es la línea correcta para func1: omitió líneas 1y 2.

Esta característica impresionante a menudo dice exactamente a qué función o clase pertenece cada trozo, lo cual es muy útil para interpretar la diferencia.

La forma en que funciona exactamente el algoritmo para elegir el encabezado se analiza en: ¿De dónde proviene el extracto del encabezado git diff hunk?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
11
Esto es para cualquiera que aún no lo haya entendido. En los @@ -1,6 +1,4 @@pls, no lea -1como minus oneo +1en su plus onelugar, lea esto como line 1 to 6en el archivo anterior (primero). Tenga en cuenta aquí - implies "old"no menos. Por cierto, gracias por la aclaración ... haash.
dkjain
a partir de esto @@ -1,8 +1,9 @@ es posible interpretar lo que realmente ha sucedido. por ejemplo 1) se ha agregado una línea 2) se está modificando una línea y se agrega una línea y así sucesivamente. O es de otra manera, ya que debería haber una forma de obtenerlos, ya que la corrección de git diff identifica qué líneas se han modificado en el código. Por favor,
ayúdenme,
Tenga en cuenta que es incorrecta y muy engañosa, esta afirmación en la respuesta anterior: " +1,4dice que esta pieza corresponde a las líneas 1 a 4 del segundo archivo ". Esto se debe a que +1,4puede referirse a líneas de contexto no contingentes. Más bien, lo que " +1,4" significa en realidad es que " hay 4líneas (es decir, líneas de contexto) en esa 'versión' del archivo ". Es importante entender el significado de la +, -y <whitespace>al principio de estas líneas, ya que se aplica a la interpretación de los trozos. Un ejemplo más visual: youtube.com/watch?v=1tqMjJeyKpw
Damilola Olowookere
23

Aquí está el ejemplo simple.

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

Aquí hay una explicación (ver detalles aquí ).

  • --git no es un comando, esto significa que es una versión git de diff (no unix)
  • a/ b/son directorios, no son reales. es solo una conveniencia cuando tratamos con el mismo archivo (en mi caso, a / está en el índice y b / está en el directorio de trabajo)
  • 10ff2df..84d4fa2 son ID de blob de estos 2 archivos
  • 100644 son los "bits de modo", que indican que se trata de un archivo normal (no ejecutable y no un enlace simbólico)
  • --- a/file +++ b/filelos signos menos muestran líneas en la versión a / pero faltan en la versión b /; y los signos más muestran las líneas que faltan en a / pero están presentes en b / (en mi caso --- significa líneas eliminadas y +++ significa líneas agregadas en b / y este el archivo en el directorio de trabajo)
  • @@ -1,5 +1,5 @@para entender esto es mejor trabajar con un archivo grande; si tiene dos cambios en diferentes lugares obtendrá dos entradas como @@ -1,5 +1,5 @@; supongamos que tiene el archivo line1 ... line100 y line10 eliminado y agrega una nueva línea100: obtendrá:
@@ -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
fuente
Gracias. "100644 son los bits de modo, lo que indica que este es un archivo normal (no ejecutable y no un enlace simbólico)". ¿Es "bits de modo" un concepto en Linux, o solo en Git?
Tim
@Tim No es específico de git. Los 3 dígitos correctos ( 644) deben leerse en octal (valores: 1, 2, 4 respectivamente permisos de Ejecución, Escritura y Lectura) y corresponden en ese orden al Propietario (Usuario), luego al Grupo, luego a Otros permisos. En resumen 644, significaría que si está escrito simbólicamente u=rw,og=r, eso es legible para todos, pero solo el propietario puede escribirlo. Los otros dígitos de la izquierda codifican otra información, como si se trata de un enlace simbólico, etc. Los valores se pueden ver en github.com/git/git/blob/… , el primer 1 en esta posición es "archivo normal".
Patrick Mevzek
15

El formato de salida predeterminado (que originalmente proviene de un programa conocido como diffsi desea buscar más información) se conoce como "diff unificado". Contiene esencialmente 4 tipos diferentes de líneas:

  • líneas de contexto, que comienzan con un solo espacio,
  • líneas de inserción que muestran una línea que se ha insertado, que comienzan con un +,
  • líneas de eliminación, que comienzan con a -, y
  • líneas de metadatos que describen cosas de nivel superior, como de qué archivo está hablando, qué opciones se usaron para generar la diferencia, si el archivo cambió sus permisos, etc.

Le aconsejo que practique la lectura de diferencias entre dos versiones de un archivo donde sepa exactamente lo que cambió. Así reconocerás lo que está sucediendo cuando lo veas.

Compañeros de Donal
fuente
55
+1: La sugerencia sobre la práctica es muy buena, probablemente mucho más rápido que tratar de leer obsesivamente la documentación.
Cascabel
6

En mi mac:

info diffluego seleccione: Output formats-> Context-> Unified format-> Detailed Unified:

O man diff en línea en gnu siguiendo la misma ruta a la misma sección:

Archivo: diff.info, Nodo: Unificado detallado, Siguiente: Ejemplo unificado, Arriba: Formato unificado

Descripción detallada del formato unificado ......................................

El formato de salida unificado comienza con un encabezado de dos líneas, que se ve así:

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

La marca de tiempo se ve como '2002-02-21 23: 30: 39.942229878 -0800' para indicar la fecha, la hora con segundos fraccionarios y la zona horaria.

Puede cambiar el contenido del encabezado con la opción `--label = LABEL '; ver * Nota nombres alternativos ::.

Luego vienen uno o más trozos de diferencias; cada trozo muestra un área donde los archivos difieren. Los trozos de formato unificado se ven así:

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

Las líneas comunes a ambos archivos comienzan con un carácter de espacio. Las líneas que realmente difieren entre los dos archivos tienen uno de los siguientes caracteres indicadores en la columna de impresión izquierda:

`+ 'Aquí se agregó una línea al primer archivo.

`- 'Aquí se eliminó una línea del primer archivo.

stefanB
fuente
1
Tenga en cuenta que git no imprime la parte 'XXX-FILE-MODIFICATION-TIME', ya que no tiene sentido para el sistema de control de versiones. Para comparar archivos en el sistema de archivos, las marcas de tiempo pueden funcionar como control de versión "pobre".
Jakub Narębski
3

No está claro a partir de su pregunta qué parte de los diferenciales encuentra confusa: la diferencia real o la información adicional del encabezado que imprime git. Por si acaso, aquí hay una descripción rápida del encabezado.

La primera línea es algo así como diff --git a/path/to/file b/path/to/file, obviamente, solo te dice para qué archivo es esta sección del diff. Si establece la variable de configuración booleana diff.mnemonic prefix, el ay bse cambiará a letras más descriptivas como cy w(commit y work tree).

A continuación, hay "líneas de modo": líneas que proporcionan una descripción de cualquier cambio que no implique cambiar el contenido del archivo. Esto incluye archivos nuevos / eliminados, archivos renombrados / copiados y cambios de permisos.

Finalmente, hay una línea como index 789bd4..0afb621 100644. Probablemente nunca te importe, pero esos números hexadecimales de 6 dígitos son los hashes SHA1 abreviados de los blobs viejos y nuevos para este archivo (un blob es un objeto git que almacena datos sin procesar como el contenido de un archivo). Y, por supuesto, este 100644es el modo del archivo: los últimos tres dígitos son obviamente permisos; los primeros tres dan información adicional de metadatos del archivo ( publicación SO que describe eso ).

Después de eso, pasará a la salida diferencial unificada estándar (al igual que la clásica diff -U). Se divide en trozos: un trozo es una sección del archivo que contiene los cambios y su contexto. Cada trozo está precedido por un par de ---y +++líneas que denotan el archivo en cuestión, luego la diferencia real es (por defecto) tres líneas de contexto a cada lado de las líneas -y +que muestran las líneas eliminadas / agregadas.

Cascabel
fuente
++ para la indexlínea. Confirmado congit hash-object ./file
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功