2010-05-20 9 views
25

Uso git con un propósito poco usual: almacena mi texto mientras escribo ficción. (Lo sé, lo sé ... geek.)Cuantificando la cantidad de cambio en un git diff?

Estoy tratando de realizar un seguimiento de la productividad, y quiero medir el grado de diferencia entre las confirmaciones posteriores. El proxy del escritor para "trabajo" es "palabras escritas", al menos durante la etapa de creación. No puedo usar el recuento directo de palabras ya que ignora la edición y la compresión, ambas partes vitales de la escritura. Creo que quiero rastrear:

(words added)+(words removed) 

que doblarán (cambiaron las palabras), pero estoy de acuerdo con eso.

Sería genial escribir un hechizo mágico y hacer que git informe esta medida de distancia para dos revisiones. Sin embargo, las diferencias de git son parches, que muestran líneas enteras incluso si solo has movido un carácter en la línea; No quiero eso, especialmente porque mis 'líneas' son párrafos. Idealmente, incluso podría especificar lo que quiero decir con "palabra" (aunque \ W + probablemente sería aceptable).

¿Hay un indicador para git-diff para dar diffs palabra por palabra? Alternativamente, ¿hay alguna solución que use herramientas de línea de comandos estándar para calcular la métrica anterior?

+3

Eso es realmente un excelente uso: git es un rastreador de contenido. Y no es tan inusual: eche un vistazo a la encuesta de git del año pasado https://git.wiki.kernel.org/index.php/GitSurvey2009#07._I_use_Git_for_.28check_all_that_apply.29: – Cascabel

Respuesta

10

wdiff hace la comparación palabra por palabra. Git se puede configurar para usar un programa externo para hacer la diferencia. Con base en esos dos hechos y this blog post, lo siguiente debería hacer más o menos lo que usted desea.

Crea una secuencia de comandos para ignorar la mayoría de los argumentos innecesarios que proporciona git-diff y pasarlos a wdiff. Guarde lo siguiente como ~/wdiff.py o algo similar y hágalo ejecutable.

#!/usr/bin/python 

import sys 
import os 

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

Indica git usarlo.

git config --global diff.external ~/wdiff.py 
git diff filename 
+0

Haré una prueba. –

+0

Esta fue una buena respuesta para el momento antes de que hubiera 'git diff --word-diff' – Jarus

+0

@Jarus Todavía es la mejor respuesta por lo que puedo decir:' --word-diff' no se muestra por palabra estadística. –

4

Git ha tenido (por mucho tiempo) una opción para --color-wordsgit diff. Esto no te hace contar, pero te deja ver los diffs.

sugerencia de scompt.com de wdiff también es bueno; es bastante fácil meterse en una diferencia diferente (ver git-difftool). A partir de ahí, solo tiene que ir desde la salida que wdiff puede dar al resultado que realmente desea.

Hay una cosa más interesante para compartir, sin embargo, a partir de git qué está cocinando:

* 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 

Aquí está la commit introducing word-diff. Presumiblemente, pasará de próximo a maestro en poco tiempo, y luego git podrá hacerlo todo internamente, ya sea produciendo su propio formato de diferencia de palabras o algo similar a wdiff. Si te atreves, podrías construir git a partir de ahora, o simplemente fusionar ese compromiso en tu maestro local para construir.

Gracias al comentario de Jakub: puede personalizar aún más las diferencias de palabra si es necesario proporcionando una palabra regex (config parámetro diff. *. WordRegex), documentada en gitattributes.

+0

Excelente. Solo me doy cuenta de que lo tendré pronto en mi servidor (un Solaris donde recompilaré Git regularmente). +1 – VonC

+1

También hay 'diff. . La variable de configuración de .wordRegex', para ir junto con 'diff = ' gitattribute, donde puede definir qué constituye la palabra. –

9

git diff --word-diff funciona en la última versión estable de git (en git-scm.com)

Hay algunas opciones que le permiten decidir en qué formato lo desea, por defecto es bastante legible, pero es posible que desee --word-diff = porcelain si está alimentando el resultado en un script.

+1

Sin embargo, no proporciona estadísticas por palabra, de lo que se trata la pregunta. –

+0

Te lleva el 99% del camino hasta allí. Por ejemplo: '' git diff --word-diff = porcelana | grep -e '^ + [^ +] \ |^- [^ -]' '' – ariddell

+0

