Количественно определить количество изменений в git diff? - PullRequest
31 голосов
/ 20 мая 2010

Я использую git для немного необычной цели - он хранит мой текст, когда я пишу беллетристику. (Я знаю, я знаю ... вызывающий.)

Я пытаюсь отслеживать производительность и хочу измерить степень разницы между последующими коммитами. Прокси автора для «работы» - это «написанные слова», по крайней мере, на этапе создания. Я не могу использовать прямой подсчет слов, так как он игнорирует редактирование и сжатие, обе важные части написания. Я думаю, что я хочу отслеживать:

 (words added)+(words removed)

, который будет в два раза (слова изменены), но я в порядке с этим.

Было бы здорово напечатать магическое заклинание и сделать так, чтобы git сообщал эту метрику расстояния для любых двух ревизий. Тем не менее, git diffs - это патчи, которые показывают целые строки, даже если вы только прокручивали один символ в строке; Я не хочу этого, тем более что мои «строки» - это абзацы. В идеале я бы даже мог указать, что я имею в виду под словом (хотя \ W +, вероятно, будет приемлемым).

Есть ли флаг для git-diff для предоставления различий для каждого слова в отдельности? Альтернативно, есть ли решение, использующее стандартные инструменты командной строки для вычисления показателя выше?

Ответы [ 9 ]

11 голосов
/ 20 мая 2010

wdiff выполняет пословное сравнение. Git может быть настроен на использование внешней программы для проведения различий. Исходя из этих двух фактов и этого сообщения в блоге , следующее должно делать примерно то, что вы хотите.

Создайте сценарий, чтобы игнорировать большинство ненужных аргументов, предоставляемых git-diff, и передайте их wdiff. Сохраните следующее как ~/wdiff.py или что-то подобное и сделайте его исполняемым.

#!/usr/bin/python

import sys
import os

os.system('wdiff -s3 "%s" "%s"' % (sys.argv[2], sys.argv[5]))

Скажите git, чтобы использовать его.

git config --global diff.external ~/wdiff.py
git diff filename
9 голосов
/ 05 августа 2015

Опираясь на вход Джеймса и Корнмаркельфа , я добавил арифметическое расширение , и придумал несколько многократно используемых команд псевдонимов для подсчета слов в git diff:

alias gitwa='git diff --word-diff=porcelain origin/master | grep -e "^+[^+]" | wc -w | xargs'
alias gitwd='git diff --word-diff=porcelain origin/master | grep -e "^-[^-]" | wc -w | xargs'
alias gitw='echo $(($(gitwa) - $(gitwd)))'

Выходные данные gitwa и gitwd обрезаны с использованием трюка xargs .

9 голосов
/ 18 августа 2010

git diff --word-diff работает в последней стабильной версии git (на git-scm.com)

Есть несколько опций, которые позволяют вам решить, в каком формате вы хотите его использовать, значение по умолчанию вполне читабельно, но вам может потребоваться --word-diff = фарфор, если вы выводите вывод в сценарий.

8 голосов
/ 28 января 2015

Я нашел способ получить конкретные цифры, опираясь на другие ответы здесь. Результат является приблизительным, но он должен быть достаточно близко, чтобы служить полезным индикатором количества символов, которые были добавлены или удалены. Вот пример с моей текущей веткой по сравнению с origin / master:

$ git diff --word-diff=porcelain origin/master | grep -e '^+[^+]' | wc -m
38741
$ git diff --word-diff=porcelain origin/master | grep -e '^-[^-]' | wc -m
46664

Разница между удаленными символами (46664) и добавленными символами (38741) показывает, что моя текущая ветвь удалила примерно 7923 символов. Эти отдельные добавленные / удаленные значения завышены из-за различий + / - и символов отступов, однако в большинстве случаев разница должна компенсировать значительную часть этой инфляции.

4 голосов
/ 20 мая 2010

Git имеет (долгое время) опцию --color-words для git diff. Это не подсчитывает, но позволяет увидеть различия.

Предложение scompt.com о wdiff тоже хорошо; это довольно легко запихнуть в разные различия (см. git-difftool ). Оттуда вам нужно просто перейти от вывода, который wdiff может дать к результату, который вы действительно хотите.

Есть еще одна захватывающая вещь, которую стоит поделиться с git: что готовит:

* tr/word-diff (2010-04-14) 1 commit
  (merged to 'next' on 2010-05-04 at d191b25)
 + diff: add --word-diff option that generalizes --color-words

Вот коммит , вводящий слово-diff . Предположительно, он скоро перейдет от следующего к мастеру, и тогда git сможет сделать все это внутренне - создав собственный формат слова diff или что-то похожее на wdiff. Если вы решитесь, вы можете собрать git из следующего или просто объединить этот коммит с вашим локальным мастером для сборки.

