Git + Bash
Les âges des branches
$ git for-each-ref --sort=authordate --format '%(authordate:relative) %(refname:short)' refs/heads
Les 100 fichiers qui changent le plus souvent
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
Les 100 plus gros fichiers
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
Les fichiers qui ont changé récemment dans un répertoire donné
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
Exemple qui combine ces trois données
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"
Quels sont les fichiers ajoutés récemment dans ce répertoire
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
Chercher les fonctions longues
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
Lister les fonctions complexes avec ESLint
$ 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
Les dépendances à la grosse louche
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
git log
Archéologie avec 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
Trouver et dater les FIXME et autres TODO
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)}'
Avoir les short stats par date en TSV
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] } }
Avec Tokei
Les 20 fichiers les plus gros
Note : en lignes de code source.
tokei --sort=code --files (git ls-files | grep -v 'package.*\.json') | head -25
Rapport des lignes de code dans tous le projet, par langage
$ 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
-------------------------------------------------------------------------------