title: 30 options de commande Git qui gagnent à être connues
url: http://www.git-attitude.fr/2014/09/15/30-options-git-qui-gagnent-a-etre-connues/
hash_url: 1cad71d948ab1aee73e88a73a0112df4

<p>Vous croyez connaître Git ? Peut-être bien…  Et pourtant, je parierais ma chemise<sup id="fnref:1"><a href="#fn:1" rel="footnote">1</a></sup> que pas mal de petites options sympathiques vous sont inconnues.</p>

<p>En effet, au fil des versions de Git, beaucoup de petites options font surface, soit pour des raisons de confort, soit pour de la puissance brute.  Mais comme ce n’est pas une nouvelle commande, ou pas trop mis en avant dans les annonces de sortie, ça passe sous le radar.</p>

<p>Je vous ai sélectionné une trentaine d’options, répartie sur une quinzaine de commandes, qui rendent la vie plus belle quand on fait du Git.  Voici une excellente manière de rentabiliser les prochaines minutes !</p>




<p><em>(The English version of this is <a href="https://medium.com/@porteneuve/30-git-cli-options-you-should-know-about-15423e8771df">on Medium</a>)</em></p>

<p>J’indique intentionnellement, le plus souvent, l’option que je vais détailler dans le titre de la section.  Cependant, n’en profitez pas pour sauter le texte sous prétexte que vous avez déjà vu l’option : il se peut que je l’utilise sur une commande, ou pour une raison, que vous n’avez pas vue venir !  Et puis, j’ajoute souvent des infos complémentaires sur des options corollaires ou des variables de configuration associées.</p>

<h2>Staging / unstaging partiel avec <code>-p</code></h2>

<p>Vous avez ouvert un fichier pour une raison précise, par exemple passer ce fichu tracker en asynchrone…  Et voilà que vous remarquez au passage que des rôles ARIA manquent à certains éléments d’UX, et aussi que le footer est toujours en dur au lieu de dépendre du layout, et que sais-je encore…</p>

<p>Au moment de faire le commit, vous vous rendez compte que ce fichier contient une bonne demi-douzaine (si ce n’est plus) de modifications qui <strong>recouvrent plusieurs sujets bien distincts</strong>.  À ce moment-là, trois possibilités :</p>

<ul>
<li>Vous faites un bon gros <strong>commit fourre-tout dégueulasse</strong>, avec un message à base de « + » voire, si vous êtes d’humeur flemmasse, le bon vieux « Correctifs », « Plein de modifs », « Plein de trucs »…  C’est gentil de pourrir l’historique comme ça, merci, merci bien.</li>
<li>Vous <strong>copiez-collez le fichier</strong> quelque part puis enquillez les <em>Undo</em>, si tant est que ça soit possible, pour ne garder ensuite que le premier sujet, committer, puis ré-appliquer le 2ème, committer, puis le 3ème…  Tout ça à la main, bien entendu.  <strong>Probabilité de gaufrage : 99%.</strong></li>
<li>Vous nous lisez ou allez à nos formations, et vous connaissez <code>-p</code> !</li>
</ul>


<p>La commande <code>git add -p</code> est en fait un affinage de <code>git add -i</code> : elle pré-sélectionne <strong>le mode <em>patch</em> de l’ajout interactif</strong>.  Dans la pratique, vous indiquez aussi le fichier, pour faire bonne mesure.  Par exemple :</p>

<p>J’en profite pour vous rappeler que <code>git add</code> ne sert <strong>pas</strong> à mettre sous gestion de version, mais plutôt à <strong><em>stager</em></strong> une modification, c’est-à-dire à la valider pour le prochain commit.</p>

<p>Lorsque vous faites un tel ajout, Git va auto-découper le contenu en <em>hunks</em>, en groupes de modifications, par proximité dans le fichier, en se basant sur les lignes inchangées pour faire sa découpe.  Si vos modifs sont trop proches, il sera sans doute tenté de ne pas pré-découper, et vous devrez le faire vous-mêmes avec la commande <code>s</code> (Git vous proposera une pléthore de commandes par leur initiale en bas d’affichage ; dans le doute, utilisez <code>?</code> pour afficher l’aide), pour <em>split</em> (découper).</p>

<p>Notez que même si vous avez des modifs <em>adjacentes</em> (sur des lignes qui se suivent immédiatement, sans ligne inchangée pour la découpe), vous pouvez éditer à la volée le <em>hunk</em> pour l’amener à la version que vous voulez <em>stager</em>, en utilisant la commande <code>e</code> (<em>edit</em>) pour ce faire.  C’est un peu comme du Photoshop express sur la photo du fichier qui part en <em>stage</em>.  En fait, si vous savez d’entrée de jeu que le fichier que vous allez <em>stager</em> contient des modifs adjacentes à retoucher, vous pouvez utiliser <code>-e</code> au lieu de <code>-p</code> pour présélectionner ce mode retouche, mais vous n’aurez alors pas la découpe automatique des <em>hunks</em> pour vous.</p>

<p>À l’issue de l’opération, votre fichier apparaîtra normalement <strong>à la fois <em>staged</em> et <em>modified</em></strong>.  C’est bien normal, car en effet :</p>

