Как с помощью git diff добавлять и изменять номера строк?

80

Предполагая, что у меня есть текстовый файл

alex
bob
matrix
will be removed
git repo

и я обновил его до

alex
new line here
another new line
bob
matrix
git

Здесь я добавил номер строки (2,3) и обновил номер строки (6).

Как я могу получить информацию об этих номерах строк с помощью git diff или любой другой команды git?

Махмуд Халед
источник

Ответы:

78

git diff --stat покажет вам результат, который вы получите при фиксации материала, о котором, я думаю, вы говорите.

git diff --stat

Для точного отображения номеров строк, которые были изменены, вы можете использовать

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

И измененная строка будет последним числом перед закрывающей круглой скобкой в ​​результате. Не самое чистое решение :(

Седрик
источник
3
stat отображает только количество вставленных / удаленных / обновленных строк. Но мне нужно знать, какие номера строк
Махмуд Халед
Это казалось более сложной проблемой, чем должно быть, но мне удалось ее решить, используя git blame и grep. См. Мой обновленный ответ
Sedrik
1
Обычно следует вызывать «git blame -p», если вывод должен обрабатываться другими программами, такими как «awk» или «grep».
Mikko Rantalainen
8
git blame не поймает удаленные строки
Виталий
2
Почему это помечено как правильное, если оно не выполняет то, о чем просил OP?
Shardj
27

Вот функция bash для вычисления результирующих номеров строк из сравнения:

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
}

Он может производить такие выходные данные, как:

$ 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);

из такого различия:

$ 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);

Если вы хотите отображать только добавленные / удаленные / измененные строки, а не окружающий контекст, вы можете перейти -U0к 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);

Он устойчив к цветовым кодам ANSI, поэтому вы можете перейти --color=alwaysк git diff, чтобы получить обычную цветовую кодировку для добавленных / удаленных строк.

Вывод можно легко найти:

$ 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);

В вашем случае git diff -U0даст:

$ 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

Если вам просто нужны номера строк, измените значение echo "$path:$line:$REPLY"на just echo "$line"и направьте вывод через конвейер uniq.

Джон Меллор
источник
Как я мог передать цветные escape-коды bash? Это здорово, но цветовая кодировка git diff --colorне проходит. Или вы думаете, что было бы лучше просто добавить экранирование цвета в возврат из этой функции?
New Alexandria
2
Я обновил функцию, чтобы различные регулярные выражения были устойчивы к цветовым кодам ANSI. git diff --color | diff-linesтеперь работает как положено :)
Джон Меллор
1
Это решение отлично работает! он должен быть отмечен как ответ, поскольку он действительно делает то, что спросил OP. Если это сработало для вас, пожалуйста, проголосуйте за него, чтобы мы могли сделать его популярным :)
markdrake
Я продолжаю получать эту ошибку, используя zsh: zsh: parse error near `]+m'Любые идеи? Ошибка происходит из этой строки:elif [[ $REPLY =~ ^($esc\[[0-9;]+m)*([\ +-]) ]]; then
Хош Садик
@HoshSadiq Простое цитирование регулярного выражения, похоже, сработало.
Koobz
20

Пользуюсь --unified=0опцией git diff.

Например, git diff --unified=0 commit1 commit2вывод diff:

* введите описание изображения здесь *

Из-за этой --unified=0опции вывод diff показывает 0 строк контекста; другими словами, он показывает в точности измененные строки .

Теперь вы можете определить строки, начинающиеся с '@@', и проанализировать их на основе шаблона:

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

Возвращаясь к приведенному выше примеру, для файла WildcardBinding.java, начиная со строки 910, удаляются 0 строк. Начиная со строки 911, добавляются 4 строки.

Ида
источник
1
что, если @@ -910,10,+911,15@@ или что-то в этом роде, тогда как мы можем точно сказать, сколько строк добавлено, удалено или изменено
Касун Сиямбалапития
1
У вас есть хороший способ вывести номера строк в список, подобный запросу OP?
Shardj 08
7

У меня была такая же проблема, поэтому я написал сценарий gawk, который изменяет вывод git diff, добавляя номер строки для каждой строки. Иногда я нахожу это полезным, когда мне нужно различать рабочее дерево, хотя это не ограничивается этим. Может здесь кому-нибудь пригодится?

$ 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

Вы можете скачать его здесь:
https://github.com/jay/showlinenum

Джей
источник
Смотрится очень удобно. Имейте в виду, что этот код имеет преимущество (или недостаток) в том, что он лицензирован GPL.
BlackVegetable
Я тоже написалgit diffn для этого, и он полностью сохраняет цвета терминала и показывает номера строк как старого файла слева, так и нового файла справа.
Габриэль Стейплс,
4

Номера строк всех незафиксированных строк (добавлены / изменены):

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

Пример вывода:

1
2
8
12
13
14
Богатый
источник
как насчет содержимого строк, которые также были изменены?
anon58192932
2

Настройте внешний инструмент сравнения, который покажет вам номера строк. Например, это то, что у меня есть в моей глобальной конфигурации 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"

Подробнее см. Этот ответ: https://stackoverflow.com/q/949242/526535

Manojlds
источник
нет другого способа получить эту информацию без использования инструмента сравнения. Только с помощью команд git?
Махмуд Халед
1

Вот функция bash, которую я сколотил:

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

В итоге это выглядит так:

views.py:
403,404,533-538,546-548,550-552,554-559,565-567,580-582
Forivall
источник
1

Вероятно, это довольно точное количество измененных строк:

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

Кроме того, вот решение для номеров строк в вашем diff: https://github.com/jay/showlinenum

Джей Кэрролл
источник
0

Не совсем то, о чем вы просили, но git blame TEXTFILEможет помочь.

u-punkt
источник
0

Вы можете использовать параметр git diffвместе с shortstatпараметром, чтобы просто показать количество измененных строк.

Если количество строк не изменилось (в файле, который уже находится в репо) с момента вашей последней фиксации

git diff HEAD --shortstat

Будет выведено что-то похожее на

1 file changed, 4 insertions(+)
Шахалпк
источник
В вопросе запрашиваются номера строк для каждой измененной строки, а не общее количество строк, которые были изменены.
Pro Q
0

Я искал способ вывести только строки, измененные для каждого файла, с помощью git diff. Моя идея заключалась в том, чтобы передать этот вывод в линтер для проверки типов. Вот что мне помогло

Максимус
источник
0

Вот несколько копий Python, чтобы получить номера строк для измененных / удаленных строк, если вы столкнулись с этим вопросом в поисках этого.

Его должно быть довольно легко изменить во что-то, что также будет иметь измененные и добавленные номера строк.

Я тестировал только Windows, но он также должен быть кросс-платформенным.

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")
Pro Q
источник