2010-05-20 19 views
25

Io uso git per uno scopo un po 'insolito - memorizza il mio testo mentre scrivo fiction. (Lo so, lo so ... geeky.)Quantificare la quantità di cambiamenti in un diff di git?

Sto cercando di tenere traccia della produttività e voglio misurare il grado di differenza tra i commit successivi. Il proxy dello scrittore per "lavoro" è "parole scritte", almeno durante la fase di creazione. Non riesco ad usare il conteggio delle parole in quanto ignora le modifiche e la compressione, entrambe parti vitali della scrittura. Penso di voler tenere traccia:

(words added)+(words removed) 

che conterrà il doppio conteggio (parole modificate), ma io sto bene con quello.

Sarebbe bello digitare alcuni incantesimi magici e fare rapporto Git questa metrica di distanza per eventuali due revisioni. Tuttavia, git diffs sono patch, che mostrano intere righe anche se hai solo girato un carattere sulla linea; Non lo voglio, soprattutto perché le mie 'linee' sono paragrafi. Idealmente, sarei persino in grado di specificare cosa intendo per "parola" (sebbene \ W + sia probabilmente accettabile).

Esiste un flag per git-diff per fornire le differenze su base parola per parola? In alternativa, esiste una soluzione che utilizza gli strumenti standard della riga di comando per calcolare la metrica sopra?

+3

Questo è davvero un uso eccellente - git è un tracker di contenuti. E non è così insolito - dai uno sguardo al sondaggio git dell'anno scorso https://git.wiki.kernel.org/index.php/GitSurvey2009#07._I_use_Git_for_.28check_all_that_apply.29: – Cascabel

risposta

10

wdiff confronto di parole per parola. Git può essere configurato per usare un programma esterno per fare la differenza. Sulla base di questi due fatti e this blog post, il seguente dovrebbe fare all'incirca quello che vuoi.

Creare uno script per ignorare la maggior parte degli argomenti non necessari che fornisce git-diff e passarli a wdiff. Salvare quanto segue come ~/wdiff.py o qualcosa di simile e renderlo eseguibile.

#!/usr/bin/python 

import sys 
import os 

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

Dillo git di usarlo.

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

Darò questo a provarlo. –

+0

Questa era una buona risposta per il tempo prima che ci fosse 'git diff --word-diff' – Jarus

+0

@Jarus È ancora la migliore risposta per quanto posso dire:' --word-diff' non mostra parola per parola statistiche. –

4

Git ha avuto (per lungo tempo) un'opzione --color-words per git diff. Questo non ti fa contare, ma ti fa vedere le differenze.

Anche il suggerimento di scompt.com su wdiff è buono; è abbastanza facile spingere in una diversa differenza (vedi git-difftool). Da lì devi solo andare dall'output che wdiff può dare al risultato che vuoi davvero.

C'è una cosa più emozionante da condividere, anche se, da Git di cosa bolle in pentola:

* 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 

Ecco il commit introducing word-diff. Presumibilmente passerà presto dal prossimo al master, e quindi git sarà in grado di fare tutto questo internamente - producendo il proprio formato diff di parole o qualcosa di simile a wdiff. Se sei audace, potresti creare git dal prossimo, o semplicemente unire quel commit al tuo master locale da costruire.

Grazie al commento di Jakub: è possibile personalizzare ulteriormente diff di parole, se necessario, fornendo una parola regex, documentati in gitattributes (parametro di configurazione diff * wordRegex..).

+0

Eccellente. Mi rendo conto che lo avrò presto sul mio server (un Solaris dove ricompro regolarmente Git). +1 – VonC

+1

C'è anche 'diff. variabile di configurazione .wordRegex', per andare insieme a 'diff = ' gitattribute, dove è possibile definire cosa costituisce la parola. –

9

git diff --word-diff funziona nell'ultima versione stabile di git (in git-scm.com)

Ci sono alcune opzioni che ti permettono di decidere in che formato vuoi che sia, l'impostazione predefinita è abbastanza leggibile, ma potresti volere --word-diff = porcelain se stai inserendo l'output in uno script.

+1

Tuttavia, non fornisce statistiche per parola, che è la domanda. –

+0

Ti raggiunge il 99% del tempo. Ad esempio: '' git diff --word-diff = porcelain | grep -e '^ + [^ +] \ |^- [^ -]' '' – ariddell

+0

