Git Blame Commit Statistics

198

¿Cómo puedo "abusar" de la culpa (o alguna función más adecuada, y / o junto con los comandos de shell) para darme una estadística de cuántas líneas (de código) hay actualmente en el repositorio que se originan en cada committer?

Salida de ejemplo:

Committer 1: 8046 Lines
Committer 2: 4378 Lines
Erik Aigner
fuente
11
Realmente debería haber un comando incorporado para eso ... hay comandos para casos de uso mucho menos comunes.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
@CiroSantilli pero es fácil agregar un shellscript invocable desde git.
Alex
posible duplicado de ¿Cómo contar las líneas totales modificadas por un autor específico en un repositorio de Git? porque puede reducirse fácilmente a ese: simplemente pase sobre todos los autores
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
esto es bastante impresionante code.google.com/p/gitinspector, especialmente si está calificando tareas por equipos de estudiantes (los proyectos grandes no necesitan aplicarse ... es lento porque culpa a cada archivo individual)
sehe

Respuestas:

166

Actualizar

git ls-tree -r -z --name-only HEAD -- */*.c | xargs -0 -n1 git blame \
--line-porcelain HEAD |grep  "^author "|sort|uniq -c|sort -nr

He actualizado algunas cosas en el camino.

Por conveniencia, también puede poner esto en su propio comando:

#!/bin/bash

# save as i.e.: git-authors and set the executable flag
git ls-tree -r -z --name-only HEAD -- $1 | xargs -0 -n1 git blame \
 --line-porcelain HEAD |grep  "^author "|sort|uniq -c|sort -nr

almacena esto en algún lugar de tu camino o modifica tu camino y úsalo como

  • git authors '*/*.c' # look for all files recursively ending in .c
  • git authors '*/*.[ch]' # look for all files recursively ending in .c or .h
  • git authors 'Makefile' # just count lines of authors in the Makefile

Respuesta original

Si bien la respuesta aceptada hace el trabajo, es muy lenta.

$ git ls-tree --name-only -z -r HEAD|egrep -z -Z -E '\.(cc|h|cpp|hpp|c|txt)$' \
  |xargs -0 -n1 git blame --line-porcelain|grep "^author "|sort|uniq -c|sort -nr

Es casi instantáneo.

Para obtener una lista de los archivos actualmente rastreados, puede usar

git ls-tree --name-only -r HEAD

Esta solución evita llamar filepara determinar el tipo de archivo y utiliza grep para que coincida con la extensión deseada por razones de rendimiento. Si se deben incluir todos los archivos, simplemente elimine esto de la línea.

grep -E '\.(cc|h|cpp|hpp|c)$' # for C/C++ files
grep -E '\.py$'               # for Python files

si los archivos pueden contener espacios, que son malos para los shells, puede usar:

git ls-tree -z --name-only -r HEAD | egrep -Z -z '\.py'|xargs -0 ... # passes newlines as '\0'

Dé una lista de archivos (a través de una tubería), uno puede usar xargs para llamar a un comando y distribuir los argumentos. Los comandos que permiten que se procesen varios archivos, obmiten el -n1. En este caso llamamos git blame --line-porcelainy para cada llamada usamos exactamente 1 argumento.

xargs -n1 git blame --line-porcelain

Luego filtramos la salida para las ocurrencias de "autor", ordenamos la lista y contamos las líneas duplicadas por:

grep "^author "|sort|uniq -c|sort -nr

Nota

Otras respuestas en realidad filtran líneas que contienen solo espacios en blanco.

grep -Pzo "author [^\n]*\n([^\n]*\n){10}[\w]*[^\w]"|grep "author "

