Suivre les changements dans vos dépôts
Si vous êtes un utilisateur de Linux (ou UNIX) et que vous utilisez des gestionnaires de versions (en anglais SCM pour Source Control Management) comme Fossil ou Git, peut-être que comme moi vous ne voulez pas immédiatement faire de commit de vos changements. Vous ne voulez pas que vos brouillons soient suivis. Mais de temps en temps vous oubliez ces changements et vous risquez de perdre votre travail.
Voudriez-vous voir une liste de vos dépôts avec des changements non suivis à chaque fois que vous ouvrez une session shell ou un terminal? Avoir quelque chose comme ça?
Changements non suivis présents dans ces dossiers:
/home/nales/git-project
/home/nales/fossil-project
/home/nales/website
~ $
Cet article est sur des scripts que j'utilise au quotidien pour suivre les changements dans mes dépôts. Vous pouvez trouver mes scripts (et d'autres) dans mon dépôt de scripts. Ces scripts fonctionnent sur Linux, ils fonctionnent peut-être sur des systèmes BSD ou MacOS. Le but de cet article n'est pas d'expliquer ligne par ligne mes scripts, mais de vous donnez l'idée d'utiliser une solution similaire et de vous montrer comment adapter ma solution à la vôtre.
Le terme SCM
est utilisé ici pour désigner les gestionnaires de versions (Source Control Management software) comme Fossil et Git. Si vous ne savez pas ce qu'est un SCM, je vous conseille fortement de voir Git qui est le plus populaire en ce moment en 2020 (même si je préfère Fossil). En résumé, un SCM est un logiciel pour organiser les changements dans vos fichiers (souvent du code source). Vous pouvez diverger de l'état actuel pour faire des changements et les appliquer plus tard, vous pouvez collaborer avec d'autres personnes et appliquer leurs changements aux vôtres (merge), vous pouvez aussi voir l'historique des changements pour voir comment les fichiers ont évolué et pour revenir en arrière.
Planifier la solution
Le but est de vérifier si il y a des changements non suivis (uncommit) de notre côté dans les dépôts suivis par la solution. Le but n'est pas de vérifier les changements distants d'autres personnes. Nous utilisons Fossil et Git.
Nous faisons deux scripts pour ça. Le premier script, which-scm
, sert à trouver quel SCM est utilisé dans un dossier et quel est le chemin racine du dépôt. Le second script, follow-scm
, sert à ajouter les dépôts que l'on veut suivre et à vérifier lesquels ont des changements non suivis. Quand nous ouvrons un nouveau shell dans un terminal, les dépôts avec des changements non suivis sont affichés.
Trouver le SCM avec which-scm
Fossil et Git créent tous les deux un fichier caché dans le chemin racine de leurs dépôts. Fossil crée une base de donnée SQLite nommé .fslckout
. (Fossil a été créé par les développeurs de SQLite.) Git créé un dossier nommé .git
.
Nous voulons que which-scm
affiche dans la sortie standard le nom du SCM et le chemin racine du dépôts, le tout séparé par une tabulation pour simplifier l'intégration par d'autres scripts. Si un SCM est trouvé, le script retourne 0
. Si aucun SCM n'est trouvé, parce que le dossier vérifié n'est pas un dépôt ou que c'est un dépôt d'un SCM non géré comme SVN, alors rien n'est affiché et le script retourne 1
.
#!/bin/sh
print_info_and_exit() {
printf "%s\t%s\n" "$1" "$(pwd)"
exit 0
}
check_local() {
if [ -d ".git" ]; then
print_info_and_exit "git"
elif [ -f ".fslckout" ]; then
print_info_and_exit "fossil"
fi;
}
# Si aucun argument, utilise le dossier courant comme argument.
path="${1:-$(pwd)}"
if [ ! -d "$path" ]; then
printf "%s is a not a directory path.\n" "$path" 1>&2
exit 2
fi
# Vérifie le dossier et ses parents jusqu'à abandonner à "/" (racine système)
cd "$path"
while [ "$(pwd)" != "/" ]; do
check_local
cd ..
done
exit 1
Utiliser le script dans un shell nous donne ces résultats.
$ which-scm /home/nales/git-project/src/
git /home/nales/git-project
$ which-scm /home/nales/fossil-project/src/
fossil /home/nales/fossil-project
$ which-scm /home/nales/not-a-repository/
$ echo $?
1
Écrire follow-scm
follow-scm
est utilisé pour choisir quels dépôts à suivre dans le système. Il utilise which-scm
pour garder en mémoire quel SCM est utilisé et le chemin racine du dépôt. Écrivons le script ensemble en partie.
Définir les fichiers et les commandes du script
Nous devons trouver où stocker la liste des dépôts que follow-scm
suit. Pour laisser l'utilisateur organiser où les fichiers se trouvent dans son système, le script suit les spécifications XDG (XDG Base Directory specification). Nous écrivons nos données dans le dossier défini par XDG_DATA_HOME
et dans le fichier follow-scm
.
Il y a plusieurs commandes à définir. Je préfère utiliser des variables pour stocker les noms de commandes, c'est plus facile à refactoriser. Je ne vais pas implémenter dans cet article toutes les commandes car ce n'est pas le but. Jetez un coup d'œil sur mon dépôt de scripts pour une implémentation complète. Voici ici le squelette du code.
#!/bin/sh
COMMAND_HELP="help"
COMMAND_ADD="add"
COMMAND_REMOVE="remove"
COMMAND_AUTOREMOVE="autoremove"
COMMAND_CHECK="check"
COMMAND_LIST="list"
COMMAND_EDIT="edit"
DATA_FILE="${XDG_DATA_HOME:-$HOME/.local/share}/follow-scm"
if [ ! -e "$DATA_FILE" ]; then
touch "$DATA_FILE"
fi
# Nous avons besoin d'un fichier temporaire pour remove et autoremove
# ATTENTION: Linux utilise /tmp, pas toujours le cas ailleurs. À adapter!
TEMP_DIR="${TMPDIR:-/tmp}"
TEMP_FILE="$TEMP_DIR/follow-scm-$$"
is_known_scm() {
[ "$1" = "fossil" ] ||
[ "$1" = "git" ]
}
# Nous définissons les commandes ici.
if [ $# -eq 0 ]; then
help
else
case "$1" in
"$COMMAND_HELP")
shift; help "$@"
;;
"$COMMAND_ADD")
shift; add "$@"
;;
"$COMMAND_REMOVE")
shift; remove "$@"
;;
"$COMMAND_AUTOREMOVE")
shift; autoremove
;;
"$COMMAND_LIST")
shift; list
;;
"$COMMAND_EDIT")
shift; edit
;;
"$COMMAND_CHECK")
shift; check
;;
*)
print_unknown_command "$1"
esac
fi
Ajouter les dépôts à suivre
La commande add
prend un chemin en argument. Si il n'y en a pas, follow-scm
utilise le dossier courant. Nous stockons simplement le résultat de which-scm
dans le fichier de données défini plus haut.
add() {
path="${1:-$(pwd)}"
which_output="$(which-scm "$path")"
if [ -z "$which_output" ]; then
printf "%s is not in a SCM repository.\n" "$path"
exit 1
fi
scm="$(printf %s "$which_output" | cut -f1)"
if ! is_known_scm "$scm"; then
# Devrait afficher les erreurs et instructions ici
exit 1
fi
# Pas besoin d'ajouter de nouveau une entrée déjà présente
if grep --quiet "^$which_output\$" "$DATA_FILE"; then
return
fi
printf "%s\n" "$which_output" >> "$DATA_FILE"
}
Puis il n'y a qu'à ajouter les dépôts que vous voulez suivre.
$ follow-scm add /home/nales/git-project/src/
$ follow-scm add /home/nales/fossil-project/src/
Vérifier les changement dans Fossil et Git
Avec Fossil, il y a besoin de deux commandes pour vérifier les changements. La commande fossil changes
affiche les changements pour les fichiers déjà suivis, la commande fossil extras
afficher les nouveaux fichiers qui ne sont pas encore suivi. Si aucun changement n'est présent, rien n'est affiché.
Avec Git, nous n'avons besoin que de git status -s
pour afficher les changements. Si il n'y en a pas, rien n'est affiché.
get_scm_from_entry() {
printf "%s" "$1" | cut -f1
}
get_path_from_entry() {
printf "%s" "$1" | cut -f2
}
check() {
line_number=0
while read -r line; do
line_number=$((line_number + 1))
# Ici il devrait y avoir des tests pour vérifier l'intégrité des
# entrées stockées dans le fichier de données. Je les passe pour être
# concis.
path="$(get_path_from_entry "$line")"
scm="$(get_scm_from_entry "$line")"
cd "$path" || continue
if [ "$scm" = "git" ] && [ -n "$(git status -s)" ]; then
printf "%s\n" "$path"
elif [ "$scm" = "fossil" ] && [ -n "$(fossil changes && fossil extras)" ]; then
printf "%s\n" "$path"
fi
done < "$DATA_FILE"
}
Maintenant nous pouvons vérifier les dépôts avec des changements non suivis.
$ follow-scm check
/home/nales/fossil-project
Supprimer des entrées
Sans aller dans les détails ici, utilisez quelque chose comme ça pour supprimer des dépôts à suivre dans le fichier de donnée:
remove() {
path="${1:-$(pwd)}"
which_output="$(which-scm "$path")"
if [ -z "$which_output" ]; then
# Afficher ici les erreurs et proposer d'utiliser autoremove
exit 1
fi
grep -v "^$which_output\$" "$DATA_FILE" > "$TEMP_FILE"
mv -f "$TEMP_FILE" "$DATA_FILE"
}
autoremove() {
printf "" > "$TEMP_FILE"
while read -r line; do
# ...
# Tester ici chaque ligne, passer les entrées incorrectes.
# ...
# Écrire uniquement les entrées correctes
printf "%s\n" "$line" >> "$TEMP_FILE"
done < "$DATA_FILE"
mv -f "$TEMP_FILE" "$DATA_FILE"
}
Pour supprimer le dépôt Git actuellement suivi de la liste des dépôts à suivre, faire:
$ follow-scm remove /home/nales/git-project/src/
Je ne vais pas montrer les autres commandes comme help
ou list
parce que ce n'est pas le but ici.
Afficher la liste dans une nouvelle session shell
Nous voulons afficher les dépôts avec des changements non suivis quand nous ouvrons une nouvelle session shell (quand vous ouvrez un terminal). Pour ce faire, ajoutez dans votre .bashrc
ou .zshrc
(ou l'équivalent pour votre système) ces lignes.
# Affiche en rouge la liste des dépôts suivis avec changements non suivis.
echo -en "\033[1;31m"
follow-scm check
echo -en "\033[0m"
Cela fonctionne, mais ce n'est pas très efficace. À chaque fois que vous ouvrez une nouvelle session shell, vous devez attendre la fin de la commande avant de pouvoir faire quoique ce soit. Améliorons ça.
La première chose à faire est de créer un script nommé follow-scm-check
qui affiche le résultat de follow-scm check
dans un fichier cache, ou supprime ce fichier si il n'y a rien à afficher.
#!/bin/sh
# Si appelé dans une tâche cron, ne pas oublier d'exporter la variable
# d'environnement PATH avec les chemins pour ce script et ses dépendances
# (follow-scm, git, fossil...). Si vous ne le faites pas, peut-être que rien ne
# fonctionnera parce que le shell ne saura pas trouver ces scripts.
CACHE_FILE="${XDG_CACHE_HOME:-$HOME/.cache}/follow-scm-check"
result="$(follow-scm check)"
if [ -n "$result" ]; then
echo "Uncommitted changes present in these directories:" > "$CACHE_FILE"
printf "%s\n" "$result" >> "$CACHE_FILE"
elif [ -f "$CACHE_FILE" ]; then
rm "$CACHE_FILE"
fi
Maintenant nous pouvons appeler ce script toutes les 10 minutes en utilisant une tâche cron. Si vous ne savez pas ce qu'est une tâche cron, c'est une tâche dont l'exécution est planifiée selon des règles que vous choisissez. Ici se trouve la tâche cron dans mon système, utilisez la commande crontab -e
pour l'ajouter:
*/10 * * * * export PATH=$PATH":$HOME/bin:$HOME/.local/bin:/usr/local/bin/"; follow-scm-check
C'est vraiment pénible de travailler avec des tâches cron. Elles ne s'exécutent pas dans votre shell habituel donc même si votre script fonctionne quand vous l'exécutez manuellement, peut-être ne fonctionnera-t-il pas en tant que tâche cron. Vous devez bien vérifier la configuration de la variable d'environnement PATH
. Vous voulez voir le journal d'erreurs en cas de problème? Que vous êtes drôle! Pensez-vous vraiment que sur Linux il y a un moyen standard entre toutes les différents versions pour ce genre de choses simples? Il n'y a vraiment pas à chercher plus loin pourquoi $CURRENT_YEAR
n'est pas the year of the Linux desktop...
Explication de la tâche cron.
- Nous configurons la tâche pour être lancée toutes les 10 minutes avec
*/10 * * * *
. - Le reste de la ligne est sur le script à exécuter. La configuration de la variable d'environnement
PATH
correspond à mes besoins sur mon système.- J'ai sauvegardé
follow-scm-check
dans$HOME/bin/
parce que c'est un script personnel. - J'ai mon exécutable
fossil
dans$HOME/.local/bin/
qui est un dossier standard pour ça. - Et les scripts venant de mon dépôt de scripts qui inclut
fossil-scm
sont installés dans/usr/local/bin/
comme le font les gens de suckless pour leurs logiciels. (Vous devriez vérifier leMakefile
de mon dépôt au cas où l'emplacement a changé depuis que j'ai écrit cet article.)
- J'ai sauvegardé
- Enfin on appelle
follow-scm-check
.
Maintenant nous pouvons mettre à jour le code dans .bashrc
ou .zshrc
(ou équivalent) pour afficher la liste des dépôts avec des changements non suivis.
# Affiche en rouge la liste des dépôts suivis avec changements non suivis.
if [ -f ~/.cache/follow-scm-check ]; then
echo -en "\033[1;31m"
cat ~/.cache/follow-scm-check
echo -en "\033[0m"
fi
Astuce: suivre tous les dépôts dans votre système
Plutôt que de manuellement chercher et ajouter tous les dépôts à suivre dans votre système, nous pouvons utiliser find
pour automatiser ça.
Pour trouver les dépôts, nous pouvons chercher les dossiers .git
et les fichiers .fslckout
. Pour plus de détails, regardez le manuel de find
à la partie OPERATORS
. Nous supposons que les dépôts sont dans le dossier Projects
.
$ find Projects \( -type d -a -name '.git' -o -type f -a -name '.fslckout' \)
Projects/Website/.fslckout
Projects/Scripts/.git
Projects/G-Code-Viewer/.git
find
a une commande pour exécuter un script pour chaque chose qu'il trouve. Nous ne pouvons pas directement utiliser follow-scm add
parce que cette commande ne fonctionne qu'avec des dossiers mais ici find
retourne également les chemins pour les fichiers .fslckout
. La solution la plus simple est de créer un script temporaire.
#!/bin/sh
# ${1%/*} enlève le plus petit suffixe dans "$1" avec le pattern "/*". Ici cela
# enlève de l'argument "/.git" et "/.fslckout", donc retourne le chemin du
# dossier.
# Exemple: "/home/nales/project/.git" devient "/home/nales/project"
follow-scm add "${1%/*}"
Nous pouvons maintenant mélanger notre commande find
avec notre script temporaire.
$ find Projects \( -type d -a -name '.git' -o -type f -a -name '.fslckout' \) -exec ./add-scm {} \;
Tous les dépôts sont maintenant suivis. Si vous voulez en supprimer certains, vous pouvez utiliser follow-scm edit
qui ouvre le fichier data follow-scm
dans un éditeur de texte. Ici le code de la commande si cela vous intéresse:
edit() {
# Vérifie les variables d'environnement VISUAL puis EDITOR sinon choisit vi
editor="${VISUAL:-${EDITOR:-vi}}"
"$editor" "$DATA_FILE"
}
En résumé
Pour afficher tous les dépôts dans votre système avec des changements non suivis, vous pouvez utiliser follow-scm
présent dans mon dépôt de scripts ou créer votre propre version. Après ça, créez simplement une tâche cron pour faire une vérification régulièrement. Affichez ensuite le résultat de cette vérification quand vous ouvrez une session shell.
Avec toutes les explications que je vous ai données, vous êtes plus que prêt pour faire votre propre version. Amusez-vous bien!