Un prompt Git qui déchire
Par Christophe Porteneuve • Publié le 22 mai 2013 • 9 min

Dans l’univers des utilisateurs de Git, il y a ceux qui sont encore cantonnés à l’interface graphique (c’est bien triste, ils n’utilisent pas 2% de la puissance quotidienne de l’outil) et ceux qui sont passés à la ligne de commande (CLI, bravo à eux).

Mais même au sein de ces derniers, une part encore trop grande ne recourt pas à un prompt personnalisé pour disposer à tout moment d’informations importantes sur leur dépôt local et leur copie de travail, sans parler d’une complétion intelligente sur les commandes Git.

Cet article vise à vous aider à optimiser votre prompt quand vous êtes dans vos dépôts Git.

C’est quoi un prompt ?

C’est le texte qui est affiché avant chaque invite de commande dans une session shell. Par exemple, dans une invite de commande Windows classique, c’est généralement juste le chemin suivi d’un chevron fermant, du genre :

C:\Users\Tristounet> _

Sur les système Linux/Unix, la plupart des gens ont un prompt par défaut qui affiche leur identifiant, le nom de la machine, et le chemin courant, suivis d’un $ s’ils sont des utilisateurs classiques ou de # s’ils sont le super-utilisateur, root :

tdd@CodeWeaver:js-attitude $ _

Selon le shell utilisé (cmd ou PowerShell sous Windows, une pléthore sur Linux/Unix dont émergent principalement Bash et ZSH), ce prompt se configure de diverses façons, le plus souvent au moyen de variables d’environnement dédiées, voire d’une fonction shell spécifique.

Pourquoi un prompt personnalisé ?

Beaucoup de gens configurent leur prompt pour ajuster l’ordre d’affichage des infos, en ajouter, coloriser des parties, indiquer automatiquement si la dernière commande a rencontré une erreur, etc.

Informations immédiates sur l’état / le statut

Dans cet esprit d’informations complémentaires, il est possible de recourir à des fichiers spécifiques de Git pour y incruster plein d’infos utiles sur le dépôt courant, son index et sa copie de travail, afin…

  • de gagner du temps (inutile de les demander explicitement)
  • mais aussi d’éviter de nombreux pièges en remarquant en amont qu’on n’est pas dans l’état où on croyait (ex. il reste de l’untracked, y’a du modified, la branche active n’est pas la bonne, etc.).

Pour cela, on va personnaliser les variables qui gouvernent la construction du prompt à chaque invite de commande.

Complétion intelligente

Il ne s’agit pas du prompt à proprement parler, mais il est également possible de renseigner auprès de notre shell une gestion de complétion (généralement avec la touche Tab) pour les diverses commandes de Git, et même pour les arguments de ces commandes !

Ce confort supplémentaire n’est pas anodin, en ce qu’il rend de nombreux aliases (raccourcis) superflus, et accélère considérablement la saisie des commandes.

Les cas déjà gérés

Selon votre situation technique, vous avez déjà plus ou moins de choses qui sont configurées pour vous côté prompt et complétion.

Oh-my-Zsh

Pour ceux qui utilisent ZSH au travers de l’excellentissime projet Oh My ZSH, ils disposent automatiquement d’une prise en charge des dépôts Git et d’un grand nombre de thèmes (du sublime à l’immonde) pour personnaliser leur affichage.

Vous y noterez un recours fréquent aux caractères Unicode et à la palette 256 couleurs. Utiliser un terminal moderne est donc recommandé.

mSysGit (Windows)

Curieusement, les utilisateurs de Git sous Windows, au travers de mSysGit, sont déjà pas mal lotis puisque la complétion et le prompt sont déjà en place dans le Git Bash installé par l’outil.

Seul bémol : la configuration par défaut omet certaines infos, notamment les untracked, il faudra donc personnaliser un peu le fichier .bashrc à la racine du compte utilisateur pour améliorer l’affichage obtenu. Vous verrez comment dans un instant.

Se configurer ça à la main

Pour les utilisateurs de ZSH, vous êtes sensés être des utilisateurs avancés, je vous laisse donc aller fouiller dans les fichiers fournis par Oh My ZSH pour y trouver votre bonheur.

Les utilisateurs de Bash auront besoin de trois choses :

  1. Charger les fichiers de prompt/complétion s’ils ne sont pas auto-chargés
  2. Définir les variables d’environnement contrôlant l’affichage par la fonction de prompt Git
  3. Modifier la définition du prompt pour recourir à ladite fonction

Le fichier de configuration du shell

Suivant le système que vous utilisez et la façon dont vous ouvrez votre shell, Bash reconnaît trois fichiers de configuration, tous placés à la racine de votre compte utilisateur.

Cette racine est du style C:\Documents and Settings\TonPtitNom ou C:\Users\TonPtitNom sur Windows, /home/tonptitnom sur Linux/Unix et /Users/tonptitnom sur OSX.

