web-dev-qa-db-fra.com

Comment compter le nombre total de lignes modifiées par un auteur spécifique dans un référentiel Git?

Existe-t-il une commande que je peux appeler qui comptera les lignes modifiées par un auteur spécifique dans un référentiel Git? Je sais qu'il doit y avoir des moyens de compter le nombre de commits, comme Github le fait pour son graphique Impact.

408
Gav

Le résultat de la commande suivante devrait être assez facile à envoyer au script pour additionner les totaux:

git log --author="<authorname>" --oneline --shortstat

Cela donne des statistiques pour tous les commits sur le HEAD actuel. Si vous voulez ajouter des statistiques dans d'autres branches, vous devrez les fournir en tant qu'arguments pour git log.

Pour passer à un script, supprimer même le format "oneline" peut être fait avec un format de journal vide, et comme le commente Jakub Narębski, --numstat est une autre alternative. Il génère des statistiques par fichier plutôt que par ligne mais est encore plus facile à analyser.

git log --author="<authorname>" --pretty=tformat: --numstat
283
CB Bailey

Cela donne des statistiques sur l'auteur, modifiez-les au besoin.

Utiliser Gawk:

git log --author="_Your_Name_Here_" --pretty=tformat: --numstat \
| gawk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "added lines: %s removed lines: %s total lines: %s\n", add, subs, loc }' -

Utiliser Awk sur Mac OSX:

git log --author="_Your_Name_Here_" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "added lines: %s, removed lines: %s, total lines: %s\n", add, subs, loc }' -

EDIT (2017)

Il y a un nouveau paquet sur github qui a l'air lisse et utilise bash comme dépendances (testé sur linux). C'est plus approprié pour une utilisation directe que pour des scripts.

C'est git-quick-stats (lien github) .

Copiez git-quick-stats dans un dossier et ajoutez-le au chemin.

mkdir ~/source
cd ~/source
git clone [email protected]:arzzen/git-quick-stats.git
mkdir ~/bin
ln -s ~/source/git-quick-stats/git-quick-stats ~/bin/git-quick-stats
chmod +x ~/bin/git-quick-stats
export PATH=${PATH}:~/bin

Usage:

git-quick-stats

enter image description here

553
Alex

Au cas où quelqu'un voudrait voir les statistiques de chaque utilisateur dans sa base de code, quelques de mes collègues ont récemment proposé ce terrible one-liner:

git log --shortstat --pretty="%cE" | sed 's/\(.*\)@.*/\1/' | grep -v "^$" | awk 'BEGIN { line=""; } !/^ / { if (line=="" || !match(line, $0)) {line = $0 "," line }} /^ / { print line " # " $0; line=""}' | sort | sed -E 's/# //;s/ files? changed,//;s/([0-9]+) ([0-9]+ deletion)/\1 0 insertions\(+\), \2/;s/\(\+\)$/\(\+\), 0 deletions\(-\)/;s/insertions?\(\+\), //;s/ deletions?\(-\)//' | awk 'BEGIN {name=""; files=0; insertions=0; deletions=0;} {if ($1 != name && name != "") { print name ": " files " files changed, " insertions " insertions(+), " deletions " deletions(-), " insertions-deletions " net"; files=0; insertions=0; deletions=0; name=$1; } name=$1; files+=$2; insertions+=$3; deletions+=$4} END {print name ": " files " files changed, " insertions " insertions(+), " deletions " deletions(-), " insertions-deletions " net";}'

(Cela prend quelques minutes pour passer en revue notre rapport, qui compte environ 10-15 000 commits.)

184
Dan

Git renommée https://github.com/oleander/git-fame-rb

est un outil de Nice pour obtenir le nombre pour tous les auteurs à la fois, y compris les comptes de validation et de modification:

Sudo apt-get install Ruby-dev
Sudo gem install git_fame
cd /path/to/gitdir && git fame

Il existe également la version Python sur https://github.com/casperdcl/git-fame (mentionné par @fracz):

