Usando git diff, ¿cómo puedo agregar y modificar números de líneas?


80

Suponiendo que tengo un archivo de texto

alex
bob
matrix
will be removed
git repo

y lo he actualizado para que sea

alex
new line here
another new line
bob
matrix
git

Aquí, agregué el número de línea (2,3) y el número de línea actualizado (6)

¿Cómo puedo obtener la información de estos números de línea usando git diff o cualquier otro comando git?

Respuestas:


78

git diff --stat le mostrará el resultado que obtiene cuando comete cosas, que es a la que se refiere, supongo.

git diff --stat

Para mostrar exactamente los números de línea que se han cambiado, puede usar

git blame -p <file> | grep "Not Committed Yet"

Y la línea cambiada será el último número antes del paréntesis final en el resultado. Sin embargo, no es una solución limpia :(


3
stat solo muestra cuántas líneas se insertan / eliminan / actualizan. Pero necesito saber qué números de línea
Mahmoud Khaled

Este parecía ser un problema más difícil de lo que debería ser, pero me las arreglé para conseguirlo usando git blame y grep. Vea mi respuesta actualizada
Sedrik

1
Normalmente se debe llamar a 'git blame -p' si la salida va a ser procesada por otros programas como 'awk' o 'grep'.
Mikko Rantalainen

8
git blame no captará las líneas eliminadas
Vitali

2
¿Por qué está marcado como correcto cuando no hace lo que OP pidió?
Shardj

27

Aquí hay una función bash para calcular los números de línea resultantes de una diferencia:

diff-lines() {
    local path=
    local line=
    while read; do
        esc=$'\033'
        if [[ $REPLY =~ ---\ (a/)?.* ]]; then
            continue
        elif [[ $REPLY =~ \+\+\+\ (b/)?([^[:blank:]$esc]+).* ]]; then
            path=${BASH_REMATCH[2]}
        elif [[ $REPLY =~ @@\ -[0-9]+(,[0-9]+)?\ \+([0-9]+)(,[0-9]+)?\ @@.* ]]; then
            line=${BASH_REMATCH[2]}
        elif [[ $REPLY =~ ^($esc\[[0-9;]+m)*([\ +-]) ]]; then
            echo "$path:$line:$REPLY"
            if [[ ${BASH_REMATCH[2]} != - ]]; then
                ((line++))
            fi
        fi
    done
}

Puede producir resultados como:

$ git diff | diff-lines
http-fetch.c:1: #include "cache.h"
http-fetch.c:2: #include "walker.h"
http-fetch.c:3: 
http-fetch.c:4:-int cmd_http_fetch(int argc, const char **argv, const char *prefix)
http-fetch.c:4:+int main(int argc, const char **argv)
http-fetch.c:5: {
http-fetch.c:6:+       const char *prefix;
http-fetch.c:7:        struct walker *walker;
http-fetch.c:8:        int commits_on_stdin = 0;
http-fetch.c:9:        int commits;
http-fetch.c:19:        int get_verbosely = 0;
http-fetch.c:20:        int get_recover = 0;
http-fetch.c:21: 
http-fetch.c:22:+       prefix = setup_git_directory();
http-fetch.c:23:+
http-fetch.c:24:        git_config(git_default_config, NULL);
http-fetch.c:25: 
http-fetch.c:26:        while (arg < argc && argv[arg][0] == '-') {
fetch.h:1: #include "config.h"
fetch.h:2: #include "http.h"
fetch.h:3: 
fetch.h:4:-int cmd_http_fetch(int argc, const char **argv, const char *prefix);
fetch.h:4:+int main(int argc, const char **argv);
fetch.h:5: 
fetch.h:6: void start_fetch(const char* uri);
fetch.h:7: bool fetch_succeeded(int status_code);

de una diferencia como esta:

$ git diff
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] == '-') {
diff --git a/fetch.h b/fetch.h
index 5fd3e65..d43e0ca 100644
--- a/fetch.h
+++ b/fetch.h
@@ -1,7 +1,7 @@
 #include "config.h"
 #include "http.h"

-int cmd_http_fetch(int argc, const char **argv, const char *prefix);
+int main(int argc, const char **argv);

 void start_fetch(const char* uri);
 bool fetch_succeeded(int status_code);

Si solo desea mostrar líneas agregadas / eliminadas / modificadas, y no el contexto circundante, puede pasar -U0a git diff:

$ git diff -U0 | diff-lines
http-fetch.c:4:-int cmd_http_fetch(int argc, const char **argv, const char *prefix)
http-fetch.c:4:+int main(int argc, const char **argv)
http-fetch.c:6:+       const char *prefix;
http-fetch.c:22:+       prefix = setup_git_directory();
http-fetch.c:23:+
fetch.h:4:-int cmd_http_fetch(int argc, const char **argv, const char *prefix);
fetch.h:4:+int main(int argc, const char **argv);

Es robusto contra los códigos de color ANSI, por lo que puede pasar --color=alwaysa git diff para obtener la codificación de color habitual para las líneas agregadas / eliminadas.

La salida se puede grep fácilmente:

$ git diff -U0 | diff-lines | grep 'main'
http-fetch.c:4:+int main(int argc, const char **argv)
fetch.h:4:+int main(int argc, const char **argv);

En tu caso git diff -U0daría:

$ git diff -U0 | diff-lines
test.txt:2:+new line here
test.txt:3:+another new line
test.txt:6:-will be removed
test.txt:6:-git repo
test.txt:6:+git

Si solo desea los números de línea, cambie echo "$path:$line:$REPLY"a just echo "$line"y canalice la salida uniq.


¿Cómo podría pasar los códigos de escape de color de bash? Esto es genial, pero los códigos de color que provienen git diff --colorno se transmiten. ¿O cree que sería mejor simplemente agregar los escapes de color al retorno de esta función?
Nueva Alejandría

2
Actualicé la función para que las diversas expresiones regulares sean robustas a los códigos de color ANSI. git diff --color | diff-linesahora funciona como se esperaba :)
John Mellor

1
¡Esta solución funciona increíble! debe marcarse como la respuesta, ya que realmente hace lo que pidió el OP. Si funcionó para usted por favor a votación, por lo que puede hacer que sea la respuesta populares :)
markdrake

Sigo recibiendo este error con zsh: ¿ zsh: parse error near `]+m'Alguna idea? El error proviene de esta línea:elif [[ $REPLY =~ ^($esc\[[0-9;]+m)*([\ +-]) ]]; then
Hosh Sadiq

@HoshSadiq Parece que ha funcionado simplemente citar la expresión regular.
Koobz

20

Utilizo la --unified=0opción de git diff.

Por ejemplo, git diff --unified=0 commit1 commit2genera el diff:

* ingrese la descripción de la imagen aquí *

Debido a la --unified=0opción, la salida diff muestra 0 líneas de contexto; en otras palabras, muestra exactamente las líneas cambiadas .

Ahora, puede identificar las líneas que comienzan con '@@' y analizarlas según el patrón:

@@ -startline1,count1 +startline2,count2 @@

Volviendo al ejemplo anterior, para el archivo WildcardBinding.java, comience desde la línea 910, se eliminan 0 líneas. Comience desde la línea 911, se agregan 4 líneas.


1
¿Qué @@ -910,10,+911,15@@
pasa

1
¿Tiene una buena manera de generar los números de línea en una lista como la que pidió OP?
Shardj

7

Tuve este mismo problema, así que escribí un script gawk que cambia la salida de git diff para anteponer el número de línea para cada línea. A veces lo encuentro útil cuando necesito diferenciar el árbol de trabajo, aunque no se limita a eso. ¿Quizás sea útil para alguien aquí?

$ git diff HEAD~1 |showlinenum.awk
diff --git a/doc.txt b/doc.txt
index fae6176..6ca8c26 100644
--- a/doc.txt
+++ b/doc.txt
@@ -1,3 +1,3 @@
1: red
2: blue
 :-green
3:+yellow

Puede descargarlo desde aquí:
https://github.com/jay/showlinenum


Parece muy útil. Tenga en cuenta que este código tiene la ventaja (o desventaja) de tener una licencia GPL.
BlackVegetable

Escribígit diffn para hacer esto también, y retiene completamente los colores de la terminal y muestra los números de línea tanto del archivo antiguo a la izquierda como del nuevo archivo a la derecha.
Gabriel Staples

3

Números de línea de todas las líneas no comprometidas (agregadas / modificadas):

git blame <file> | grep -n '^0\{8\} ' | cut -f1 -d:

Salida de ejemplo:

1
2
8
12
13
14

¿Qué pasa con el contenido de las líneas que también se cambiaron?
anon58192932

2

Configure una herramienta de diferenciación externa que le mostrará los números de línea. Por ejemplo, esto es lo que tengo en mi configuración global de git:

diff.guitool=kdiff3
difftool.kdiff3.path=c:/Program Files (x86)/KDiff3/kdiff3.exe
difftool.kdiff3.cmd="c:/Program Files (x86)/KDiff3/kdiff3.exe" "$LOCAL" "$REMOTE"

Consulte esta respuesta para obtener más detalles: https://stackoverflow.com/q/949242/526535


¿No hay otra forma de obtener esta información sin usar la herramienta de diferencias? ¿Solo usas comandos de git?
Mahmoud Khaled

1

Aquí hay una función de bash que improvisé:

echo ${f}:
for n in $(git --no-pager blame --line-porcelain $1 |
        awk '/author Not Committed Yet/{if (a && a !~ /author Not Committed Yet/) print a} {a=$0}' |
        awk '{print $3}') ; do
    if (( prev_line > -1 )) ; then
        if (( "$n" > (prev_line + 1) )) ; then
            if (( (prev_line - range_start) > 1 )) ; then
                echo -n "$range_start-$prev_line,"
            else
                echo -n "$range_start,$prev_line,"
            fi
            range_start=$n
        fi
    else
        range_start=$n
    fi
    prev_line=$n
done
if (( "$range_start" != "$prev_line" )) ; then
    echo "$range_start-$prev_line"
else
    echo "$range_start"
fi

Y termina luciendo así:

views.py:
403,404,533-538,546-548,550-552,554-559,565-567,580-582

1

Este es probablemente un recuento bastante preciso de líneas cambiadas:

git diff --word-diff <commit> |egrep '(?:\[-)|(?:\{\+)' |wc -l

Además, aquí hay una solución para los números de línea en su diff: https://github.com/jay/showlinenum


0

No es exactamente lo que estaba pidiendo, pero git blame TEXTFILEpuede ayudar.


0

Puede usar git diffjunto con el shortstatparámetro para mostrar simplemente el número de líneas cambiadas.

Para el número de líneas cambiado (en un archivo que ya está en el repositorio) desde su última confirmación

git diff HEAD --shortstat

Producirá algo similar a

1 file changed, 4 insertions(+)

La pregunta solicita los números de línea para cada línea que se ha cambiado, no un total de cuántas líneas se han cambiado.
Pro Q

0

Estaba buscando una forma de generar solo las líneas cambiadas para cada archivo usando git diff. Mi idea era enviar esta salida a un linter para la verificación de tipos. Esto es lo que me ayudó


0

Aquí hay algo de copia de Python para obtener los números de línea de las líneas modificadas / eliminadas, en caso de que se encuentre con esta pregunta buscando eso.

Debería ser bastante fácil modificarlo en algo que también obtenga los números de línea modificados y agregados.

Solo lo he probado en Windows, pero también debería ser multiplataforma.

import re
import subprocess

def main(file1: str, file2: str):
    diff = get_git_diff(file1, file2)
    print(edited_lines(diff))

def edited_lines(git_diff: str):
    ans = []
    diff_lines = git_diff.split("\n")
    found_first = False
    # adjust for added lines
    adjust = 0
    # how many lines since the start
    count = 0
    for line in diff_lines:
        if found_first:
            count += 1
            if line.startswith('-'):
                # minus one because count is 1 when we're looking at the start line
                ans.append(start + count - adjust - 1)
                continue

            if line.startswith('+'):
                adjust += 1
                continue

        # get the start line
        match = re.fullmatch(r'@@ \-(\d+),\d+ \+\d+,\d+ @@', line)
        if match:
            start = int(match.group(1))
            count = 0
            adjust = 0
            found_first = True

    return ans


def get_git_diff(file1: str, file2: str):
    try:
        diff_process: subprocess.CompletedProcess = subprocess.run(['git', 'diff', '--no-index', '-u', file1, file2], shell=True, check=True, stdout=subprocess.PIPE)
        ans = diff_process.stdout
    # git may exit with 1 even though it worked
    except subprocess.CalledProcessError as e:
        if e.stdout and e.stderr is None:
            ans = e.stdout
        else:
            raise

    # remove carriage at the end of lines from Windows
    ans = ans.decode()
    ans.replace('\r', '')
    return ans


if __name__ == "__main__":
    main("file1.txt", "file2.txt")
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.