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