Sudo apt-get install python-pip python-dev build-essential 
pip install --user git-fame
cd /path/to/gitdir && git fame

Exemple de sortie:

Total number of files: 2,053
Total number of lines: 63,132
Total number of commits: 4,330

+------------------------+--------+---------+-------+--------------------+
| name                   | loc    | commits | files | percent            |
+------------------------+--------+---------+-------+--------------------+
| Johan Sørensen         | 22,272 | 1,814   | 414   | 35.3 / 41.9 / 20.2 |
| Marius Mathiesen       | 10,387 | 502     | 229   | 16.5 / 11.6 / 11.2 |
| Jesper Josefsson       | 9,689  | 519     | 191   | 15.3 / 12.0 / 9.3  |
| Ole Martin Kristiansen | 6,632  | 24      | 60    | 10.5 / 0.6 / 2.9   |
| Linus Oleander         | 5,769  | 705     | 277   | 9.1 / 16.3 / 13.5  |
| Fabio Akita            | 2,122  | 24      | 60    | 3.4 / 0.6 / 2.9    |
| August Lilleaas        | 1,572  | 123     | 63    | 2.5 / 2.8 / 3.1    |
| David A. Cuadrado      | 731    | 111     | 35    | 1.2 / 2.6 / 1.7    |
| Jonas Ängeslevä        | 705    | 148     | 51    | 1.1 / 3.4 / 2.5    |
| Diego Algorta          | 650    | 6       | 5     | 1.0 / 0.1 / 0.2    |
| Arash Rouhani          | 629    | 95      | 31    | 1.0 / 2.2 / 1.5    |
| Sofia Larsson          | 595    | 70      | 77    | 0.9 / 1.6 / 3.8    |
| Tor Arne Vestbø        | 527    | 51      | 97    | 0.8 / 1.2 / 4.7    |
| spontus                | 339    | 18      | 42    | 0.5 / 0.4 / 2.0    |
| Pontus                 | 225    | 49      | 34    | 0.4 / 1.1 / 1.7    |
+------------------------+--------+---------+-------+--------------------+

Mais soyez averti: comme le mentionne Jared dans le commentaire, le faire sur un très grand référentiel prendra des heures. Pas sûr si cela pourrait être amélioré cependant, étant donné qu'il doit traiter beaucoup de données Git.

J'ai trouvé ce qui suit utile pour savoir qui avait le plus de lignes actuellement dans la base de code:

git ls-files -z | xargs -0n1 git blame -w | Ruby -n -e '$_ =~ /^.*\((.*?)\s[\d]{4}/; puts $1.strip' | sort -f | uniq -c | sort -n

Les autres réponses ont principalement porté sur les lignes modifiées dans les commits, mais si les commits ne survivent pas et sont écrasés, ils ont peut-être simplement été remplacés. L'incantation ci-dessus vous permet également de classer tous les auteurs par lignes au lieu d'un seul à la fois. Vous pouvez ajouter quelques options à git blame (-C -M) pour obtenir de meilleurs nombres prenant en compte le mouvement de fichier et le mouvement de ligne entre fichiers, mais la commande risque d'être beaucoup plus longue si vous le faites.

De plus, si vous recherchez des lignes modifiées dans tous les commits pour tous les auteurs, le petit script suivant est utile:

http://git-wt-commit.rubyforge.org/#git-rank-contributors

102
mmrobins

Pour compter le nombre de commits par un auteur donné (ou tous les auteurs) sur une branche donnée, vous pouvez utiliser git-shortlog ; voir en particulier ses options --numbered et --summary, par ex. lorsqu'il est exécuté sur le référentiel git:

$ git shortlog v1.6.4 --numbered --summary
  6904  Junio C Hamano
  1320  Shawn O. Pearce
  1065  Linus Torvalds
    692  Johannes Schindelin
    443  Eric Wong
88
Jakub Narębski

Après avoir regardé la réponse de Alex et Gerty30, j'ai essayé de raccourcir le one-liner:

