$ git for-each-ref --sort=authordate --format '%(authordate:relative) %(refname:short)' refs/heads
Note : ne tient pas compte des renommages.
Note : avec --before et --after on peut scanner une période donnée.
$ git log --format=format: --name-only --after=2020-01-01 | grep -v '^$' | sort | uniq -c | sort -r | head -100
Autre versions, nombre de commits par fichier :
$ find . -name "*.java" | xargs -n1 -I file sh -c 'echo `git log --oneline file | wc -l` file' | sort -nr
$ find . -name "*.java" -exec sh -c 'echo `git log --oneline {} | wc -l` {}' \; | sort -nr
Note : en nombre brut de lignes, même vides. Voir ci-dessous, avec Tokei pour compter les lignes de code source.
$ git ls-files | grep -v 'package.*\.json' | xargs wc -l | sort -r | head -100
Note : l'étape de l'âge peut être assez longue. Sur cette étape, pv est optionnel, il sert à avoir un indicateur de progression.
$ git ls-files api/lib/domain/services > files.txt
$ git ls-files api/lib/domain/services | xargs -n 1 git log -1 --format='%ad' --date=short -- | pv -l > age.txt
$ paste age.txt files.txt | sort -r
Peut être utilisé pour générer un tsv, ou coller la sortie dans Excel.
Note : à exécuter à la racine du projet git, même pour analyser un sous-répertoire.
Note : à cause de la longueur de la commande pour déterminer la date de dernier changement, ça peut prendre plusieurs minutes. Restreindre à un sous répertoire accélère.
#!/usr/bin/env bash
set -e
SIZE_FILE=`mktemp /tmp/seb.XXXX`
AGE_FILE=`mktemp /tmp/seb.XXXX`
CHANGES_FILE=`mktemp /tmp/seb.XXXX`
DIR=${1:-.}
git log --format=format: --name-only "$DIR" | grep -v '^$' | sort | uniq -c > "$CHANGES_FILE"
git ls-files "$DIR" | xargs wc -l > "$SIZE_FILE"
git ls-files "$DIR" | xargs -n 1 git log -1 --format='%ad' --date=short -- | paste "$SIZE_FILE" - > "$AGE_FILE"
echo -e "file\tchanges\tsize\tage"
join -1 2 -2 2 "$CHANGES_FILE" "$AGE_FILE" | sort -rhk 2 | tr ' ' '\t'
rm "$SIZE_FILE" "$AGE_FILE" "$CHANGES_FILE"
Note : c'est du fish shell, à adapter un brin si vous utilisez bash ou zsh.
$ for file in (find lib/domain/services -type f) ; \
git log --format=%ad --date=short $file | tail -1 ; \
echo $file ; \
end | \
paste -d ' ' - - | \
sort
Avec eslint et gawk, lister et trier par taille :
$ npx eslint -f compact --rule '"max-lines-per-function": ["error", 20]' lib |\
gawk 'match($0, /\(([0-9]+)\)/, a) {print a[1]"\t"$0}' |\
sort -rn
Avec un format un peu plus sympa à lire (autre option : écrire un formater eslint en JS) :
$ npx eslint -f compact --rule '"max-lines-per-function": ["error", 20]' lib |\
gawk 'match($0, /([^:]+): line ([0-9]+).*Error - (.*) has too many lines \(([0-9]+)\)/, a) {print a[4]"\t"a[1]":"a[2]" "a[3]}' |\
sort -rn |\
less
La même chose mais filtre et ouvre les 20 premiers fichiers dans vim :
$ npx eslint -f compact --rule '"max-lines-per-function": ["error", 20]' lib |\
gawk 'match($0, /([^:]+): line ([0-9]+).*Error - (.*) has too many lines \(([0-9]+)\)/, a) {sub(ENVIRON["PWD"]"/", "", a[1]); print a[4]"\t"a[1]":"a[2]" "a[3]}' |\
grep -v 'Constructor\|serializer\|index\|module.exports = {' |\
sort -rn |\
head -20 |\
awk '{printf $2"\0"}' |\
xargs -o -0 vim
Avec un script awk qui compte le nombre de ligne par profondeur de blocs {} :
scopeDepth.awk
#!/usr/bin/env gawk
{ inc = 0; dec = 0 }
/{/ { inc = gsub(/{/, "{") }
/}/ { dec = gsub(/}/, "}") }
inc {
for (i = depth + inc; i > depth; --i) {
n[i] = 0;
position[i] = FNR
text[i] = $0
}
}
dec {
for (i = depth + inc; i > depth + inc - dec; --i) \
if (n[i]> max_depth) print n[i]"\t"i"\t"FILENAME":"position[i]" "text[i];
}
{ depth += inc - dec; for (i = 0; i <= depth; ++i) n[i] += 1 }
Utilisation avec find et xargs pour analyser un répertoire :
find api/lib -type f -not -name '*.ods' -print0 |\
xargs -0 -L10 awk -v max_depth=20 -f scopeDepth.awk |\
grep -v 'class\|constructor\|index\|config\|serializer\|module.exports = {' |\
sort -rn > result.txt
$ npx eslint -f compact --rule '"complexity": ["error", 5]' lib |\
gawk 'match($0, /([^:]+): line ([0-9]+).*Error - (.*) has a complexity of ([0-9]+)/, a) {sub(ENVIRON["PWD"]"/","",a[1]);print a[4]"\t"a[1]":"a[2]" "a[3]}' |\
sort -rn |\
head -20
En JS par exemple, où le mot clé est require :
$ rg '\brequire\(\'\.' api/lib | awk -F\' '{ print $2 }' | awk -F/ '{ print $NF }' | sort | uniq -c | sort
Avec grep :
$ grep -Er '\brequire\(\'\.' api/lib | awk -F\' '{ print $2 }' | awk -F/ '{ print $NF }' | sort | uniq -c | sort
Pour voir tous les changements successifs d'un fichier :
$ git log -p monfichier.js
La même chose depuis Vim :
:!git log -p %
Trouver les commits dont le diff contient un texte (le fameux "git pickaxe") :
$ git log -p -S montexte
rg --vimgrep --smart-case fixme | awk -F':' '{system("git --no-pager blame --show-name -L"$2","$2" "$1)}'
Avec grep :
grep --exclude-dir=node_modules --exclude-dir=.git --exclude-dir=dist --exclude-dir=tmp --exclude-dir=.idea -rin fixme . | awk -F':' '{system("git --no-pager blame --show-name -L"$2","$2" "$1)}'
Nécessite GNU Awk (gawk sur macOS). En Node.js les commits avec package-lock.json faussent les stats, on peut viser explicitement les répertoires sources (ici lib et tests). Pour voir si l’équipe fonctionne par “ajout de code” vs “transformation de code” par exemple ?
$ git log --shortstat --format='Date: %cd' --after=2020-11-15 lib tests | gawk '/Date:/ {d=$2}; match($0,/([0-9]+) insertions?/,a) {p=a[1]}; match($0,/([0-9]+) deletions?/,a) {m=a[1]}; /files? changed/ {print d"\t"p"\t"m}'
Ou dans un fichier awk pour sommer par date :
$ git log --format='Date: %cd' --shortstat --after=2020-01-01 lib tests | gawk -f stats.awk | sort > stats.tsv
Fichier stats.awk :
#!/usr/bin/env gawk -f
/^Date:/ { d = $2 ; dates[d]=1 }
match($0,/([0-9]+) insertions?/,a) { p[d] += a[1] }
match($0,/([0-9]+) deletions?/,a) { m[d] += a[1] }
END { for (d in dates) { print d"\t"p[d]"\t"m[d] } }Note : en lignes de code source.
tokei --sort=code --files (git ls-files | grep -v 'package.*\.json') | head -25
$ tokei --sort=code (git ls-files | grep -v 'package.*\.json')
-------------------------------------------------------------------------------
Language Files Lines Code Comments Blanks
-------------------------------------------------------------------------------
JavaScript 1291 98096 74308 7606 16182
Sass 118 6986 5835 115 1036
SVG 64 2341 2315 26 0
Handlebars 125 2606 2213 11 382
Markdown 9 1812 1812 0 0
YAML 3 289 239 32 18
HTML 7 275 230 8 37
Shell 10 264 191 16 57
Ruby 1 249 179 26 44
Plain Text 3 65 65 0 0
JSON 4 46 46 0 0
-------------------------------------------------------------------------------
Total 1635 113029 87433 7840 17756
-------------------------------------------------------------------------------