<ul>
<li>La dernière version committée n’est pas la même que dans le <em>stage</em> : le fichier est donc <em>staged</em></li>
<li>La version dans le <em>stage</em> n’est pas la même que celle dans le <em>working directory</em> : le fichier est donc <em>modified</em>.</li>
</ul>


<p>Vous pouvez consulter le diff du <em>stage</em> pour ce fichier avec un <code>git diff --staged index.html</code>.  Si vous voulez carrément voir le <em>snapshot</em> présent dans le <em>stage</em>, plutôt qu’une série de diffs : <code>git show :0:index.html</code>.</p>

<p>Du coup, ayez soin de ne pas faire un <code>git commit -a</code> (par exemple <code>git ci -am "Tracker asynchrone"</code>), car ce <code>-a</code> va auto-<em>stager</em> toutes les modifs connues, écrasant au passage le <em>stage</em> « sculpté » que vous aviez mis en place.</p>

<p>Enfin, peu de gens savent que <code>git reset</code> aussi a une option <code>-p</code>, qui a exactement la même UX que pour <code>add</code>, mais réalise évidemment l’inverse : elle <em>unstage</em> les <em>hunks</em> retenus.  On l’utilise souvent pour redécouper le dernier commit effectué, en faisant:</p>

<p>Les modifs qui nous sont alors présentées sont des annulations de celles réalisées par le dernier commit en date.  On valide les annulations qu’on veut, on amende le commit (voir plus loin), puis on réalise le ou les commits complémentaire(s) qu’on souhaitait effectuer pour la découpe.</p>

<p>C’est une manière très « vite et bien » de piloter une découpe de commit au sein d’un <a href="http://www.git-attitude.fr/2014/05/04/bien-utiliser-git-merge-et-rebase/#d-coupe%20au%20laser"><em>rebase</em> interactif</a>, à l’aide d’une commande de script <code>edit</code>.</p>

<h2>Prendre en compte les renommages d’un coup avec <code>-A</code></h2>

<p>Vous le savez peut-être, par défaut (en tout cas avant la 2.0), <code>git add</code> se comportait en fait comme <code>git add --no-all</code> ou, si vous préférez, <code>git add --ignore-removal</code>.  Il se basait uniquement sur le <em>working directory</em> pour établir sa liste de fichiers à prendre en compte, ce qui comprenait donc :</p>

<ul>
<li>Les modifications aux fichiers connus</li>
<li>Les nouveaux fichiers</li>
</ul>


<p>En revanche, les fichiers connus de l’index Git et introuvables sur le disque, qui apparaissent donc dans le statut comme supprimés, n’étaient pas traités.</p>

<p><strong>C’était gênant pour les renommages ou déplacements</strong>, qui entraînent à la fois une « suppression » de l’ancien chemin de fichier, et l’apparition du nouveau.</p>

<p>Pour ça, on a <code>git add -A</code>, ou sa version plus longue, <code>git add --all</code><sup id="fnref:2"><a href="#fn:2" rel="footnote">2</a></sup>.  <strong>Ça prend la totale en compte.</strong>  Lorsque l’index a connaissance des deux mouvements, il peut se rendre compte que c’est un renommage (même si une partie du contenu a bougé par-dessus le marché), ce qui permet notamment de demander plus tard à <code>git log</code> de suivre le fichier à travers ces renommages.</p>

<p><strong>À partir de Git 2.0</strong>, c’est le comportement par défaut de <code>git add</code> si on lui précise un chemin (genre <code>git add .</code>).  Au passage autre changement important en 2.0 : avant, si on faisait <code>git add -A</code> sans chemin, ça ne prenait que le répertoire courant et ses sous-dossiers, alors que maintenant, ça prend <em>tout le dépôt</em>, où qu’on soie à l’intérieur.</p>

<h2>Entrer dans les répertoires <em>untracked</em> lors du <code>status</code></h2>

<p>Vous l’avez sûrement remarqué : quand on rajoute un dossier à son dépôt, <code>git status</code> se contente de nous lister le dossier lui-même comme <em>untracked</em>, et non son contenu.  Imaginons par exemple que je viens de rajouter un super plugin à mon projet, avec un JS et une CSS à l’intérieur :</p>

<p><img src="http://www.git-attitude.fr/images/n-options-status-untracked-1.png"/></p>

<p>C’est assez pénible, je trouve…  Pourtant, on peut demander à <code>status</code> de rentrer dans les dossiers avec <code>-u</code> :</p>

<p><img src="http://www.git-attitude.fr/images/n-options-status-untracked-2.png"/></p>

<p>Je trouve d’ailleurs ça tellement pratique que je me suis collé la bonne variable de configuration pour le rendre systématique :</p>

<figure class="code"><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
</pre></td><td class="code"><pre><code class=""><span class="line">git config --global status.showUntrackedFiles all</span></code></pre></td></tr></table></div></figure>


<h2>Faire des diffs plus utiles</h2>

<p>Les diffs fournis par <code>git diff</code>, <code>git log</code> et <code>git show</code>, pour ne citer qu’eux, sont sympa, mais y’a carrément moyen de les améliorer.  En voici trois qui me tiennent particulièrement à cœur :</p>