Fondamentalement, en utilisant git log numstat et not en gardant une trace du nombre de fichiers modifiés.

Git version 2.1.0 sur Mac OSX:

git log --format='%aN' | sort -u | while read name; do echo -en "$name\t"; git log --author="$name" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "added lines: %s, removed lines: %s, total lines: %s\n", add, subs, loc }' -; done

Exemple:

Jared Burrows   added lines: 6826, removed lines: 2825, total lines: 4001
69
Jared Burrows

Le Answer de AaronM utiliser Shell One-Liner est bon, mais en réalité, il existe un autre bogue, où les espaces corrompent les noms d'utilisateur s'il y a différentes quantités de blanc. espaces entre le nom d'utilisateur et la date. Les noms d'utilisateur corrompus donneront plusieurs lignes pour le nombre d'utilisateurs et vous devrez les résumer vous-même.

Ce petit changement a résolu le problème pour moi:

git ls-files -z | xargs -0n1 git blame -w --show-email | Perl -n -e '/^.*?\((.*?)\s+[\d]{4}/; print $1,"\n"' | sort -f | uniq -c | sort -n

Remarquez le signe + après\s qui absorbera tous les espaces du nom à la date.

En ajoutant cette réponse, autant pour mon souvenir que pour aider quelqu'un d’autre, car c’est au moins la deuxième fois que je recherche le sujet sur Google :)

  • Edit 2019-01-23 Ajouté --show-email à git blame -w pour agréger par courrier électronique à la place, car certaines personnes utilisent différentes Name formats sur différents ordinateurs, et parfois deux personnes portant le même nom travaillent dans le même git.
27
Erik Zivkovic

Voici une courte ligne qui produit des statistiques pour tous les auteurs. C'est beaucoup plus rapide que la solution de Dan ci-dessus à https://stackoverflow.com/a/20414465/1102119 (le mien a une complexité temporelle O(N) au lieu de O(NM) où N est le nombre de commits et M le nombre d’auteurs).

git log --no-merges --pretty=format:%an --numstat | awk '/./ && !author { author = $0; next } author { ins[author] += $1; del[author] += $2 } /^$/ { author = ""; next } END { for (a in ins) { printf "%10d %10d %10d %s\n", ins[a] - del[a], ins[a], del[a], a } }' | sort -rn
23
kccqzy

@mmrobins @AaronM @ErikZ @JamesMishra a fourni des variantes qui ont toutes un problème en commun: elles demandent à git de produire un mélange d'informations non destinées à la consommation de scripts, y compris le contenu des lignes du référentiel situé sur la même ligne, puis associent le désordre à une expression rationnelle. .

C'est un problème lorsque certaines lignes ne sont pas du texte UTF-8 valide et également lorsque certaines lignes correspondent à l'expression rationnelle (c'est ce qui s'est passé ici).

Voici une ligne modifiée qui n'a pas ces problèmes. Il demande à git de sortir les données proprement sur des lignes séparées, ce qui facilite le filtrage de ce que nous voulons:

git ls-files -z | xargs -0n1 git blame -w --line-porcelain | grep -a "^author " | sort -f | uniq -c | sort -n

Vous pouvez grep pour d'autres chaînes, comme auteur-mail, committer, etc.

Peut-être devrions-nous d'abord commencer par export LC_ALL=C (en supposant que bash) pour forcer le traitement au niveau octet (cela accélère également énormément grep à partir des paramètres régionaux basés sur UTF-8).

21

Une solution a été donnée avec Ruby au milieu, Perl étant un peu plus disponible par défaut, voici une alternative utilisant Perl pour les lignes actuelles par auteur.

git ls-files -z | xargs -0n1 git blame -w | Perl -n -e '/^.*\((.*?)\s*[\d]{4}/; print $1,"\n"' | sort -f | uniq -c | sort -n
16
AaronM