La mia risposta qui sotto si basa sul commento di ariddell per ottenere una risposta approssimativa: http://stackoverflow.com/a/28183710/204480 –

7

Ho trovato un modo per ottenere numeri concreti costruendo in cima alle altre risposte qui. Il risultato è un'approssimazione, ma dovrebbe essere abbastanza vicino da fungere da utile indicatore della quantità di caratteri aggiunti o rimossi. Ecco un esempio con il mio ramo corrente rispetto al 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 

La differenza tra i caratteri rimossi (46664) ei caratteri aggiunti (38741) mostra che il mio ramo corrente ha rimosso circa 7923 caratteri. I singoli conteggi aggiunti/rimossi vengono gonfiati a causa del diff +/- e dei caratteri di indentazione, tuttavia, la differenza dovrebbe cancellare una parte significativa dell'inflazione nella maggior parte dei casi.

+5

Perché non andare fino in fondo e rispondere alla domanda originale? 'wc -w' potrebbe invece trovare la parola conta. Molto più utile. – cormacrelf

+0

È possibile aggiungere 'sed 's /^.//'' alla pipeline per rimuovere il trailing '+'/'-'. – superbob

7

Sulla James' and cornmacrelf's input, ho aggiunto arithmetic expansion, e si avvicinò con alcuni comandi alias riutilizzabili per il conteggio parole in un diff git:

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)))' 

uscita da gitwa e gitwd è trimmed using xargs trick.

+0

Grazie, @Stoutie, questo funziona bene. Ho cercato di aggiungere più sofisticazione a questo: vedere la mia risposta per la protezione contro il testo che è stato semplicemente spostato in un documento ... – Miles

1

Mi è piaciuto Stoutie 's answer e volevo renderlo un po' più configurabile per rispondere ad alcune domande di conteggio delle parole che avevo. Finito con la seguente soluzione che funziona in ZSH e dovrebbe funzionare in Bash. Ogni funzione prende qualsiasi revision or revision difference, con un valore predefinito di confronto tra lo stato attuale del mondo 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))) 
} 

Quindi è possibile utilizzare in questo modo:


$ 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 

Spero che questo aiuti qualcuno!

+1

Mi ha aiutato :) Bel lavoro. Grazie! – lucapette

0

Poiché Git 1.6.3 è disponibile anche git difftool, che può essere configurato per eseguire quasi tutti gli strumenti di diffusione esterni. Questo è molto più facile che alcune delle soluzioni che richiedono la creazione di script, ecc Se vi piace l'uscita di wdiff -s è possibile configurare qualcosa di simile:

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

Ora si può semplicemente eseguire git difftool -t wdiffs o il suo alias git wdiffs.

Se si preferisce ottenere statistiche per tutti i file modificati insieme, invece fare qualcosa di simile:

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

Questo prende l'uscita di un tipico unificata diff e la trasporta in wdiff con la sua -d opzione impostata per interpretare solo l'input. Al contrario, l'argomento extra -d su difftool nell'alias dice a git di copiare tutti i file modificati in una directory temporanea prima di fare il diff.

0

Le risposte sopra non riescono per alcuni casi di utilizzo in cui è necessario escludere il testo spostato (ad esempio, se sposto una funzione in codice o paragrafo in latex più in basso nel documento, non voglio contare tutti quelli come modifiche !)

Per questo, è anche possibile calcolare il numero di righe duplicate ed escludere quelle dalla query se ci sono troppi duplicati.

Per esempio, sulla base delle altre risposte, non posso fare:

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

calcola il numero di parole duplicate in diff, dove sha è il vostro commit.

Si può fare questo per tutti i commit entro l'ultimo giorno (dal 6 del mattino) da:

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 

Stampe: aggiunte, eliminate, duplica

(prendo il diff linea per i duplicati, poiché esclude i tempi in cui git diff tenta di essere troppo intelligente e presuppone che in realtà hai appena modificato il testo anziché spostato. Sconti anche le istanze in cui una singola parola viene contata come duplicato.

Oppure, se vuoi per essere sofisticato su di esso , È possibile escludere impegna tutto se non v'è più del 80% la duplicazione, e riassumere il 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 

ho questo script per farlo qui: https://github.com/MilesCranmer/git-stats.

Questo stampa:

➜ 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 

La impegna in cui io sono solo muoversi le cose sono evidenti, quindi non contano quei cambiamenti. Conta tutto il resto e mi dice il numero totale di parole cambiate.