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

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


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  |
+----------------+-----+---------+-------+---------------------+

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

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%

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.


4

git shortlog -sn

Esto mostrará una lista de confirmaciones por autor.


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.


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

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

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


-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

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
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.