En plus de réponse de Charles Bailey , vous pouvez ajouter le paramètre -C aux commandes. Sinon, les renommements de fichiers comptent pour beaucoup d’additions et de suppressions (autant que le fichier contient de lignes), même si le contenu du fichier n’a pas été modifié.

Pour illustrer cela, voici n commit avec de nombreux fichiers déplacés d’un de mes projets lors de l’utilisation de la commande git log --oneline --shortstat:

9052459 Reorganized project structure
 43 files changed, 1049 insertions(+), 1000 deletions(-)

Et voici le même commit en utilisant la commande git log --oneline --shortstat -C qui détecte les copies de fichiers et les renomme:

9052459 Reorganized project structure
 27 files changed, 134 insertions(+), 85 deletions(-)

À mon avis, ce dernier donne une idée plus réaliste de l’impact d’une personne sur le projet, car renommer un fichier est une opération bien moins importante que l’écrire à partir de zéro.

13
Esko Luontola

Voici un script rapide Ruby qui corrige l'impact par utilisateur par rapport à une requête de journal donnée.

Par exemple, pour rubinius :

Brian Ford: 4410668
Evan Phoenix: 1906343
Ryan Davis: 855674
Shane Becker: 242904
Alexander Kellett: 167600
Eric Hodel: 132986
Dirkjan Bussink: 113756
...

le scénario:

#!/usr/bin/env Ruby

impact = Hash.new(0)

IO.popen("git log --pretty=format:\"%an\" --shortstat #{ARGV.join(' ')}") do |f|
  prev_line = ''
  while line = f.gets
    changes = /(\d+) insertions.*(\d+) deletions/.match(line)

    if changes
      impact[prev_line] += changes[1].to_i + changes[2].to_i
    end

    prev_line = line # Names are on a line of their own, just before the stats
  end
end

impact.sort_by { |a,i| -i }.each do |author, impact|
  puts "#{author.strip}: #{impact}"
end
11
Nevir

