guest@6admin.io:~$

Git submodules à la main : mes plugins Vim 8 sans gestionnaire

Debian et Ubuntu venaient enfin de livrer Vim 8 et avec lui le système de packages natif: pack/plugins/start et pack/plugins/opt. Adieu pathogen, adieu mon hack aromatisé à l’apt (vim-apthogen , épisode un de cette série). Mais les packages de Vim 8 règlent le chargement, pas la gestion: rien ne dit quels plugins votre config utilise, ni comment les cloner sur une machine neuve.

La réponse que j’ai choisie est celle que je fais tourner encore aujourd’hui: le repo de config est l’état. Les plugins vivent en git submodules du repo de config, dans pack/plugins/start/ pour les toujours actifs et pack/plugins/opt/ pour les optionnels. Un clone avec --recursive et l’éditeur entier se matérialise.

L’ère du script bash

Parce que taper git submodule add https://github.com/scrooloose/nerdtree pack/plugins/start/nerdtree douze fois par semaine fatigue, j’ai écrit un script bash de 100 lignes nommé, avec créativité, plugins:

fUpdate(){
	git submodule sync
	git submodule update --remote --merge
	git commit -am "Update Modules"
}

C’est l’histoire complète de la mise à jour et elle expose à la fois la force et la maladie de ce design. Une commande pour tout mettre à jour. Et un commit, automatiquement, à chaque fois, que la mise à jour le mérite ou pas.

Le jeu de verbes complet: add <url> [nom] [opt], remove <nom> [-f] (avec une confirmation oui/non, -f pour la sauter), list (un grep sur .gitmodules avec un pretty-printer awk), status, summary, update. L’URL est validée contre une regex copiée d’un gist, le nom du module vaut le basename du repo par défaut et add avec un argument opt dépose le plugin dans opt/.

Le necessary devil

J’avais même gardé un using-git-submodules.md dans le repo, adapté d’un billet de blog de l’époque, qui expliquait le cycle de vie complet: init, git submodule deinit, git rm -rf, puis rm -rf .git/modules/... parce que deinit ne nettoie pas les métadonnées (il ne le fait toujours pas, d’ailleurs). Il se termine sur la phrase qui résume toute l’approche: submodules are a necessary devil.

Nécessaire, parce que les submodules offrent le pinning gratuitement: le repo parent enregistre un commit exact de chaque plugin, donc “mon éditeur marche” est à un checkout git sur n’importe quelle machine. Diabolique, parce que:

  • après chaque mise à jour de plugin, le repo parent a des modifications non commitées qu’il faut commitées (à la main ou grâce à mon auto-commit au-dessus);
  • deinit + git rm + nettoyage des métadonnées est une cérémonie en trois commandes pour désinstaller quoi que ce soit;
  • tout est séquentiel: 25 plugins, c’est 25 clones git les uns après les autres.

La config autour

Le vimrc de l’époque était le monolithe classique: syn on, filetype plugin indent on, un colorscheme commenté avec un concurrent à moitié commenté aussi, swapdir/ et undodir/ créés à la main et les autocmds NERDTree collés directement depuis le README (y compris le trio “fermer Vim si NERDTree est la dernière fenêtre”). Plus deux alias de commandes dont je suis fier: command W w !sudo tee > /dev/null % et command Q qa, parce que se prémunir des typos est un art de vivre (paire toujours vivante aujourd’hui, restaurée dans l’article .bashrc de 2009 ).

Ce qui a survécu, ce qui a été corrigé

Huit ans de recul et ce design est toujours le squelette: submodules + pack/ natif, le vimrc comme source de vérité. Ce que l’époque n’avait pas et que les épisodes suivants ont corrigé:

  • Installations parallèles (le script de 2018 clone en séquentiel).
  • Pinning ré-asserté depuis la config: update --remote --merge faisait flotter joyeusement les plugins hors de leurs commits enregistrés.
  • Lazy loading: opt/ offrait le chargement à la demande, mais rien ne reliait un déclencheur de commande à packadd.
  • Ne pas commit après chaque update (l’auto-commit a fini par mourir).

Sept ans plus tard, le script a grandi en vim-plugin-manager , écrit en VimScript, avec installations parallèles en tâche de fond et tous les péchés ci-dessus corrigés. Mais le modèle d’état n’a jamais changé: il était bon en 2018, il est bon maintenant.

[edit: 2026] L’implémentation Ansible de tout ceci: le rôle vim .