Go to Hackademy website

Les aliases Git de l'extrême

Tom Panier

Posté par dans la catégorie outils

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 !


L’équipe Synbioz.
Libres d’être ensemble.

Articles connexes

Optimiser sa productivité en console

13/09/2011

J’ai l’intime conviction que tous les développeurs, dès que cela est possible, devraient travailler sous un environnement dérivé d’Unix (Linux, *BSD, Mac OS X) ce qui assure d’avoir à disposition tous les outils nécessaires au développement mais aus…

Astuces Zsh - Les plugins Git de Oh My Zsh

09/06/2015

Dans le dernier article sur ZSH, je vous ai expliqué pourquoi choisir ZSH et quelques exemples de ses fonctionnalités principales. Je vous ai également parlé rapidement de Oh My Zsh qui permet d’étendre encore les capacités de ZSH. Entrons mainten…

Afficher plus Afficher moins

Commentaires (4) Flux RSS des commentaires

  • 15/02/2018 à 15:53

    Christ_off

    Bonjour
    J'ai beaucoup d'alias mais beaucoup plus simples.
    Ma dernière joie avec git vient du -
    du - ?????
    Et oui
    Exemple
    git checkout -
    M'amène sur la branche sur laquelle j'étais AVANT
    ensuite git merge -
    me permet de merger sur la branche courante la branche sur laquelle j'étais avant
    Très pratique pour le cas : je suis sur une branche de feature, je fais un tour sur develop, je pull, je repars sur ma branche de feature et j'y merge develop

    Merci pour toutes les bonnes idées !

  • 16/02/2018 à 17:50

    zzzz

    Voir autostash dans gitconfig pour que git stash avant rebase et pop après rebase, sans avoir à le faire soi même

  • 19/02/2018 à 08:31

    Michel

    Salut et merci pour ton billet bien intéressant :)
    J'ai appris des trucs ^^

    J'aurais juste quelques remarques : tu utilise des alias et fonctions shell c'est dommage de ne pas utiliser les alias et plugins git (https://git-scm.com/book/fr/v2/Les-bases-de-Git-Les-alias-Git & https://adamcod.es/2013/07/12/how-to-create-git-plugin.html).

    Pour retrouver le dernier commit tu as beaucoup plus simple avec git log --pretty=format:'%h' -1.

    De la même manière tu peut écrire logz sous forme d'un alias git et ça supporte la coloration. Tu peux mettre -<num> pour limiter la taille.

    Tu utilise plusieurs fois "git symbolic-ref HEAD | cut -d "/" -f 3-", ça peut s'écrire "git symbolic-ref --short HEAD".

    Le fixup peut aussi se faire via : git log -1 --oneline --grep="fixup" --invert-grep --pretty=format:'%h'.

    Et enfin pour le git-updates, tu peux simplement configurer globalement git avec "git config --global pull.rebase true" (ou ajouter "rebase = true" à la section "pull" de ton ~/.gitconfig).

    Encore merci il y a de super idées et tu m'a donné l'occasion d'aller fouiller un peu les pages man de git ;)

  • 19/02/2018 à 08:42

    Tom Panier

    Bonjour Michel, et merci pour ton commentaire !

    Le fait de ne pas utiliser d'aliases/plugins Git et de ne pas me reposer sur sa configuration est un choix conscient, qui augmente la portabilité de mon setup et d'une manière générale correspond mieux à mes préférences (la concision, notamment). Cela ne rend toutefois pas tes remarques moins valables, et comme dirait l'autre, c'est une question d'habitude !

    En revanche, tes suggestions de simplification pour certains de mes aliases sont très intéressantes, je vais étudier ça de ce pas !

Ajouter un commentaire