Благодаря комментарию Якуба: вы можете дополнительно настроить различие в словах, если это необходимо, предоставив слово regex (параметр конфигурации diff. *. WordRegex), задокументированное в gitattributes .

2 голосов
/ 13 августа 2016

Мне понравился Stoutie 's answer и я хотел сделать его более настраиваемым, чтобы отвечать на некоторые мои вопросы о количестве слов. Закончилось следующим решением, которое работает в ZSH и должно работать в Bash. Каждая функция принимает любую ревизию или разницу ревизий , по умолчанию сравнивая текущее состояние мира с origin/master:


# Calculate writing word diff between revisions. Cribbed / modified from:
# /2076858/kolichestvenno-opredelit-kolichestvo-izmenenii-v-git-diff
function git_words_added {
  revision=${1:-origin/master}

  git diff --word-diff=porcelain $revision | \
    grep -e "^+[^+]" | \
    wc -w | \
    xargs
}

function git_words_removed {
  revision=${1:-origin/master}

  git diff --word-diff=porcelain $revision | \
    grep -e "^-[^-]" | \
    wc -w | \
    xargs
}

function git_words_diff {
  revision=${1:-origin/master}

  echo $(($(git_words_added $1) - $(git_words_removed $1)))
}

Тогда вы можете использовать его так:


$ git_words_added
# => how many words were added since origin/master

$ git_words_removed
# => how many words were removed since origin/master

$ git_words_diff
# => difference of adds and removes since origin/master (net words)

$ git_words_diff HEAD
# => net words since you last committed

$ git_words_diff master@{yesterday}
# => net words written today!

$ git_words_diff HEAD^..HEAD
# => net words in the last commit

$ git_words_diff ABC123..DEF456
# => net words between two arbitrary commits

Надеюсь, это кому-нибудь поможет!

1 голос
/ 11 декабря 2018

Извините, у меня недостаточно очков репутации, чтобы прокомментировать ответ @ codebeard. Это тот, который я использовал, и я добавил обе его версии в мой файл .gitconfig. Они дали разные ответы, и я проследил проблему до wdiff -sd во второй версии (та, которая объединяет все измененные файлы вместе), посчитав слова в двух строках вверху вывода diff -pdrU3. Это будет что-то вроде:

--- 1   2018-12-10 22:53:47.838902415 -0800
+++ 2   2018-12-10 22:53:57.674835179 -0800

Я исправил это, пропустив tail -n +4.

Вот мои полные настройки .gitconfig с исправлением:

[alias]
    wdiff = diff
    wdiffs = difftool -t wdiffs
    wdiffs-all = difftool -d -t wdiffs-all
[difftool "wdiffs"]
    cmd = wdiff -n -s \"$LOCAL\" \"$REMOTE\" | colordiff
[difftool "wdiffs-all"]
    cmd = diff -pdrU3 \"$LOCAL\" \"$REMOTE\" | tail -n +4 | wdiff -sd

Если вы предпочитаете использовать git config, вот команды:

git config --global difftool.wdiffs.cmd 'wdiff -n -s "$LOCAL" "$REMOTE"' | colordiff
git config --global alias.wdiffs 'difftool -t wdiffs'
git config --global difftool.wdiffs-all.cmd 'diff -pdrU3 "$LOCAL" "$REMOTE" | wdiff -sd'
git config --global alias.wdiffs-all 'difftool -d -t wdiffs-all'

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

Чтобы сравнить с оригиналом / мастером, выполните git wdiffs origin/master или git wdiffs-all origin/master.

Мне нравится этот ответ лучше всего, потому что он дает и количество слов, и разницу, и если вы передадите colordiff, он получится красивым и цветным. (Ответ @Miles также хорош, но требует, чтобы вы выяснили, какое время использовать. Однако мне нравится идея поиска перемещенного текста.)

Вывод статистики wdiff в конце выглядит следующим образом:

file1.txt: 12360 words  12360 100% common  0 0% deleted  5 0% changed
file2.txt: 12544 words  12360 99% common  184 1% inserted  11 0% changed

Чтобы узнать, сколько слов вы добавили, добавьте inserted и changed из второй строки, 184 + 11, в приведенном выше примере.

Почему не что-нибудь из первой строки? Ответ: это слова удалены.

Вот скрипт bash для получения единого счетчика слов:

wdiffoutput=$(git wdiffs-all | tail -n 1)
wdiffins=$(echo "$wdiffoutput" | grep -oP "common *\K\d*")
wdiffchg=$(echo "$wdiffoutput" | grep -oP "inserted *\K\d*")
echo "Word Count: $((wdiffins+wdiffchg))"
0 голосов
/ 18 июня 2017