<p>Ou sa version longue, <code>git diff --ignore-all-space</code> (plus explicite, forcément).  Cette option permet aux diff d’ignorer tout changement de whitespace à l’intérieur ou en bordure des lignes, y compris lorsqu’on passe de la présence à l’absence de whitespace, ou inversement (à l’exception de lignes entières : 1+ ligne vide là où on n’en avait aucune, ou inversement).</p>

<p>Ça peut te jouer des tours quand tu travailles sur des fichiers à indentation significative, mais pour tous les autres cas, c’est une super manière de « dépolluer » l’affichage pour se concentrer sur les parties utiles.</p>

<p>Sans <code>-w</code> :</p>

<p><img src="http://www.git-attitude.fr/images/n-options-diff-regular.png"/></p>

<p>Avec <code>-w</code> :</p>

<p><img src="http://www.git-attitude.fr/images/n-options-diff-w.png"/></p>

<p>L’autre option que j’adore a trait au mode d’affichage unitaire des diffs.  Par défaut, on fonctionne par ligne, ce qui est parfois un peu limite :</p>

<p><img src="http://www.git-attitude.fr/images/n-options-diff-lines.png"/></p>

<p>Il est déjà possible de demander à <code>diff</code> de n’afficher qu’une fois la ligne, en découpant par mot, avec <code>--word-diff</code>, la définition de « mot » étant basée sur la présence de <em>whitespace</em>.  Pour du rédactionnel, ce n’est pas gênant :</p>

<p><img src="http://www.git-attitude.fr/images/n-options-diff-words.png"/></p>

<p>Si vous trouvez que les cadrages +/- pourrissent un peu l’affichage, vous pouvez utiliser <code>--word-diff=color</code> pour affiner ça.  En fait, on peut même raccourcir ça en <code>--color-words</code> (si c’est pas mignon…).</p>

<p><img src="http://www.git-attitude.fr/images/n-options-diff-color-words.png"/></p>

<p>Mais ça nous laisse avec un souci lorsqu’on diffe du code, où les <em>whitespaces</em> sont loin d’être les seuls délimiteurs utiles.  Voyez plutôt :</p>

<p><img src="http://www.git-attitude.fr/images/n-options-diff-words-suck.png"/></p>

<p>Du coup on va préférer indiquer une <a href="http://www.js-attitude.fr/2012/08/13/enfin-maitriser-les-expressions-rationnelles/">regex</a> qui décrit « ce qu’est un mot ».  À l’extrême, on dira « au minimum un caractère, quelconque » avec juste <code>.</code>, mais en cas de découpe résultante excessive, on allongera peut-être, genre <code>.{3,}</code> pour au moins 3 caractères.</p>

<p>Ça donnerait une commande un peu longuette : <code>--word-diff=color --word-diff-regex=.</code> ce qui, comme les notes d’examen du <a href="http://fr.wikiquote.org/wiki/La_Cit%C3%A9_de_la_peur">commissaire Bialès</a>, est bien mais pas top.  On préfèrera la version courte :</p>

<figure class="code"><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
</pre></td><td class="code"><pre><code class=""><span class="line">git diff --color-words=.</span></code></pre></td></tr></table></div></figure>


<p>Démonstration :</p>

<p><img src="http://www.git-attitude.fr/images/n-options-diff-words-regex.png"/></p>

<p>Notez que si vous souhaitez toujours passer par une telle regex (c’est souvent mon cas), vous pouvez configurer l’option <code>diff.wordRegex</code> à la valeur souhaitée (par exemple <code>.</code>), de sorte que tout type de <em>word diff</em> (<code>--word-diff</code> ou <code>--color-words</code>) sans argument utilisera cette regex (si vous précisez la regex manuellement, la vôtre a naturellement priorité).</p>

<h2>Réparer le dernier commit avec <code>--amend</code></h2>

<p>Il arrive souvent qu’en lisant le bilan affiché par un <code>git commit</code>, on se rende compte qu’on a fait une bévue : on a oublié un fichier, committé un fichier en trop, ce genre de choses…</p>

<p>Une manière simple de corriger le tir, tant qu’on n’a pas <em>pushé</em> ce commit auprès des copains, consiste à se replacer dans la bonne situation (par exemple faire un <code>git add</code>, <code>git reset</code> ou <code>git rm --cached</code> sur le fichier problématique, peut-être accompagné d’un complément dans le <code>.gitignore</code>…) puis à faire :</p>

<p>Cette option n’est en fait rien de plus qu’un <code>git reset --soft HEAD^</code> suivi du commit demandé, mais comme la majorité des gens <em>ne maîtrisent pas <code>reset</code></em>, ça facilite ce cas de figure.</p>

<p>Notez que, la plupart du temps, le message de commit initial était le bon.  Je doute que vous ayiez dit « Passage à Bootstrap 3.1, et au fait je versionne à tort le mdp root du serveur » dans le commit original.  Du coup, pour vous éviter de retaper le message, ou simplement d’avoir à le revalider dans l’éditeur, vous pouvez faire :</p>