vous pouvez utiliser whodid ( https://www.npmjs.com/package/whodid )

$ npm install whodid -g
$ cd your-project-dir

et

$ whodid author --include-merge=false --path=./ --valid-threshold=1000 --since=1.week

ou tapez simplement

$ whodid

alors vous pouvez voir le résultat comme ça

Contribution state
=====================================================
 score  | author
-----------------------------------------------------
 3059   | someguy <[email protected]>
 585    | somelady <[email protected]>
 212    | niceguy <[email protected]>
 173    | coolguy <[email protected]>
=====================================================
9
victor.cheval

c’est le meilleur moyen et cela vous donne également une image claire du nombre total de commits de tous les utilisateurs

git shortlog -s -n
8
edrich13

J'ai fourni une modification d'une réponse courte ci-dessus, mais ce n'était pas suffisant pour mes besoins. Je devais être capable de classer les lignes engagées et les lignes dans le code final. Je voulais aussi une ventilation par fichier. Ce code ne recurse pas, il ne renverra que les résultats pour un seul répertoire, mais c'est un bon début si quelqu'un voulait aller plus loin. Copiez et collez dans un fichier et rendez-le exécutable ou exécutez-le avec Perl.

#!/usr/bin/Perl

use strict;
use warnings;
use Data::Dumper;

my $dir = shift;

die "Please provide a directory name to check\n"
    unless $dir;

chdir $dir
    or die "Failed to enter the specified directory '$dir': $!\n";

if ( ! open(GIT_LS,'-|','git ls-files') ) {
    die "Failed to process 'git ls-files': $!\n";
}
my %stats;
while (my $file = <GIT_LS>) {
    chomp $file;
    if ( ! open(GIT_LOG,'-|',"git log --numstat $file") ) {
        die "Failed to process 'git log --numstat $file': $!\n";
    }
    my $author;
    while (my $log_line = <GIT_LOG>) {
        if ( $log_line =~ m{^Author:\s*([^<]*?)\s*<([^>]*)>} ) {
            $author = lc($1);
        }
        elsif ( $log_line =~ m{^(\d+)\s+(\d+)\s+(.*)} ) {
            my $added = $1;
            my $removed = $2;
            my $file = $3;
            $stats{total}{by_author}{$author}{added}        += $added;
            $stats{total}{by_author}{$author}{removed}      += $removed;
            $stats{total}{by_author}{total}{added}          += $added;
            $stats{total}{by_author}{total}{removed}        += $removed;

            $stats{total}{by_file}{$file}{$author}{added}   += $added;
            $stats{total}{by_file}{$file}{$author}{removed} += $removed;
            $stats{total}{by_file}{$file}{total}{added}     += $added;
            $stats{total}{by_file}{$file}{total}{removed}   += $removed;
        }
    }
    close GIT_LOG;

    if ( ! open(GIT_BLAME,'-|',"git blame -w $file") ) {
        die "Failed to process 'git blame -w $file': $!\n";
    }
    while (my $log_line = <GIT_BLAME>) {
        if ( $log_line =~ m{\((.*?)\s+\d{4}} ) {
            my $author = $1;
            $stats{final}{by_author}{$author}     ++;
            $stats{final}{by_file}{$file}{$author}++;

            $stats{final}{by_author}{total}       ++;
            $stats{final}{by_file}{$file}{total}  ++;
            $stats{final}{by_file}{$file}{total}  ++;
        }
    }
    close GIT_BLAME;
}
close GIT_LS;

print "Total lines committed by author by file\n";
printf "%25s %25s %8s %8s %9s\n",'file','author','added','removed','pct add';
foreach my $file (sort keys %{$stats{total}{by_file}}) {
    printf "%25s %4.0f%%\n",$file
            ,100*$stats{total}{by_file}{$file}{total}{added}/$stats{total}{by_author}{total}{added};
    foreach my $author (sort keys %{$stats{total}{by_file}{$file}}) {
        next if $author eq 'total';
        if ( $stats{total}{by_file}{$file}{total}{added} ) {
            printf "%25s %25s %8d %8d %8.0f%%\n",'', $author,@{$stats{total}{by_file}{$file}{$author}}{qw{added removed}}
            ,100*$stats{total}{by_file}{$file}{$author}{added}/$stats{total}{by_file}{$file}{total}{added};
        } else {
            printf "%25s %25s %8d %8d\n",'', $author,@{$stats{total}{by_file}{$file}{$author}}{qw{added removed}} ;
        }
    }
}
print "\n";

print "Total lines in the final project by author by file\n";
printf "%25s %25s %8s %9s %9s\n",'file','author','final','percent', '% of all';
foreach my $file (sort keys %{$stats{final}{by_file}}) {
    printf "%25s %4.0f%%\n",$file
            ,100*$stats{final}{by_file}{$file}{total}/$stats{final}{by_author}{total};
    foreach my $author (sort keys %{$stats{final}{by_file}{$file}}) {
        next if $author eq 'total';
        printf "%25s %25s %8d %8.0f%% %8.0f%%\n",'', $author,$stats{final}{by_file}{$file}{$author}
            ,100*$stats{final}{by_file}{$file}{$author}/$stats{final}{by_file}{$file}{total}
            ,100*$stats{final}{by_file}{$file}{$author}/$stats{final}{by_author}{total}
        ;
    }
}
print "\n";


print "Total lines committed by author\n";
printf "%25s %8s %8s %9s\n",'author','added','removed','pct add';
foreach my $author (sort keys %{$stats{total}{by_author}}) {
    next if $author eq 'total';
    printf "%25s %8d %8d %8.0f%%\n",$author,@{$stats{total}{by_author}{$author}}{qw{added removed}}
        ,100*$stats{total}{by_author}{$author}{added}/$stats{total}{by_author}{total}{added};
};
print "\n";


print "Total lines in the final project by author\n";
printf "%25s %8s %9s\n",'author','final','percent';
foreach my $author (sort keys %{$stats{final}{by_author}}) {
    printf "%25s %8d %8.0f%%\n",$author,$stats{final}{by_author}{$author}
        ,100*$stats{final}{by_author}{$author}/$stats{final}{by_author}{total};
}
5
AaronM

Enregistrez vos journaux dans un fichier en utilisant:

git log --author="<authorname>" --oneline --shortstat > logs.txt

Pour Python amoureux:

with open(r".\logs.txt", "r", encoding="utf8") as f:
    files = insertions = deletions = 0
    for line in f:
        if ' changed' in line:
            line = line.strip()
            spl = line.split(', ')
            if len(spl) > 0:
                files += int(spl[0].split(' ')[0])
            if len(spl) > 1:
                insertions += int(spl[1].split(' ')[0])
            if len(spl) > 2:
                deletions += int(spl[2].split(' ')[0])

    print(str(files).ljust(10) + ' files changed')
    print(str(insertions).ljust(10) + ' insertions')
    print(str(deletions).ljust(10) + ' deletions')

Vos sorties seraient comme:

225        files changed
6751       insertions
1379       deletions
1
Amen Ayach

Ce script le fera ici. Mettez-le dans authorship.sh, chmod + x it et tout sera prêt.

#!/bin/sh
declare -A map
while read line; do
    if grep "^[a-zA-Z]" <<< "$line" > /dev/null; then
        current="$line"
        if [ -z "${map[$current]}" ]; then 
            map[$current]=0
        fi
    Elif grep "^[0-9]" <<<"$line" >/dev/null; then
        for i in $(cut -f 1,2 <<< "$line"); do
            map[$current]=$((map[$current] + $i))
        done
    fi
done <<< "$(git log --numstat --pretty="%aN")"

for i in "${!map[@]}"; do
    echo -e "$i:${map[$i]}"
done | sort -nr -t ":" -k 2 | column -t -s ":"
1
user1074897

La question demandait des informations sur un auteur spécifique , mais bon nombre des réponses étaient des solutions qui renvoyaient des listes classées d'auteurs en fonction de leurs lignes de code modifiées.

C’était ce que je recherchais, mais les solutions existantes n’étaient pas tout à fait parfaites. Dans l'intérêt des personnes susceptibles de trouver cette question via Google, j'ai apporté quelques améliorations et les ai transformées en script Shell, que je présente ci-dessous. Un annoté (que je continuerai de maintenir) peut être trouvé sur mon Github .

Il n'y a aucune dépendance sur Perl ou Ruby. De plus, les espaces, les renommés et les mouvements de ligne sont pris en compte dans le nombre de modifications de ligne. Il suffit de mettre cela dans un fichier et de passer votre référentiel Git comme premier paramètre.

#!/bin/bash
git --git-dir="$1/.git" log > /dev/null 2> /dev/null
if [ $? -eq 128 ]
then
    echo "Not a git repository!"
    exit 128
else
    echo -e "Lines  | Name\nChanged|"
    git --work-tree="$1" --git-dir="$1/.git" ls-files -z |\
    xargs -0n1 git --work-tree="$1" --git-dir="$1/.git" blame -C -M  -w |\
    cut -d'(' -f2 |\
    cut -d2 -f1 |\
    sed -e "s/ \{1,\}$//" |\
    sort |\
    uniq -c |\
    sort -nr
fi
0
James Mishra

Le meilleur outil que j'ai identifié jusqu'à présent est gitinspector. Il donne le rapport défini par utilisateur, par semaine, etc. Vous pouvez installer comme ci-dessous avec npm

npm install -g gitinspector

Les liens pour obtenir plus de détails

https://www.npmjs.com/package/gitinspector

https://github.com/ejwa/gitinspector/wiki/Documentation

https://github.com/ejwa/gitinspector

des exemples de commandes sont

gitinspector -lmrTw 
gitinspector --since=1-1-2017 etc

Vous voulez Git blame .

Il existe une option --show-stats pour imprimer des statistiques.

0
gbjbaanb