Mi respuesta a continuación se basa en el comentario de ariddell para obtener una respuesta aproximada: http://stackoverflow.com/a/28183710/204480 –

7

Descubrí una manera de obtener números concretos mediante la construcción de otras respuestas aquí. El resultado es una aproximación, pero debe ser lo suficientemente cerca como para servir como un indicador útil de la cantidad de caracteres que se agregaron o eliminaron. Aquí hay un ejemplo con mi rama actual en comparación con origen/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 

La diferencia entre los caracteres eliminados (46664) y los caracteres añadidos (38741) muestra que mi rama actual ha eliminado aproximadamente 7923 caracteres. Esos recuentos añadidos/eliminados individuales están inflados debido a los +/- del diff y los caracteres de sangría, sin embargo, la diferencia debería cancelar una porción significativa de esa inflación en la mayoría de los casos.

+5

¿Por qué no ir hasta el final y responder la pregunta original? 'wc -w' encontraría conteos de palabras en su lugar. Mucho mas util – cormacrelf

+0

Puede agregar 'sed 's /^.//'' a la canalización para eliminar el final '+'/'-'. – superbob

7

Basándose en James' and cornmacrelf's input, he añadido arithmetic expansion, y se acercó con algunos comandos alias reutilizables para el recuento de palabras en un 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)))' 

salida de gitwa y gitwd es trimmed using xargs trick.

+0

Gracias, @Stoutie, esto funciona bien. Traté de agregar más sofisticación a esto: ver mi respuesta para la protección contra el texto que simplemente se movió en un documento ... – Miles

1

Me gustó Stoutie 's answer y quería que fuera un poco más configurable para responder algunas preguntas de conteo de palabras que tenía. Terminó con la siguiente solución que funciona en ZSH y debería funcionar en Bash. Cada función tiene ningún revision or revision difference, con un valor predeterminado de comparar el estado actual del mundo con origin/master:


# Calculate writing word diff between revisions. Cribbed/modified from: 
# https://stackoverflow.com/questions/2874318/quantifying-the-amount-of-change-in-a-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))) 
} 

A continuación, puede usarlo de esta manera:


$ 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 [email protected]{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 

Espero que esto ayude a alguien!

+1

Me ayudó :) Buen trabajo. ¡Gracias! – lucapette

0

Desde Git 1.6.3 también existe git difftool, que se puede configurar para ejecutar casi cualquier herramienta de diferencia externa. Esto es mucho más fácil que algunas de las soluciones que requieren la creación de scripts, etc. Si te gusta la salida de wdiff -s puede configurar algo como:

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

Ahora sólo puede ejecutar git difftool -t wdiffs o su alias git wdiffs.

Si prefiere obtener estadísticas para todos los archivos modificados en conjunto, en lugar de hacer algo como:

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

Esto toma la salida de un típico unificado diff y tuberías en wdiff con su opción -d establecer simplemente interpretar la entrada. Por el contrario, el argumento adicional -d al difftool en el alias le dice a git que copie todos los archivos modificados a un directorio temporal antes de hacer el diff.

0

Las respuestas anteriores no para algunos casos de uso donde se necesita excluir el texto movido (por ejemplo, si muevo una función en el código o párrafo en látex más abajo en el documento, no quiero contar todos los cambios que !)

Para eso, también puede calcular el número de líneas duplicadas y excluir aquellas de su consulta si hay demasiadas duplicaciones.

Por ejemplo, sobre la base de las otras respuestas, que puedo hacer:

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

calcula el número de palabras duplicadas en el diff, donde sha es su confirmación.

Puede hacer esto para todas las confirmaciones en el último día (desde el 6 de la mañana) por:

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 

Prints: añadido, suprimido, duplicados

(Tomo el diff línea de duplicados, ya que excluye los tiempos en los que git diff intenta ser demasiado inteligente, y asume que realmente acabas de cambiar el texto en lugar de moverlo. También descuenta instancias donde una sola palabra se cuenta como un duplicado.)

O, si quieres ser sofisticado al respecto , Puede excluir compromete por completo si hay más de un 80% la duplicación, y que resume el resto:

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 

tengo este script para hacerlo aquí: https://github.com/MilesCranmer/git-stats.

Esto muestra:

➜ 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 

El compromete donde estoy sólo va a desplazarse alrededor de las cosas son evidentes, por lo que no cuentan los cambios. Contar todo lo demás y me dice el número total de palabras cambiadas.