<figure class="code"><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
<span class="line-number">2</span>
<span class="line-number">3</span>
<span class="line-number">4</span>
</pre></td><td class="code"><pre><code class=""><span class="line">git commit --amend --no-edit
</span><span class="line">
</span><span class="line"># Ou si vous êtes antérieur à 1.7.9 :
</span><span class="line">git commit --amend -C HEAD</span></code></pre></td></tr></table></div></figure>


<p>C’est un cas de figure tellement courant que je vois souvent un alias <code>git oops</code> pour cette commande :-)</p>

<p>Dans votre historique, seule la dernière version du commit est visible : c’est comme si vous n’aviez pas dérapé à la base (trop fort !).  La, voire les, anciennes versions du commit sont évidemment toujours présentes dans votre <a href="https://www.atlassian.com/fr/git/tutorial/rewriting-git-history#!reflog">reflog</a>, la maxime générale de Git restant vraie : « si ça a été commité, c’est virtuellement impossible à paumer ».</p>

<h2>Fouiller intelligemment les logs avec <code>-S</code> et <code>-G</code></h2>

<p>La commande <code>git log</code> est truffée d’options (plus d’une centaine !), dont elle partage la majeure partie avec sa cousine germaine, <code>git diff</code>.</p>

<p>Nombre de ces options servent à <strong>filtrer le log</strong> avant même de l’afficher (ce qui est infiniment plus performant et pratique que de faire un <code>grep</code> par-dessus) : filtrage sur dates, chemins, branches, auteurs et committers, messages de commit… mais aussi <strong>contenus des diffs</strong>.</p>

<p>Le filtrage sur diffs est extrêmement utile pour <strong>retrouver l’origine d’un code, voire d’un bug</strong>.  Trop de gens ont tendance à passer par <code>git blame</code>, parce qu’ils étaient habitués à <code>svn blame</code>, mais cette commande est tout aussi limitée que sa consœur chez SVN :</p>

<ul>
<li>Elle affiche juste quel commit <em>a touché à la ligne en dernier</em>, sans dire pourquoi ; si ça se trouve, ça virait les espaces de fin de ligne, c’est tout</li>
<li>Elle n’affiche <em>que les lignes actuelles</em>, donc si le problème est qu’une ligne a dégagé, ou a été déplacée, ça ne nous aide en rien.</li>
</ul>


<p>En revanche, si on filtre le log sur <strong>les contenus des diffs</strong>, on saura effectivement <strong>quel commit a introduit le changement qui nous intéresse</strong>.</p>

<p>Si on s’intéresse juste à la présence de la chaîne dans les lignes activecs du diff, peu importe pourquoi et comment, on utilisera plutôt <code>-G</code> (penser alors à échapper les caractères spéciaux de regex) :</p>

<figure class="code"><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
</pre></td><td class="code"><pre><code class=""><span class="line">git log -G'Secure_?Random' -2 -- path/to/problematic_file</span></code></pre></td></tr></table></div></figure>


<p>(Généralement, dans une telle recherche, on limite aux 1 ou 2 commits les plus récents.)</p>

<p>En revanche, si on cherche spécifiquement des diffs qui ont <strong>retiré ou introduit le texte</strong>, on passera par <code>-S</code>, qui ne renvoie que les diffs ayant <strong>changé le nombre d’occurrences</strong> du texte.  Par défaut, <code>-S</code> prend un texte simple, mais si vous voulez une regex, vous pouvez rajouter <code>--pickaxe-regex</code> :</p>

<figure class="code"><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
</pre></td><td class="code"><pre><code class=""><span class="line">git log -S'Secure_?Random' --pickaxe-regex -2 -- path/to/problematic_file</span></code></pre></td></tr></table></div></figure>


<p>Si vous avez besoin que vos textes, ou regex, soient insensibles à la casse, ajoutez <code>-i</code>.  Les regex sont systématiquement traitées comme de la syntaxe étendue (ERE).  Enfin, si vous souhaitez afficher le diff à la volée (qui peut être lourd, attention), ajoutez comme d’habitude <code>-p</code> (raison de plus pour pré-filtrer sur le fichier qui vous intéresse).</p>

<h2>Accélérer les branches avec <code>-b</code>, <code>-v</code> et <code>-vv</code></h2>

<p>Alors déjà, si vous ne saviez pas encore que <code>git checkout -b</code> crée la branche cible à la volée, maintenant vous n’avez plus d’excuse.</p>

<p>Pourquoi se faire ch… à faire :</p>

<figure class="code"><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
<span class="line-number">2</span>
</pre></td><td class="code"><pre><code class=""><span class="line">$ git branch ticket-12
</span><span class="line">$ git checkout ticket-12</span></code></pre></td></tr></table></div></figure>


<p>Alors qu’on peut parfaitement faire :</p>

<figure class="code"><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
</pre></td><td class="code"><pre><code class=""><span class="line">git checkout -b ticket-12</span></code></pre></td></tr></table></div></figure>


<p>Bien sûr, rien ne vous empêche d’indiquer en 2ème argument la base de la branche (par défaut le <code>HEAD</code>, comme pratiquement tout le temps).</p>

<p>Notez un cas où le <code>-b</code> est superflu car <code>checkout</code> est super-malin : lorsque vous voulez commencer à bosser sur la branche <code>super-feature</code> présente sur le <em>remote</em>, et que vous n’avez donc pas encore de branche locale qui la tracke.  Si vous faites tout bêtement :</p>

