Warning
J’ai abandonné cette approche il y a quelque temps. J’explique plus bas quels sont les inconvénients de cette approche. Depuis, j’ai opté pour une solution que j’ai trouvé bien meilleure: il s’agit de chezmoi [5].
Cette page présente ma façon de gérer les fichiers de configuration utilisateurs. Elles s’inspire largement de l’approche présentée ici [1] qui utilise Git.
Mise en place
Création du dépôt local
Commencer par créer un dépôt nu:
git init --bare $HOME/.dotfiles
Le script suivant:
#!/bin/bash
/usr/bin/git --git-dir="$HOME/.dotfiles/" --work-tree="$HOME" "$@"
placé dans un fichier config avec les bons droits, et dans un bon chemin. Les dotfiles sont ainsi géré comme un dépôt Git normal, mais en utilisant à commande config à la place de git.
Reste à adapter légèrement la configuration du dépôt:
config config --local status.showUntrackedFiles no
config checkout -b $(hostname)
La première ligne évite d’avoir une immense liste de fichiers non-suivis dans config status. La seconde remplace la branche master par une branche au nom de la machine; cela permettra de mieux gérer l’harmonisation entre ordis.
Vous pouvez commencer à ajouter les fichiers que vous voulez suivre.
Sauvegarde
La sauvegarder s’obtient simplement en poussant votre branche dans un dépôt distant.
Harmonisation des dotfiles
Le dépôt distant peut servir à stocker et harmoniser les dotfiles de vos différents terminaux. Différentes stratégies sont possibles:
Configuration distincte pour chaque terminal.
L’harmonisation se fait à coups de cherry-picking.
Configuration de base commune; suivie d’une branche pour les particularitées de la machine.
On peut alors faire remonter les configuration que l’on veut harmoniser avec un rebase interactive, puis faire un rebase sur les autres terminaux.
Fusionner sa branche locale avec la base commune
Lors de l’ajout d’un nouveau terminal, on se retrouve naturellement avec deux branches aux racines distinctes: celle de la branche locale et celle de la configuration commune (ici appelée master). Git refuse alors de fusionner les branche directement:
$ config merge origin/master
fatal: refusing to merge unrelated histories
De plus certains fichiers vont probablement enter en conflit:
$ config rebase origin/master
error: The following untracked working tree files would be overwritten by checkout:
[…]
Please move or remove them before you switch branches.
Aborting
Note. La commande:
for f in $(config ls-tree -r origin/master --name-only) do ; if [ -e $f ]; then echo $f ; fi ; done
liste les fichiers encombrants [2].
Voici deux méthodes de résolution:
Méthode pas-à-pas.
La branche locale est rebasée sur le premier commit de la branche commune, puis on fusionne les commits un à un.
config rebase $(config rev-list --max-parents=0 origin/master)
Méthode rapide.
Les fichiers encombrants sont ajoutés dans un commit
for f in $(config ls-tree -r origin/master --name-only) do ; if [ -e $f ]; then config add $f ; fi ; done config commit -m"host specifics"
On peut alors faire un config rebase origin/master directement. Vous aurez probablement des conflits lors de l’application du commit ci-dessus. Vous pouvez automatiquement appliquer les versions locales avec [3]
config checkout --theirs . config add .
Pourquoi faire simple quand on peut faire compliqué
Les fichiers de configuration sont pour la plupart indépendant. On peut les stocker dans des features branches distinctes. La configuration complète se fera en dans une branche dédiée où l’on fusionnera toutes le features branches.
Pour faciliter les fusions: créer une racine vide:
config commit --allow-empty
Et lui donner un nom, par exemple start.
Pour mettre à jour une feature branch, il faut d’abord la modifier localement, puis faire un rebase particulier de la branche du terminal:
config rebase --rebase-merges -i start
Le git-rebase-todo ressemble à ça:
label onto # Branch f-feature-x reset onto pick 1111111 comment pick 2222222 comment' … label f-feature-x # … other branches reset onto merge -C 3333333 f-feature-x # Merge branch 'f-feature-x' into …
Il faut alors identifier le merge à mettre à jour, et remplacer la ligne par un break. Git rebase s’arrêtera au break, il faut alors fusionner la branche à nouveau, puis config rebase --continue.
Autres choses pêle-mêle
Si vous êtes un utilisateur de gitg vous pouvez vouloir afficher les commits par ordre topologique:
gsettings set org.gnome.gitg.preferences.history topological-order true
Ce réglage est toutefois global.
Retour sur expérience: les défauts
Après une période d’utilisation, j’ai remarqué deux défauts dans cette approche:
- Le fait que le worktree soit directement dans les dotfiles posent des problèmes car le comportement des outils, les programmes diponnibles, etc. est modifié en direct.
- C’est compliqué de gérer les différences entre les machines. Toujours faire des rebases est compliqué, et on finit par abandonner cette approche à la longue.
Le logiciel chezmoi [5] est un wrapper autour de Git qui propose des solutions à ces problèmes. Il y a effectivement un worktree distinct où on peut préparer les fichiers, et un système de templates permet d’avoir des différences d’une machine à une autre.
Liens
| [1] | https://www.atlassian.com/git/tutorials/dotfiles |
| [2] | https://stackoverflow.com/questions/15606955/how-can-i-make-git-show-a-list-of-the-files-that-are-being-tracked |
| [3] | https://stackoverflow.com/questions/10697463/resolve-git-merge-conflicts-in-favor-of-their-changes-during-a-pull |
| [4] | https://shibumi.dev/posts/how-to-handle-dotfiles |
| [5] | (1, 2) https://www.chezmoi.io/ |