Article écrit par Tom Panier
Si vous êtes un·e utilisateur·rice un tant soit peu aguerri·e de Git, il y a fort à parier que vous vous êtes mis à utiliser des aliases pour gagner quelques précieuses millisecondes à chaque commande, comme, par exemple, ceux fournis par OhMyZsh si vous en êtes adepte. En général, ces aliases sont conçus pour rester suffisamment génériques pour que chacun y trouve son compte, correspondent plus ou moins directement à des commandes habituellement plus longues, et sont idéalement intégrés à votre configuration locale de Git pour pouvoir être utilisés comme les commandes susmentionnées.
Rien de tout cela ici ! Nous allons retourner l’outil dans tous les sens et user de magie noire, pour emprunter de dangereux raccourcis et placer d’immenses pouvoirs au bout de nos petits doigts… Ces aliases ne plairont guère à tous, et ce n’est pas le but ; mais peut-être pourront-ils au moins vous inspirer les vôtres. Vous êtes prêt·e ? C’est parti !
git status
, c’est pour les faibles
Personnellement, j’aime pouvoir visualiser en un coup d’œil la liste de mes branches locales, évidemment mises à jour par rapport à la remote ; cela me paraît important pour pouvoir donner du contexte à cette commande peu parlante qu’est git status
. J’ai donc progressivement délaissé ce dernier au profit de state
:
alias state='git fetch --prune ; git fetch --tags ; clear && git branch -vv && git status'
- Mise à jour des branches, en indiquant celles qui n’existent plus en remote
- Mise à jour des tags
- Nettoyage de l’écran
- Affichage de la liste des branches locales, avec un niveau de détail suffisant
-
git status
(enfin)
git pull
, c’est pour les poussins
La stratégie par défaut de git pull
(le merge) me paraît inadéquate : c’est à mon dépôt local de s’adapter aux modifications distantes, et pas l’inverse. J’ai donc systématisé l’usage de la stratégie du rebase via update
:
git-update() { branch=$1 if [[ -z $branch ]] then branch=`git symbolic-ref HEAD | cut -d "/" -f 3-` else git checkout $branch fi git pull --rebase origin $branch } alias update='git-update'
$ update # git pull --rebase sur la branche courante $ update toto # git pull --rebase sur la branche toto, après s'être placé dessus
Il n’y a qu’un petit problème : si j’ai des modifications locales, Git exigera que j’en dispose avant de pouvoir lancer cette commande. J’ai donc mis en place une seconde version qui va les mettre au préalable en stash, puis les en ôter en fin de traitement :
git-updates() { git stash && git-update "$@" && git stash pop } alias updates='git-updates'
Dès lors, je n’ai qu’à ajouter un s
à ma commande en cas de besoin. Vous constaterez que c’est un schéma qui se répète dans ma collection d’aliases.
git tag -l
, c’est pour les murs de ta cité
Je n’ai pas grand-chose à reprocher à cette commande, si ce n’est l’ordre dans lequel les tags apparaissent : un tri bête et méchant qui ne va pas vraiment être logique en passant de v0.1
à v0.10
. J’ai aussi un autre besoin : savoir quels sont les commits de ma branche courante depuis le dernier tag, afin de savoir ce qu’embarquera la « prochaine release » de mon projet.
alias tagz='git tag -l | sort -V' alias nxtrlz='tagz | tail -n 1 && git log --oneline `tagz | tail -n 1`..HEAD'
$ tagz # mes tags, triés comme il se doit $ nxtrlz # le dernier tag, ainsi que la liste des commits ayant eu lieu depuis sur la branche courante
Alors, je change de branche, je mets à jour, je supprime l’autre… RAAAH !
Qui n’a jamais fait cette action 17 536 fois durant la même journée ? Votre MR vient d’être mergée, après avoir promis que vous ferez plus attention au coding style la prochaine fois, et vous devez donc, dans l’ordre :
- vous placer sur la branche sur laquelle vous venez de merger (au hasard,
master
) - mettre cette dernière à jour
- supprimer votre branche de développement locale, car vous êtes une personne consciencieuse
C’est vite rébarbatif, laissons merged
faire le boulot :
git-merged() { sourceBranch=`git symbolic-ref HEAD | cut -d "/" -f 3-` targetBranch=$1 if [[ -z $targetBranch ]] then targetBranch='master' fi git-update $targetBranch && git branch -d $sourceBranch } alias merged='git-merged'
$ merged # se place sur master, la met à jour, et vire la branche sur laquelle vous étiez $ merged develop # pareil, mais avec develop au lieu de master
Évidemment, la petite sœur mergeds
existe également :
git-mergeds() { git stash && git-merged "$@" && git stash pop } alias mergeds='git-mergeds'
J’ai besoin du hash de mon dernier commit… je pose mon aprèm’
git-lstcmt() { git log --oneline $1 | grep -v fixup | head -n 1 | cut -d " " -f 1 } alias lstcmt='git-lstcmt'
$ lstcmt # affiche le hash du dernier commit de la branche courante $ lstcmt toto # affiche le hash du dernier commit de la branche toto
git log
, c’est trop long
git-logz() { nb=$2 if [[ -z $nb ]] then nb=10 fi git log --oneline $1 | head -n $nb } alias logz='git-logz'
$ logz # les 10 derniers commits de la branche courante, un par ligne (hash + message) $ logz toto # pareil sur la branche toto $ logz HEAD 20 # les 20 derniers commits de la branche courante $ logz toto 20 # pareil sur la branche toto
« Ingrid, est-ce que tu rebases ? »
Aaah, le rebase… la seule évocation de ce mot effraie les développeurs débutants. Une erreur classique consiste à ne pas mettre à jour la branche sur laquelle on veut se baser pour effectuer cette opération : rebase
règle ce problème !
git-rebase() { sourceBranch=`git symbolic-ref HEAD | cut -d "/" -f 3-` targetBranch=$1 if [[ -z $targetBranch ]] then targetBranch='master' fi git-update $targetBranch && git checkout $sourceBranch && git rebase $targetBranch } alias rebase='git-rebase'
$ rebase # met à jour master et rebase la branche courante dessus $ rebase develop # pareil, mais avec develop au lieu de master
Le propre du rebase, c’est qu’on rencontre parfois des conflits ; et une fois ceux-ci résolus, qu’y a-t-il de plus rageant que de parfois devoir saisir deux, voire trois commandes pour passer à l’étape suivante ? On aurait parfois simplement envie de dire à Git… de lui dire… oui, c’est ça, goon
(« go on ») !
alias goon='git add . && git rebase --continue || git rebase --skip'
Et si vous avez des modifications locales ? Bingo, rebases
!
git-rebases() { git stash && git-rebase "$@" && git stash pop } alias rebases='git-rebases'
« Oui, mais seulement en interactif ! »
La version interactive du rebase, tout aussi utile en d’autres circonstances, n’a pas échappé à ma fourberie ; étant donné que je m’en sers moi-même principalement pour fusionner des commits ensemble, la commande idoine se nomme squash
:
git-squash() { ref=$1 if [[ -z $ref ]] then ref=`git log --oneline | grep -v fixup | head -n 2 | tail -n 1 | cut -d " " -f 1` fi git rebase -i --autosquash $ref } alias squash='git-squash'
Je vais prendre un moment pour décomposer cette commande :
- elle peut prendre en paramètre une référence, tel un hash ou autre
HEAD~2
- si la référence n’est pas fournie, on va s’appuyer sur le plus ancien commit ne contenant pas « fixup »
Euh, pourquoi ?
Déjà, cesse de m’interrompre. Ensuite, c’est parce qu’on va tirer parti de cette fonctionnalité absolument magistrale du rebase interactif : l’autosquash. Cette dernière va automatiquement « préremplir » votre éditeur dans le contexte du rebase interactif, en mettant tout seul s
pour les commits dont le message commence par squash!
, et f
pour ceux dont le message commence par fixup!
, et en mettant tout ce petit monde dans le bon ordre. La cerise sur le gâteau, c’est qu’il est possible d’automatiquement créer de tels commits, plutôt que de le faire à la main… et la pastèque sur la cerise, c’est, vous l’aurez deviné, que j’ai non pas un, mais deux aliases pour ça !
git-fixup() { ref=$1 if [[ -z $ref ]] then ref=`lstcmt` fi git commit --fixup $ref } alias fixup='git-fixup'
En effet, git commit --fixup
exige qu’on lui passe une référence explicite pour savoir quel commit ajuster, alors qu’on veut bien souvent cibler le dernier.
$ fixup # crée un commit d'ajustement destiné au dernier commit $ fixup abcd1234 # idem, en ciblant le hash fourni
Ce workflow est particulièrement efficace pour répartir des modifications locales sur plusieurs commits de votre historique : vous utilisez git add
et fixup
pour créer vos commits d’ajustements, puis squash
pour lancer le rebase interactif, sachant que vous pourrez immédiatement sauvegarder et quitter l’éditeur, tout étant déjà prémâché dedans !
Dis donc, tu as parlé de deux aliases, non ?
Finement observé ! Voici le second :
alias fixupa='adal && fixup'
wat
Justement, j’y venais, excellente transition !
git add
, git commit
et git push
, c’est pour les noobs
On va finir en beauté avec les aliases les plus simples et élégants, mais surtout les plus efficaces de ma collection, et qui sont sûrement ceux que j’utilise le plus au quotidien après state
.
Commençons par un cas d’usage courant : ajouter toutes vos modifications locales à la zone de staging, y compris les nouveaux fichiers et ceux que vous avez supprimé. C’est le susmentionné adal
qui joue ce rôle :
alias adal='git add --all .'
En grand maniaque de l’historique devant l’Éternel, j’apprécie aussi de pouvoir facilement ajouter ce qui se trouve dans cette zone de staging au dernier commit sans changer son message ; cet alias-ci se nomme, fort logiquement, amend
, et est accompagné d’une variante – amenda
– qui le précède d’adal
.
alias amend='git commit --amend --no-edit' alias amenda='adal && amend'
Enfin, puisque nous avons évoqué plus haut le rebase, comment ne pas parler de git push --force
? J’ai aussi un alias pour ça (toute ressemblance avec un slogan publicitaire célèbre serait purement fortuite), dont le nom reflète bien sa philosophie : yolo
!
alias yolo='git push origin HEAD --force-with-lease'
Pour ceux d’entre vous qui ne la connaîtraient pas, l’option --force-with-lease
va faire échouer le push
si la remote contient des commits que vous n’avez pas récupérés en local : pratique pour éviter les erreurs d’inattention ! Terminons avec deux variantes fort sympathiques :
alias yoloc='amend && yolo' alias yoloca='adal && yoloc'
$ yoloc # amende le dernier commit avec le contenu de la zone de staging, et pousse le tout en force $ yoloca # idem, mais ajoute d'abord vos modifications locales à la zone de staging ; à utiliser avec prudence !
En conclusion
Vous voici désormais en possession de mes plus sombres secrets – en ce qui concerne Git, en tout cas. Je n’ai qu’un conseil supplémentaire à vous donner, qui vaut pour tous les aliases, quels qu’ils soient : ce n’est pertinent de les utiliser que quand vous savez parfaitement ce qu’ils font, sans quoi un accident est vite arrivé. De plus, vous vous retrouverez fort dépourvu si vous devez intervenir sur la machine d’un·e collègue, par exemple. À bon entendeur, salut !