<figure class="code"><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
</pre></td><td class="code"><pre><code class=""><span class="line">git checkout super-feature</span></code></pre></td></tr></table></div></figure>


<p>Git va se rendre compte que la branche locale <code>super-feature</code> n’existe pas, mais qu’il y en a une sur le <em>remote</em> par défaut, et va <strong>automatiquement</strong> faire l’équivalent de ceci (en supposant que votre <em>remote</em> par défaut s’appelle <code>origin</code>, ce qui est généralement le cas) :</p>

<figure class="code"><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
</pre></td><td class="code"><pre><code class=""><span class="line">git checkout -b -t super-feature origin/super-feature</span></code></pre></td></tr></table></div></figure>


<p>Pourquoi faire long quand on peut faire court, hein ?</p>

<p>Parlons à présent de <code>-v</code> et sa version agressive, <code>-vv</code>.</p>

<p>Vous avez sans doute l’habitude  lister vos branches locales avec un simple <code>git branch</code> :</p>

<p><img src="http://www.git-attitude.fr/images/n-options-git-branch-simple.png"/></p>

<p>Saviez-vous qu’en fait vous pouvez avoir un peu plus d’infos (SHA, écart avec l’upstream éventuel, première ligne du message de commit) avec <code>-v</code> ?</p>

<p><img src="http://www.git-attitude.fr/images/n-options-git-branch-v.png"/></p>

<p>Et même vérifier l’upstream tracké en poussant jusqu’à <code>-vv</code> ?</p>

<p><img src="http://www.git-attitude.fr/images/n-options-git-branch-vv.png"/></p>

<p>Sympa, non ?  En temps normal le nom de l’upstream est en bleu, mais ça pue sur du noir, j’ai donc configuré <code>color.branch.upstream</code> à <code>cyan</code>…</p>

<h2>Une aide plus facile d’emploi avec <code>-w</code></h2>

<p>Les <em>manpages</em>, c’est bien, facile, et ça marche de partout, y compris au travers d’une connexion SSH. … Enfin, presque partout.  Certains environnements ne gèrent pas très bien <code>man</code>.</p>

<p>Et puis, la grande majorité des gens ne savent pas utiliser interactivement <code>man</code> autrement que pour défiler à travers le contenu (et encore).  Le recours aux hyperliens, notamment, est extrêmement rare.</p>

<p><strong>Git publie toutes ses documentations</strong> non seulement au format <code>man</code> mais aussi <strong>au format HTML</strong>, un type de document utilisable par tous, liens compris.  Pour y recourir, il suffit d’utiliser l’option <code>-w</code> de <code>git help</code> :</p>

<p>Sur certains environnements (l’installeur officiel pour Windows, je crois), c’est d’ailleurs le mode <strong>automatique</strong>.  Si vous souhaitez vous aussi fonctionner comme ça, vous pouvez configurer ça dans votre config globale :</p>

<figure class="code"><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
</pre></td><td class="code"><pre><code class=""><span class="line">git config --global help.format html</span></code></pre></td></tr></table></div></figure>


<p>Si le navigateur employé par défaut ne vous convient pas (déterminé par <code>git-web--browse</code>, qui en connaît une tera-chiée), vous pouvez configurer <code>help.browser</code> avec la commande ou le chemin d’invocation de celui que vous désirez.</p>

<p>Notez que ces contenus restent locaux (fichiers HTML installés par Git), vous n’avez donc même <strong>pas besoin d’une connexion à Internet</strong>.</p>

<h2>Stasher plus efficacement avec <code>save</code> et <code>-u</code></h2>

<p><strong>Trop de gens ne connaissent pas <a href="http://git-scm.com/docs/git-stash"><code>git stash</code></a></strong>, et ceux qui le connaissent ont rarement fait l’effort d’en lire la doc pour apprendre à <strong>bien l’utiliser</strong>.  Je vois la plupart des gens juste faire un <code>git stash</code> en amont, et un <code>git stash apply</code> en aval.</p>

<p>Le comportement par défaut de <code>stash</code> est assez pénible :</p>

<ol>
<li>Il laisse de côté les <em>untracked</em>, ce qui est rarement désirable</li>
<li>Il fournit un message pourri par défaut (genre <code>WIP on master: &lt;message du dernier commit connu&gt;</code>)</li>
</ol>


<p>Un tel message n’a aucun intérêt, vu qu’il <strong>ne précise pas la nature du <em>work in progress</em></strong> (WIP), ce qui rend difficile l’identification du stash par la suite.</p>

<p>Pour pouvoir régler ces deux points, il suffit de recourir explicitement à la <strong>sous-commande <code>save</code>, avec l’option <code>-u</code></strong> d’une part (pour intégrer les <em>untracked</em>), et le message voulu d’autre part.  Par exemple :</p>

<figure class="code"><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
</pre></td><td class="code"><pre><code class=""><span class="line">git stash save -u 'Début du refactoring Bootstrap 3'</span></code></pre></td></tr></table></div></figure>


<p>On se retrouve alors sur un <em>clean tree</em> : le contenu du HEAD, plus les éventuels fichiers ignorés.</p>