Les trois fichiers concernés sont .bashrc, .bash_profile et .profile. Linux/Unix utilise généralement les deux premiers : l’un contient les instructions d’initialisation tandis que l’autre se contente de lui déléguer le travail. OSX a tendance à favoriser .profile pour son Terminal.

mSysGit n’en propose aucun par défaut, mais vous pourrez créer .bashrc manuellement (attention toutefois à la majorité des éditeurs Windows qui refuseront de créer un fichier commençant par un point : ils colleront sûrement une extension .txt supplémentaire derrière, il vous faudra ouvrir le Git Bash et faire mv .bashrc.txt .bashrc pour le renommer correctement afin qu’il soit utilisé par le shell).

Dans la suite de cet article, nous ferons référence au fichier qui concerne votre cas de figure sous le nom générique « fichier de configuration ».

Localiser les fichiers contribués

C’est ici que ça varie pas mal suivant votre contexte technique.

Si vous utilisez mSysGit, vous n’avez rien à faire côté fichiers, il vous suffira de manipuler les variables d’environnement tout à l’heure.

Dans les autres cas basés sur Bash, tout dépend de comment vous avez installé Git.

Si vous êtes passés par un paquet Linux/Unix classique, vous avez probablement le fichier de complétion déjà posé à la bonne place. C’est généralement dans /etc/bash_completion.d, et ça s’appelle soit git (un seul fichier), soit git-completion.bash et git-prompt.sh (deux fichiers). Dans les deux cas, vu qu’ils sont placés dans ce dossier, ils sont auto-chargés.

Si c’est auto-chargé, vous n’avez rien à faire à la section suivante (« Activer la complétion »), passez directement aux sections de configuration du prompt.

Activer la complétion

Restent les cas d’installation manuelle, au travers d’une compilation des sources ou, plus automatiquement, de Homebrew sur OSX.

Dans le cas d’une compilation manuelle, cherchez vos fichiers de complétion. Ils sont probablement quelque part dans /usr/local, par exemple /usr/local/git/contrib/completion. Supposons que ce soit le cas, vous devrez rajouter au fichier de configuration la ligne suivante (adaptez aux noms de fichier que vous y aurez trouvés) :

source /usr/local/git/contrib/completion/git-completion.bash
source /usr/local/git/contrib/completion/git-prompt.sh

Dans le cas de Homebrew, ils sont normalement dans le etc/bash_completion.d du préfixe Homebrew, qui est par défaut /usr/local. Les scripts de ce répertoire sont alors traités automatiquement par un script général etc/bash_completion. Histoire d’être génériques, on va lui demander de nous sortir ça et on teste autour pour ne pas se faire mal plus tard. Ajoutez ceci à votre fichier de configuration :

if [ -f $(brew --prefix)/etc/bash_completion ]; then
  source $(brew --prefix)/etc/bash_completion
fi

Une fois que vous avez bidouillé et sauvé (si si, plein de gens oublient…) votre fichier de configuration, ouvrez directement un nouveau terminal pour vérifier que vous n’avez pas d’erreur au démarrage (regardez bien ce qui s’affiche dès le lancement !).

Tapez ensuite git, une espace, et la touche Tab (une ou deux fois, suivant votre terminal) : si vous voyez apparaître une liste de commandes Git, on a gagné. Si vous voyez juste les fichiers du répertoire courant, on a perdu, et il vous va falloir examiner votre configuration pour trouver ce qui cloche.

Configurer un prompt simple

Commençons avec un prompt simple, qui reproduirait uniquement la version classique en incrustant si besoin des infos Git. Cette section est longue mais c’est parce qu’on teste plusieurs configurations d’affichage, n’ayez pas peur…

Nous allons personnaliser la variable spéciale de Bash nommée PS1. Cette variable agit comme un motif évalué à chaque invite de commande. Elle peut contenir des codes spéciaux sous forme d’un caractère précédé d’une barre oblique inverse (antislash : \). Par exemple, un prompt classique est de la forme :

export PS1='\u@\h:\w \$ '

Décryptons un brin :

  • \u est l’identifiant de l’utilisateur courant
  • \h est le premier segment du nom de la machine courante (avant un éventuel premier .)
  • \w est le chemin complet du répertoire courant, l’éventuelle racine de votre compte étant abrégée en ~ (par exemple, au lieu de /Users/tdd/js-attitude, on verrait ~/js-attitude)
  • \$ affichera # ou $ suivant que l’on est super-utilisateur ou non
  • Les autres caractères (@, :, les espaces) sont littéraux.

Vous trouverez la liste complète des codes dans l’aide de Bash (man bash), à la section PROMPTING.