El comando anterior imprimirá autores de líneas que contengan al menos un carácter que no sea un espacio en blanco. También puede usar la coincidencia, \w*[^\w#]que también excluirá líneas donde el primer carácter que no sea un espacio en blanco no es un #(comentario en muchos lenguajes de secuencias de comandos).

Alex
fuente
2
@nilbus: no puedes. echo "a\nb\nc"|xargs -n1 cmdse expandirá acmd a; cmd b; cmd d
Alex
2
--line-porcelain ya no parece funcionar (git 1.7.5.4) en su lugar usa --porcelain
isoiphone
44
Usuarios de OSX, intente lo siguiente (todavía no funciona en archivos con líneas nuevas en su nombre):git ls-tree --name-only -r HEAD | grep -E '\.(cc|h|m|hpp|c)$' | xargs -n1 git blame --line-porcelain | grep "^author "|sort|uniq -c|sort -nr
Wayne
3
Si solo quiere que todo esté debajo de la ruta actual, a cualquier profundidad, use "./" como filtro de ruta (donde el respondedor puso " / .c").
Ben Dilts
2
Tal vez use "blame -w" para obtener una mejor propiedad del código cuando el código solo fue reformateado stackoverflow.com/questions/4112410/…
sleeplessnerd
124

Escribí una gema llamada git-fame que podría ser útil.

Instalación y uso:

  1. $ gem install git_fame
  2. $ cd /path/to/gitdir
  3. $ git fame

Salida:

Statistics based on master
Active files: 21
Active lines: 967
Total commits: 109

Note: Files matching MIME type image, binary has been ignored

+----------------+-----+---------+-------+---------------------+
| name           | loc | commits | files | distribution (%)    |
+----------------+-----+---------+-------+---------------------+
| Linus Oleander | 914 | 106     | 21    | 94.5 / 97.2 / 100.0 |
| f1yegor        | 47  | 2       | 7     |  4.9 /  1.8 / 33.3  |
| David Selassie | 6   | 1       | 2     |  0.6 /  0.9 /  9.5  |
+----------------+-----+---------+-------+---------------------+
Linus Oleander
fuente
55
+1 finalmente 1 que funciona y parece que da números razonables, el resto de los de la línea de comandos no funcionan en OSX debido a la incompatibilidad de las utilidades o dan números pequeños en mi repositorio. Esto está en OSX y ruby ​​1.9.3 (brew)
Karthik T
9
No seas tonto, @tcaswell. No es spam señalar algo útil, incluso si fue usted quien escribió ese algo.
Wayne
55
Respondiendo a mi propia pregunta: git fame --exclude = caminos / a / archivos, caminos / a / otros / archivos
Maciej Swic
2
@ Adam: ¿Todavía tienes problemas con esto? Me funciona muy bien en OS X 10.9.5.
Sam Dutton
2
Por eso ninguna de pases de más de unos pocos compromete a la vez esta joya tiene que hacer su trabajo es astronómico
Erik Aigner
48
git ls-tree -r HEAD|sed -re 's/^.{53}//'|while read filename; do file "$filename"; done|grep -E ': .*text'|sed -r -e 's/: .*//'|while read filename; do git blame -w "$filename"; done|sed -r -e 's/.*\((.*)[0-9]{4}-[0-9]{2}-[0-9]{2} .*/\1/' -e 's/ +$//'|sort|uniq -c

Explicación paso a paso:

Listar todos los archivos bajo control de versiones

git ls-tree -r HEAD|sed -re 's/^.{53}//'

Pode la lista solo a archivos de texto

|while read filename; do file "$filename"; done|grep -E ': .*text'|sed -r -e 's/: .*//'

Culpe a todos los archivos de texto, ignorando los cambios en los espacios en blanco

|while read filename; do git blame -w "$filename"; done

Saca los nombres de los autores

|sed -r -e 's/.*\((.*)[0-9]{4}-[0-9]{2}-[0-9]{2} .*/\1/' -e 's/ +$//'

Ordene la lista de autores y haga que uniq cuente el número de líneas repetidas consecutivamente

|sort|uniq -c

Salida de ejemplo:

   1334 Maneater
   1924 Another guy
  37195 Brian Ruby
   1482 Anna Lambda
nilbus
fuente
1
Parece que tengo una sedversión diferente , la mía no entiende la -rbandera y tiene problemas con la expresión regular (se queja de padres desequilibrados, incluso cuando elimino el excedente ().
Erik Aigner
77
No importa, lo sudo brew install gnu-sedresolvió. ¡Funciona de maravilla!
Erik Aigner
55
O port install gsedpara usuarios de MacPorts.
Gavin Brock
Hice un sudo brew install gnu-sed(que funcionó) pero todavía recibo errores que sed no reconoce -r. :(
Adam Tuttle
1
En OSX después de instalar gsed a través de macports ejecuté este comando para que funcione (reemplazado sed con gsed):git ls-tree -r HEAD|gsed -re 's/^.{53}//'|while read filename; do file "$filename"; done|grep -E ': .*text'|gsed -r -e 's/: .*//'|while read filename; do git blame -w "$filename"; done|gsed -r -e 's/.*\((.*)[0-9]{4}-[0-9]{2}-[0-9]{2} .*/\1/' -e 's/ +$//'|sort|uniq -c
nerdherd
38

git summaryproporcionado por el paquete git-extras es exactamente lo que necesita. Consulte la documentación en git-extras - git-summary :

git summary --line

Da una salida que se ve así:

project  : TestProject
lines    : 13397
authors  :
8927 John Doe            66.6%
4447 Jane Smith          33.2%
  23 Not Committed Yet   0.2%
adius
fuente
1
Agradable, pero no parece admitir un filtro de ruta, o al menos un argumento de subdirectorio. Sería mejor
Spinkus
1
Solución agradable y limpia. La respuesta de @ Alex arrojó recuentos de líneas muy pequeñas por alguna razón. Esto simplemente salió de la caja. Tomó algo así como 30 segundos para ~ 200k líneas repartidas en unos pocos cientos de archivos.
fgblomqvist
6

La solución de Erik fue increíble, pero tuve algunos problemas con los signos diacríticos (a pesar de que mis LC_*variables de entorno se configuraron aparentemente correctamente) y el ruido que se filtraba en las líneas de código que realmente tenían fechas. Mi sed-fu es pobre, así que terminé con este fragmento de frankenstein con rubí, pero me funciona perfectamente en más de 200,000 LOC, y clasifica los resultados:

git ls-tree -r HEAD | gsed -re 's/^.{53}//' | \
while read filename; do file "$filename"; done | \
grep -E ': .*text' | gsed -r -e 's/: .*//' | \
while read filename; do git blame "$filename"; done | \
ruby -ne 'puts $1.strip if $_ =~ /^\w{8} \((.*?)\s*\d{4}-\d{2}-\d{2}/' | \
sort | uniq -c | sort -rg

También tenga gseden cuenta en lugar de sedporque esas son las instalaciones binarias homebrew, dejando el sistema intacto.

gtd
fuente
4

git shortlog -sn

Esto mostrará una lista de confirmaciones por autor.

moinudin
fuente
17
Esto devuelve el número de confirmaciones por autor, no el número de líneas.
v64
Muy útil para determinar los principales contribuyentes a un proyecto / directorio / archivo
Ares
4

Aquí está el fragmento principal de la respuesta de @Alex que realmente realiza la operación de agregar las líneas de culpa. Lo he reducido para operar en un solo archivo en lugar de un conjunto de archivos.

git blame --line-porcelain path/to/file.txt | grep  "^author " | sort | uniq -c | sort -nr

Publico esto aquí porque vuelvo a esta respuesta a menudo y releo la publicación y vuelvo a digerir los ejemplos para extraer la parte que valoro que está gravando. Tampoco es lo suficientemente genérico para mi caso de uso; su alcance es para un proyecto completo de C.


Me gusta enumerar las estadísticas por archivo, logrado a través de un foriterador bash en lugar de xargsporque encuentro que xargs es menos legible y difícil de usar / memorizar. Las ventajas / desventajas de xargs vs para deberían discutirse en otra parte.

Aquí hay un fragmento práctico que mostrará resultados para cada archivo individualmente:

for file in $(git ls-files); do \
    echo $file; \
    git blame --line-porcelain $file \
        | grep  "^author " | sort | uniq -c | sort -nr; \
    echo; \
done

Y probé, ejecutar este stright en un shell bash es ctrl + c seguro, si necesita poner esto dentro de un script bash, es posible que necesite atrapar SIGINT y SIGTERM si desea que el usuario pueda romper su ciclo for.

ThorSummoner
fuente
1
git blame -w -M -C -C --line-porcelain path/to/file.txt | grep -I '^author ' | sort | uniq -ic | sort -nrEncontré un ligero ajuste al git blame aquí que retrata con mayor precisión las estadísticas que estaba buscando. Específicamente, la opción -M y -C -C (esas son dos C's a propósito). -M detecta movimientos dentro del archivo, y -C -C detecta líneas copiadas de otros archivos. Ver documento aquí . Para completar, -w ignora los espacios en blanco.
John Lee
1

Tengo esta solución que cuenta las líneas culpables en todos los archivos de texto (excluyendo los archivos binarios, incluso los versionados):

IFS=$'\n'
for file in $(git ls-files); do
    git blame `git symbolic-ref --short HEAD` --line-porcelain "$file" | \
        grep  "^author " | \
        grep -v "Binary file (standard input) matches" | \
        grep -v "Not Committed Yet" | \
        cut -d " " -f 2-
    done | \
        sort | \
        uniq -c | \
        sort -nr
Gabriel Diego
fuente
1

Esto funciona en cualquier directorio de la estructura fuente del repositorio, en caso de que desee inspeccionar un determinado módulo fuente.

find . -name '*.c' | xargs -n1 git blame --line-porcelain | grep "^author "|sort|uniq -c|sort -nr
Martin G
fuente
0

Adopté la respuesta principal a Powershell:

(git ls-tree -rz --name-only HEAD).Split(0x00) | where {$_ -Match '.*\.py'} |%{git blame -w --line-porcelain HEAD $_} | Select-String -Pattern '^author ' | Group-Object | Select-Object -Property Count, Name | Sort-Object -Property Count -Descending

Es opcional si se ejecuta git blamecon el -winterruptor, lo agregué porque ignora los cambios en los espacios en blanco.

El rendimiento en mi máquina estaba a favor de Powershell (~ 50s vs ~ 65s para el mismo repositorio), aunque la solución Bash se estaba ejecutando bajo WSL2

Matt M.
fuente
-1

Hice mi propio script que es una combinación de @nilbus y @Alex

#!/bin/sh

for f in $(git ls-tree -r  --name-only HEAD --);
do
    j=$(file "$f" | grep -E ': .*text'| sed -r -e 's/: .*//');
    if [ "$f" != "$j" ]; then
        continue;
    fi
    git blame -w --line-porcelain HEAD "$f" | grep  "^author " | sed 's/author //'`enter code here`
done | sort | uniq -c | sort -nr
vossman77
fuente
Para mí, tu cosa enter code hereestaba causando problemas ... ¿funciona esto correctamente?
Menios
-1

Función Bash que se dirige a un único archivo fuente ejecutado en MacOS.

function glac {
    # git_line_author_counts
    git blame -w "$1" |  sed -E "s/.*\((.*) +[0-9]{4}-[0-9]{2}.*/\1/g" | sort | uniq -c | sort -nr
}
jxramos
fuente