<p>Il est tout aussi important de <strong>bien récupérer le stash obtenu</strong> lorsqu’on aura fini de traiter l’urgence qui nous l’a fait créer à la base.</p>

<p>Beaucoup de gens font <code>git stash apply</code>, ce qui est un peu dommage parce qu’en cas de succès (pas de conflit avec le nouvel état de base), le stash continue d’exister, ce qui pourrait nous tenter plus tard de le réappliquer, à tort évidemment.</p>

<p>Ce qu’il faudrait en fait, c’est que si l’<code>apply</code> marche, on exécute automatiquement le <code>drop</code>.  Et c’est précisément le sens de <code>git stash pop</code>.</p>

<p>Contrairement à ce que son nom peut laisser penser, celui-ci ne se limite pas au dernier stash existant : on peut en préciser un autre (par exemple <code>git stash pop stash@{2}</code>).  Ceci dit, je trouve que <strong>les stashes ne devraient exister que très peu de temps</strong>, pour contourner une situation complexe ou un obstacle, et qu’on ne devrait donc pratiquement jamais en avoir plusieurs.</p>

<p>Par ailleurs, il est dommage que <code>apply</code> et <code>pop</code> ne restaurent pas, par défaut, l’état du <em>stage</em>.  Celui-ci est pourtant bien sauvegardé par <code>save</code>, toujours par défaut.  Alors pourquoi ne pas le restaurer d’office, dans la mesure où le <em>stage</em> est une info importante ?</p>

<p>C’est parce que la commande est peureuse : si vous avez modifié un fichier figurant dans le <em>stage</em> du stash, elle serait contrainte de fusionner les deux et de re-<em>stager</em> le résultat.  Git fait ce genre de choses d’habitude (suite à un <code>merge</code>, <code>rebase</code> ou <code>cherry-pick</code> par exemple, avec ou sans assistance de <code>rerere</code>), mais sur ce coup, il le refusera.</p>

<p>Du coup, pour ne pas avoir à hurler <em>en cas de fusion portant sur le stage</em>, par défaut, il ne restaure pas ce dernier : les snapshots concernés redeviennent de simples modifs locales sur le disque.</p>

<p>Ça m’exaspère au plus haut point, de sorte que <strong>je lui demande toujours explicitement de tenter la restauration du <em>stage</em></strong>.  Au pire, si j’ai en effet un souci car j’aurais modifié un fichier par ailleurs <em>staged</em>, je retirerai mon option et reconstruirai mon <em>stage</em> manuellement.  J’utilise donc systématiquement l’option <code>--index</code> :</p>

<p>Il n’existe hélas pas de variable de configuration pour systématiser ce comportement…</p>

<p>Ah oui, et puis il est <strong>très facile d’oublier qu’on a stashé</strong> : pensez à avoir <a href="http://www.git-attitude.fr/2013/05/22/prompt-git-qui-dechire/">un bon prompt</a> avec la variable d’environnement <code>GIT_PS1_SHOWSTASHSTATE</code> activée.</p>

<h2>La précédente branche active : <code>-</code></h2>

<p>Vous savez peut-être que dans les shells usuels, <code>cd -</code> vous permet de revenir au répertoire précédent (du coup, l’utilisation répétée de la commande bascule entre deux répertoires).</p>

<p>Au fil des versions, Git a successivement appris cette astuce pour les commandes <code>checkout</code>, <code>merge</code>, <code>cherry-pick</code> et <code>rebase</code>.  Voici un schéma classique :</p>

<figure class="code"><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
<span class="line-number">2</span>
</pre></td><td class="code"><pre><code class=""><span class="line">(topic)  $ git checkout master
</span><span class="line">(master) $ git merge -</span></code></pre></td></tr></table></div></figure>


<p>Encore un autre :</p>

<figure class="code"><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
<span class="line-number">2</span>
<span class="line-number">3</span>
</pre></td><td class="code"><pre><code class=""><span class="line">(2-3-stable +) $ git ci -m "fix: no more conflict with Underscore.  Fixes #217."
</span><span class="line">(2-3-stable)   $ git checkout master
</span><span class="line">(master)       $ git cherry-pick -</span></code></pre></td></tr></table></div></figure>


<p>Si vous êtes sur une version qui n’a pas cette astuce pour votre commande (<a href="http://www.git-attitude.fr/2014/08/28/git-evolutions-recentes/">vérifiez</a>), ou que vous en avez besoin pour une commande qui ne gère pas encore la syntaxe, n’oubliez pas que c’est juste du sucre syntaxique pour la syntaxe bien plus ancienne et universelle <code>@{-1}</code>.</p>

<h2>Annuler le merge en cours sans effacer les modifs locales</h2>

<p><strong>Git n’a pas besoin d’un <em>clean tree</em></strong> pour autoriser une fusion : il lui suffit que le <em>working directory</em> (WD) soit <a href="http://git-scm.com/docs/git-merge#_pre_merge_checks"><em>in good order</em></a>, c’est-à-dire, principalement, que les fichiers qui vont être touchés par la fusion ne fassent pas actuellement l’objet de modifs locales, et d’autre part qu’il n’y ait pas déjà un <em>stage</em> en cours (pour éviter à terme un commit fourre-tout).</p>