Ce qui nous intéresse nous, c’est qu’on peut utiliser la syntaxe d’imbrication d’exécution de Bash pour évaluer une fonction Bash et en récupérer l’affichage, directement dans le motif du prompt. Cette syntaxe est de la forme $(ligne de commande), et nous aurons ici recours à une fonction dédiée, __git_ps1, fournie par les fameux scripts qu’on s’est échinés à trouver puis charger quelques paragraphes plus haut.

Le prompt final donnerait ceci :

export PS1='\u@\h:\w$(__git_ps1) \$ '

Notez qu’il est possible de passer à __git_ps1 un motif printf pour formater son affichage. Par défaut, il s’agit de " (%s)". Le %s sera remplacé par le texte issu de la commande.

Mettez à jour votre PS1 en fin du fichier de configuration, puis rechargez-le en tapant source nom-du-fichier, ou ouvrez un nouveau terminal.

Vérifiez déjà que votre prompt n’a pas d’entrée de jeu une sale tête, ou affiche une erreur. Il devrait être comme avant (sauf si vous l’aviez personnalisé, auquel cas préférez incruster où bon vous semble $(__git_ps1) dans le motif que vous aviez déjà).

Si dès l’ouverture d’un terminal, à la racine de votre compte, vous voyez s’afficher des infos entre parenthèses (sans doute après qu’un certain temps de gel apparent a passé) c’est que vous avez créé un dépôt Git à la racine de votre compte, voire plus haut (le pire cas possible étant /, la racine absolue). Un tel cas est plus que probablement une erreur, et vous devrez retirer/déplacer le dossier .git correspondant si vous souhaitez utiliser le prompt, car dans une telle situation, à chaque prompt, tous les sous-dossiers de celui contenant le .git seront examinés en profondeur pour déterminer l’état à afficher, ce qui est catastrophique en performances.

Si tout va bien, déplacez-vous dans un dépôt Git local (à défaut, créez-en un pour voir : mkdir /tmp/toto && cd /tmp/toto && git init) : vous devriez voir apparaître, entre parenthèses, au minimum le nom de la branche courante, suivi potentiellement de plusieurs symboles.

Un prompt Git basique

Par défaut, seules les modifications dans la copie locale (dirty) et l’index (staged) sont signalées, au moyen des symboles * et +, respectivement. Suivant la version de Git utilisée, vous disposez de plus ou moins de réglages, au travers de variables d’environnement.

Sur Git 1.8.1.2 par exemple, ces variables sont :

  • GIT_PS1_SHOWDIRTYSTATE : signaler les modifs à la copie locale (symbole *) et à l’index (le stage, symbole +).
  • GIT_PS1_SHOWSTASHSTATE : signaler la présence d’entrées dans le stash (symbole $)
  • GIT_PS1_SHOWUNTRACKEDFILES : signaler la présence de fichiers ni versionnés ni ignorés (donc untracked) dans la copie locale (symbole %).
  • GIT_PS1_SHOWUPSTREAM : indiquer le rapport entre la branche locale et sa version trackée (en retard <, en avance >, synchro = ou ayant divergé <>). Les affichages peuvent être modifiés selon la valeur de la variable ; les symboles décrits ici sont pour la valeur auto, mais on en a d’autres, notamment verbose. Voyez votre script de prompt pour les détails.
  • GIT_PS1_DESCRIBE_STYLE peut prendre diverses valeurs pour représenter un detached HEAD ; par défaut (default), on a l’abbreviated SHA mais on a d’autres options, la plus utile étant probablement branch.

Eh ben, ça fait déjà du monde !

Du coup, pour personnaliser un peu votre prompt, définissons les variables qui vont bien au sein de votre fichier de configuration, par exemple juste avant de redéfinir PS1 :

export GIT_PS1_SHOWDIRTYSTATE=1 GIT_PS1_SHOWSTASHSTATE=1 GIT_PS1_SHOWUNTRACKEDFILES=1
export GIT_PS1_SHOWUPSTREAM=verbose GIT_PS1_DESCRIBE_STYLE=branch

Un prompt Git bien paramétré

Notez le % en plus (puisque j’ai de l’untracked, là…) et le u+1 qui m’indique en l’occurrence que j’ai un commit d’avance sur la branche distante, et aucun commit en retard.

Configurer un prompt avancé

Pour obtenir davantage de notre prompt, il nous faut recourir à une autre variable Bash que PS1, à savoir la bien-nommée PROMPT_COMMAND.

Cette variable contient une ligne de commande Bash complète (en général un appel de fonction, les instructions complexes étant dans la fonction) qui va être exécutée à chaque invite. Elle n’a pas pour but de renvoyer un texte, mais fait tout ce qu’elle veut et l’affichage directement. Libre à elle, donc, de faire du multi-ligne, de découper sa sortie à gauche et à droite à l’aide des codes ANSI VT-x, etc.