Приведенные выше ответы не подходят для некоторых случаев, когда вам нужно исключить перемещенный текст (например, если я перемещу функцию в коде или абзаце в латексе дальше по документу, я не хочу считать все эти изменения изменениями!) )

Для этого вы также можете рассчитать количество повторяющихся строк и исключить их из вашего запроса, если дубликатов слишком много.

Например, опираясь на другие ответы, я могу сделать:

git diff $sha~1..$sha|grep -e"^+[^+]" -e"^-[^-]"|sed -e's/.//'|sort|uniq -d|wc -w|xargs

вычисляет количество повторяющихся слов в diff, где sha - ваш коммит.

Вы можете сделать это для всех коммитов за последний день (с 6 утра):

for sha in $(git rev-list --since="6am" master | sed -e '$ d'); do
     echo $(git diff --word-diff=porcelain $sha~1..$sha|grep -e"^+[^+]"|wc -w|xargs),\
     $(git diff --word-diff=porcelain $sha~1..$sha|grep -e"^-[^-]"|wc -w|xargs),\
     $(git diff $sha~1..$sha|grep -e"^+[^+]" -e"^-[^-]"|sed -e's/.//'|sort|uniq -d|wc -w|xargs)
done

Печать: добавлено, удалено, дубликаты

(Я использую строку diff для дубликатов, поскольку она исключает время, когда git diff пытается быть слишком умным, и предполагает, что вы на самом деле только что изменили текст, а не переместили его. Это также исключает случаи, когда учитывается одно слово как дубликат.)

Или, если вы хотите быть изощренным, вы можете полностью исключить коммиты, если дублирование превышает 80%, и суммировать остальные:

total=0
for sha in $(git rev-list --since="6am" master | sed -e '$ d'); do
    added=$(git diff --word-diff=porcelain $sha~1..$sha|grep -e"^+[^+]"|wc -w|xargs)
    deleted=$(git diff --word-diff=porcelain $sha~1..$sha|grep -e"^-[^-]"|wc -w|xargs)
    duplicated=$(git diff $sha~1..$sha|grep -e"^+[^+]" -e"^-[^-]"|sed -e's/.//'|sort|uniq -d|wc -w|xargs)
    if [ "$added" -eq "0" ]; then
        changed=$deleted
        total=$((total+deleted))
        echo "added:" $added, "deleted:" $deleted, "duplicated:"\
             $duplicated, "changed:" $changed
    elif [ "$(echo "$duplicated/$added > 0.8" | bc -l)" -eq "1" ]; then
        echo "added:" $added, "deleted:" $deleted, "duplicated:"\
             $duplicated, "changes counted:" 0
    else
        changed=$((added+deleted))
        total=$((total+changed))
        echo "added:" $added, "deleted:" $deleted, "duplicated:"\
             $duplicated, "changes counted:" $changed
    fi
done
echo "Total changed:" $total

У меня есть этот скрипт, чтобы сделать это здесь: https://github.com/MilesCranmer/git-stats.

Это распечатывает:

➜  bifrost_paper git:(master) ✗ count_changed_words "6am" 

added: 38, deleted: 76, duplicated: 3, changes counted: 114
added: 14, deleted: 19, duplicated: 0, changes counted: 33
added: 1113, deleted: 1112, duplicated: 1106, changes counted: 0
added: 1265, deleted: 1275, duplicated: 1225, changes counted: 0
added: 4207, deleted: 4208, duplicated: 4391, changes counted: 0
Total changed: 147

Коммиты, в которых я просто перемещаюсь, очевидны, поэтому я не считаю эти изменения. Он подсчитывает все остальное и сообщает мне общее количество измененных слов.

0 голосов
/ 28 ноября 2016

Начиная с Git 1.6.3, существует также git difftool, который можно настроить для запуска практически любого внешнего инструмента сравнения. Это намного проще, чем некоторые решения, которые требуют создания сценариев и т. Д. Если вам нравится вывод wdiff -s, вы можете настроить что-то вроде:

git config --global difftool.wdiffs.cmd 'wdiff -s "$LOCAL" "$REMOTE"'
git config --global alias.wdiffs 'difftool -t wdiffs'

Теперь вы можете просто запустить git difftool -t wdiffs или его псевдоним git wdiffs.

Если вы предпочитаете собирать статистику для всех измененных файлов вместе, вместо этого сделайте что-то вроде:

git config --global difftool.wdiffs.cmd 'diff -pdrU3 "$LOCAL" "$REMOTE" | wdiff -sd'
git config --global alias.wdiffs 'difftool -d -t wdiffs'

Это берет вывод типичного унифицированного diff и передает его в wdiff с его опцией -d, установленной только для интерпретации ввода. Напротив, дополнительный аргумент -d для difftool в псевдониме указывает git скопировать все измененные файлы во временный каталог перед выполнением diff.

...