<p>Du coup, lorsque vous vous retrouvez à arbitrer des conflits de fusion, vous pouvez avoir sous la main des modifs issues de conflits de fusion, mais aussi d’autres qui étaient là avant de lancer <code>git merge</code>.</p>

<p>Si vous abandonnez la fusion faute d’informations suffisantes, il peut être tentant de faire un bon vieux <code>git reset --hard</code>.  Ce serait en fait <strong>dangereux</strong>, parce que ça purgerait aussi les modifs locales qui étaient là <strong>avant le <code>merge</code></strong>.</p>

<p>C’est pourquoi il est possible de faire un <code>git reset --merge</code> (ou la syntaxe plus récente <code>git merge --abort</code>, qui ressemble à son équivalent pour le <code>rebase</code>) : vous ne réinitialiserez alors <strong>que ce qui vient de la fusion</strong>.</p>

<figure class="code"><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
<span class="line-number">2</span>
<span class="line-number">3</span>
<span class="line-number">4</span>
<span class="line-number">5</span>
<span class="line-number">6</span>
<span class="line-number">7</span>
</pre></td><td class="code"><pre><code class=""><span class="line">(master *) $ git merge cool-feature
</span><span class="line">Auto-merging index.html
</span><span class="line">CONFLICT (content): Merge conflict in index.html
</span><span class="line">Automatic merge failed; fix conflicts and then commit the result.
</span><span class="line">
</span><span class="line">(master *+) $ git merge --abort
</span><span class="line">(master *)  $</span></code></pre></td></tr></table></div></figure>


<p>C’est en fait même faisable <strong>après une fusion réussie</strong> !</p>

<figure class="code"><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
<span class="line-number">2</span>
<span class="line-number">3</span>
<span class="line-number">4</span>
<span class="line-number">5</span>
<span class="line-number">6</span>
<span class="line-number">7</span>
<span class="line-number">8</span>
</pre></td><td class="code"><pre><code class=""><span class="line">(master *) $ git merge cool-feature
</span><span class="line">Auto-merging index.html
</span><span class="line">Merge made by the `recursive` strategy.
</span><span class="line">[afbd564] Merged `cool-feature` branch
</span><span class="line">
</span><span class="line">(master *) $ git reset --merge ORIG_HEAD
</span><span class="line">[ac3489b] Original master tip
</span><span class="line">(master *) $</span></code></pre></td></tr></table></div></figure>


<p>La classe, non ?  Ça nous évite du stash, tout ça…</p>

<h2>Éviter de flinguer un <em>merge</em> en le <em>rebasant</em></h2>

<p>Le <em>rebasing</em> est décidément <a href="http://www.git-attitude.fr/2014/05/04/bien-utiliser-git-merge-et-rebase/">un super couteau suisse</a>, avec juste un petit risque potentiel : par défaut, <strong>lorsqu’il <em>rebase</em> un commit de fusion, il <em>inline</em> la fusion</strong>.  En gros, on court le risque suivant (ici illustré au travers d’un <code>pull</code> qui <em>rebase</em> au lieu de fusionner) :</p>

<p><img src="http://www.git-attitude.fr/images/git-merge-rebase-inlining.png" title="&quot;Un exemple d’inlining fortuit d’un commit de fusion lors du rebase&quot;" alt="&quot;Un exemple d’inlining fortuit d’un commit de fusion lors du rebase&quot;"/></p>

<p>Pour éviter ça, il suffit de demander au <em>rebase</em> de <strong>préserver les fusions</strong>, à l’aide de l’option <code>-p</code>, ou sa version longue <code>--preserve-merges</code>.</p>

<p><img src="http://www.git-attitude.fr/images/git-merge-rebase-preserved.png" title="&quot;Un rebase qui préserve les fusions&quot;" alt="&quot;Un rebase qui préserve les fusions&quot;"/></p>

<p>Évitez toutefois de combiner avec des réordonnancements en mode interactif, <a href="http://git-scm.com/docs/git-rebase#_bugs">ça donnerait des résultats inattendus</a>.</p>

<h2>Être un ninja avec le rebase <code>-i</code></h2>

<p>C’est en mode interactif que le <em>rebase</em> donne <strong>toute sa mesure</strong>, le cas d’utilisation pluri-quotidien étant le réflexe de bonne pratique qui consiste à toujours évaluer son historique local avant un <em>push</em>, et à le <a href="http://www.git-attitude.fr/2014/05/04/bien-utiliser-git-merge-et-rebase/#nettoyer-son%20historique%20local%20avant%20envoi">mettre au propre</a> si besoin, à l’aide d’un <code>git rebase -i @{u}</code>.</p>

<p>Purge de commits à somme nulle (genre l’original et son <em>revert</em>), réordonnancement de commits, fusion de tentatives successives pour un même correctif, réécriture des messages, découpe de commits fourre-tout…  <a href="http://www.git-attitude.fr/2014/05/04/bien-utiliser-git-merge-et-rebase/#nettoyer-son%20historique%20local%20avant%20envoi">Tout est possible</a>.</p>

<h2>Nettoyer sereinement avec <code>-i</code> et <code>-n</code></h2>