Avant d’entrer dans des commandes de fou, commençons juste par faire l’équivalent de ce qu’on avait jusqu’ici, mais en mode commande au lieu du motif de prompt. Il suffit de remplacer la ligne où nous définissions PS1 par celle-ci :

export PROMPT_COMMAND='__git_ps1 "\u@\h:\w" " \\\$ "'

Vous voyez ici qu’on appelle __git_ps1 avec deux arguments : le préfixe et le suffixe de l’éventuel affichage Git-spécifique. Dans cette syntaxe, si vous vouliez préciser un motif de formatage, il aurait fait l’objet d’un troisième argument. L’antislash avant le dollar est triplé car on est dans une chaîne interprétée (guillemets droits "), il y a donc déjà un premier niveau d’échappement utilisé d’entrée de jeu.

Ce qui est sympa avec ce mode, c’est qu’il nous permet de demander la colorisation du texte affiché par __git_ps1, en ajoutant à nos variables de configuration GIT_PS1_SHOWCOLORHINTS=1 :

Un prompt Git colorisé

On peut bien sûr aller beaucoup plus loin en créant ses propres fonctions. Ainsi, Matthew McCullough, le principal formateur Git chez Github, maintient dans ses fichiers de conf un prompt personnalisé plutôt bourrin, qui recourt librement aux caractères Unicode :

#!/bin/bash
# Source : https://github.com/matthewmccullough/dotfiles/blob/master/bash_gitprompt

########################################################################
# Matthew's Git Bash Prompt
########################################################################
        RED="\[\033[0;31m\]"
     YELLOW="\[\033[0;33m\]"
    GREEN="\[\033[0;32m\]"
       BLUE="\[\033[0;34m\]"
  LIGHT_RED="\[\033[1;31m\]"
LIGHT_GREEN="\[\033[1;32m\]"
      WHITE="\[\033[1;37m\]"
 LIGHT_GRAY="\[\033[0;37m\]"
 COLOR_NONE="\[\e[0m\]"

function parse_git_branch {
  git rev-parse --git-dir &> /dev/null
  git_status="$(git status 2> /dev/null)"
  branch_pattern="^# On branch ([^${IFS}]*)"
  remote_pattern="# Your branch is (.*) of"
  diverge_pattern="# Your branch and (.*) have diverged"

  if [[ ! ${git_status}} =~ "working directory clean" ]]; then
    state="${RED}⚡"
  fi
  # add an else if or two here if you want to get more specific
  if [[ ${git_status} =~ ${remote_pattern} ]]; then
    if [[ ${BASH_REMATCH[1]} == "ahead" ]]; then
      remote="${YELLOW}↑"
    else
      remote="${YELLOW}↓"
    fi
  fi
  if [[ ${git_status} =~ ${diverge_pattern} ]]; then
    remote="${YELLOW}↕"
  fi
  if [[ ${git_status} =~ ${branch_pattern} ]]; then
    branch=${BASH_REMATCH[1]}
    echo " (${branch})${remote}${state}"
  fi
}

function git_dirty_flag {
  git status 2> /dev/null | grep -c : | awk '{if ($1 > 0) print "⚡"}'
}

function prompt_func() {
    previous_return_value=$?;
    #The lowercase w is the full current working directory
    #prompt="${TITLEBAR}${BLUE}[${RED}\w${GREEN}$(parse_git_branch)${BLUE}]${COLOR_NONE}"

    #Capital W is just the trailing part of the current working directory
    prompt="${TITLEBAR}${BLUE}[${RED}\W${GREEN}$(parse_git_branch)${BLUE}]${COLOR_NONE}"

    if test $previous_return_value -eq 0
    then
        PS1="${prompt}> "
    else
        PS1="${prompt}${RED}>${COLOR_NONE} "
    fi
}

PROMPT_COMMAND=prompt_func

Pour la même situation que tout à l’heure, ça donne ceci :

le prompt Git Bash version Matthew McCullough

Chez Matthew, l’écart avec la branche distante trackée utilise des flèches simples ou doubles, et tout état non « clean tree » affiche juste un éclair. On pourrait bien sûr revenir au chemin complet, identifiant et nom de machine, etc. en plus de ces infos et de leur colorisation, et détailler les états locaux distincts.

L’important, c’est que c’est un script, votre script, et que vous pouvez donc tout faire :-)

Envie d’en savoir plus ?

Nos formations Git au Quotidien explorent en profondeur les questions de configuration, de prompt et d’options avancées ou méconnues pour les commandes même les plus simples. Une excellente façon d’aller bien au-delà de ces quelques réglages !

    Tu veux aller plus loin et maîtriser pleinement les fondamentaux de Git ou être accompagné pour garantir la qualité de tes projets grâce à une bonne mise en place de Git ? On peut t’aider ou te former, il suffit de nous décrire ton besoin !