<p>La commande <code>git clean</code> est bien utile, mais <strong>potentiellement destructrice</strong> : en effet, elle impacte le <em>working directory</em> (WD) et, du coup, peut <strong>dégager des modifs locales qui n’ont jamais été committées</strong>, de sorte que si on a fait une bévue, Git ne pourra pas nous aider à récupérer ce travail !</p>

<p>Par défaut d’ailleurs, <code>git clean</code> est une <em>no-op</em>, car <code>clean.requireForce</code> est à <code>true</code>, il faut donc au moins un <code>git clean -f</code> pour envoyer la purée ; et même ça, ça n’entrera pas dans les sous-répertoires (sauf <code>-d</code>), et laissera de côté les fichiers ignorés (sauf <code>-x</code>).</p>

<p>Vous avez toutefois la possibilité de <strong>voir à quoi ressemblera votre <code>clean</code> sans aucun risque</strong>, avec la traditionnelle option <code>-n</code> (ou <code>--dry-run</code>), présente sur de nombreuses commandes Git, qui listera les fichiers et dossiers qui seraient supprimés si elle n’était pas là, mais se bornera, justement, à les lister.</p>

<p>Et pour passer à l’action, vous pouvez vous <strong>tranquilliser encore</strong> en utilisant <code>-i</code> (le traditionnel <code>--interactive</code>), qui vous proposera une sorte de shell listant les candidats à la suppression, et permet de les filtrer, de confirmer pour chaque, etc.  Plus d’angoisse !</p>

<h2>Caler l’upstream à la volée avec <code>-u</code></h2>

<p>Vous <em>pushez</em> une branche pour la première fois ?  Il vous faudra toujours expliciter le <em>remote</em> voulu (même si vous n’en avez qu’un de défini) et la branche souhaitée (même si elle est active), par exemple <code>git push origin topic</code>.</p>

<p>Toutefois, ce simple <code>push</code> <strong>ne met pas en place le <em>tracking</em></strong> : la configuration locale de votre dépôt ne retient pas l’association entre votre branche locale <code>topic</code> et son <em>upstream</em>, ici la branche <code>topic</code> du <em>remote</em> <code>origin</code>.</p>

<p>Pour y remédier, vous pouvez à tout moment refaire ce <code>push</code> en précisant <code>-u</code> (ou <code>--set-upstream</code>), qui calera cette configuration avant le <em>push</em> proprement dit, afin que vous n’ayez plus besoin de préciser tout ça lors des futurs <code>push</code> et <code>pull</code>.</p>

<figure class="code"><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
</pre></td><td class="code"><pre><code class=""><span class="line">git push -u origin topic</span></code></pre></td></tr></table></div></figure>


<p>En interne, ça fait un <code>git branch --set-upstream-to=origin/topic topic</code>, ce que vous pouvez donc faire à la main si vous souhaitez le <strong>configurer sans <em>pusher</em> tout de suite</strong> pour autant.</p>

<p>Pour rappel, vous n’êtes pas obligés de connecter votre branche locale à une branche distante homonyme : si les noms doivent différer, il vous suffira de connecter l’<em>upstream</em> en utilisant la <strong>syntaxe totale de <code>push</code></strong>, par exemple ici pour avoir une branche distante <code>christophe-topic</code> pour ma branche locale <code>topic</code> :</p>

<figure class="code"><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
</pre></td><td class="code"><pre><code class=""><span class="line">git push -u origin topic:christophe-topic</span></code></pre></td></tr></table></div></figure>


<p>C’est d’ailleurs pour cette raison que la syntaxe de <strong>suppression de branche distante</strong> est :</p>

<figure class="code"><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
</pre></td><td class="code"><pre><code class=""><span class="line">git push origin :old-remote-branch</span></code></pre></td></tr></table></div></figure>


<p>Elle revient à dire « remplacer la branche distante <code>old-remote-branch</code> par rien du tout »… donc la supprimer.</p>

<h2>Ah tiens, 36 en fait</h2>

<p>Eh oui, 36 options finalement (sans compter les variations courte/longue).  Et 4 variables de configuration (plus une d’environnement).  Que voulez-vous, on est comme ça chez Git Attitude, on donne, on donne…</p>

<h2>Envie d’en savoir plus ?</h2>

<p>Déjà, vous pouvez voir par le menu <a href="http://www.git-attitude.fr/2014/08/28/git-evolutions-recentes/">tout ce qui est apparu d’intéressant dans Git depuis la 1.7</a>, grâce à notre article dédié.  Vous pouvez aussi aller fouiller dans le détail <a href="http://www.git-attitude.fr/2014/05/04/bien-utiliser-git-merge-et-rebase/">la bonne utilisation de merge vs. rebase</a>, si ce n’est déjà fait.</p>

<p>Mais surtout, notre formation <a href="http://www.git-attitude.fr/git-total/">Git Total</a> explore ces thématiques et bien d’autres pour vous donner une compréhension en profondeur de Git, vous transformant en experts en seulement 3 jours, pour un tarif très raisonnable !  Disponible en inter-entreprises tous les 2 mois (hors été) et en intra-entreprises <a href="http://www.git-attitude.fr/demander-une-intra-ou-custom">sur demande</a>.</p>
