Article écrit par Eddie Barraco
Lorsqu’on est à l’aise avec Git, il est courant d’user et d’abuser du rebase interactif.
Pour rappel, git rebase -i ACOMMIT
vas nous permettre d’éditer, renommer, fusionner vos commits. Il devient rapidement l’outil principal des développeurs une fois pris en main. Cependant il est également la bête noire qui risque de réduire à néant notre travail.
Voici quelques astuces pour nous rendre plus confiant lors de nos rebases.
Comment résoudre efficacement un conflit
Avant toute chose, nous devons absolument changer une configuration Git. Ajoutons ceci dans notre ~/.gitconfig
si ce n’est pas déjà fait.
[merge] conflictstyle = diff3
Ce style de conflit va beaucoup nous aider et devrait selon moi être ainsi par défaut. En cas de conflit, voici ce que nous verrons :
<<<<<<< HEAD THIS IS USEFUL ||||||| merged common ancestors This is useful ======= This is really useful >>>>>>> c2392943.....
Lorsque nous rencontrons ceci :
- Tout d’abord, localisons lequel du
HEAD
ou duc2392943
est notre changement, celui qui est de notre côté. - Ensuite, nous devons comprendre ce que l’autre côté a apporté par rapport à la partie centrale.
- Maintenant, nous devons reporter cette évolution de notre côté si c’est toujours nécessaire !
- Supprimons tout le reste
Concrètement :
- J’ai passé le texte en majuscule
- L’autre côté a ajouté le mot «really»
- Je dois donc ajouter «REALLY» de mon côté
- Je nettoie le reste
THIS IS REALLY USEFUL
Attachons notre Git hook
Il est commun de devoir gérer des conflits lorsque on fait des rebases (ou tout autre manipulation Git d’ailleurs !). Je nous propose ici d’installer une ceinture de sécurité qui va nous empêcher de passer à travers le pare-brise au premier dérapage.
L’idée est d’être informé si nous avons modifié quelque chose lors de notre gestion du conflit.
Une petite vidéo vaut mieux que toutes les explications ?
Ajoutons ces deux scripts dans notre dossier .git/hooks
avec les droits d’exécution. On peut également les ajouter globalement grâce à ~/.gitconfig
ainsi :
[init] templatedir = ~/.gitconfig.d/template
Notons que cela n’impactera pas les repos Git existant !
Ajoutons-les ensuite dans ce dossier ~/.gitconfig.d/template/hooks/
Fichier pre-rebase
:
#!/usr/bin/env sh export GIT_DIR="$(git rev-parse --absolute-git-dir)" rebase_in_progress_file="$GIT_DIR/rebase_in_progress" git rev-parse HEAD > "$rebase_in_progress_file"
Celui-ci vraiment simple. On stocke la référence Git au départ d’une réécriture.
Fichier post-rewrite
:
#!/usr/bin/env sh export GIT_DIR="$(git rev-parse --absolute-git-dir)" rebase_in_progress_file="$GIT_DIR/rebase_in_progress" confirm_rebase_diff() { old_ref="$1" new_ref="$2" if [ -z "$(git diff "$old_ref..$new_ref")" ]; then printf "%sn" "Your rebase caused no code changes, good for you" >&2 return fi printf "%sn" "Your rebase resulted in code changes" >&2 git diff "$old_ref..$new_ref" >&2 printf "%s" "Do you want to keep the changes? [Y/n] " >&2 read -r reply < /dev/tty if [ -z "$reply" ] || [ "$reply" = "y" ]; then printf "%sn" "Cool! All changes kept" >&2 else printf "%snn" "Discarding changes..." >&2 git reset --hard "$old_ref" fi } if [ -f "$rebase_in_progress_file" ] && [ "$1" = "rebase" ]; then old_sha="$(cat "$rebase_in_progress_file")" confirm_rebase_diff "$old_sha" "HEAD" rm "$rebase_in_progress_file" fi
On vient comparer la différence entre la référence Git que nous avons précédemment stocké et celle à la fin.
Plusieurs choix s’offrent à nous si c’est le cas :
- Oui c’est normal : Une méthode est passée chronologiquement avant une autre ou bien nous avons dû adapter une bricole.
- Non ce n’est pas ce que je voulais faire : Annule tout Git, je t’en conjure !
Si la réécriture n’a engendré aucune différence dans le code, tant mieux ! Rien ne s’affiche et la vie continue
Git alaide
Vous avez fait une bêtise. Ne mentez pas, je le vois à vos yeux horrifiés qui scrutent l’open-space. Vous cherchez de l’aide.
Pas de panique. Git intègre une mécanique d’historique (cela ne devrait vraiment pas nous étonner).
$ git reflog
Git reflog vas nous donner la liste des dernières actions qu’il a réalisées. Ce ne sont que d’autres références Git vers des états antérieurs. Elles sont sous la forme HEAD@{0}
.
Si nous avons ravagé notre travail à cause d’un rebase, cherchons le reflog qui commence par rebase (start)
. Nous pouvons aussi cibler le reflog de notre précédent commit. Gardons juste en tête qu’un reflog représente l’état après l’action.
Si nous voulons revenir avant le début du rebase dans l’exemple suivant, faisons par exemple :
$ git reflog 2d0272ac0 (HEAD) HEAD@{0}: commit: fix: Add a missing shipping type 8fef1ba6d HEAD@{1}: commit: fix: add a missing technical link between garment listing and its ordered products 6b8df6666 HEAD@{2}: commit: fix: Add a required index to make Odoo sync faster 54bfc6977 HEAD@{3}: checkout: moving from feature/ops to 54bfc6977 fa529fdea HEAD@{4}: rebase (continue) (finish): returning to refs/heads/feature/ops fa529fdea HEAD@{5}: rebase (continue) (pick): WIP: feat: odoo sync customer bills ca827f90c HEAD@{6}: rebase (continue): WIP: feat: odoo sync bank account and entities 54bfc6977 HEAD@{7}: rebase (continue) (pick): feat(ops): add ops config eea26d0ed HEAD@{8}: rebase (continue): feat: Odoo add a reset command fe252ae98 HEAD@{9}: rebase (start): checkout perso/production ... $ git reset --hard HEAD@{9}^
Au final, nous pourrions tout aussi bien écrire sur un bout de papier la référence de notre dernier commit avant la catastrophe
car cela revient au même.
Dans le doute, poussez
Une fois que nous avons travaillé vos commits, nous pouvons enfin montrer au monde nos talents d’arboriste.
Éditer l’historique Git nous oblige maintenant à pousser nos commits avec force et conviction. Difficile de savoir quelles seront les conséquences de l’autre côté du réseau.
Pourtant Git nous offre un moyen simple d’en déterminer les issues.
$ git push origin HEAD:oopsi/fixing-typo -fn
Notons le paramètre -n
(«n» pour dry-run ¯_(ツ)_/¯
) adossé au -f
. En réponse à cette commande, le repo distant va nous donner ceci :
To git.synbioz.com:~ebarraco/hello-world.git + cfc907c4...d575e61a d575e61a -> oopsi/fixing-typo (forced update)
Ce qui nous intéresse ici est cfc907c4…d575e61a
. Cela signifie que le push fais passer la référence oopsi/fixing-typo
du commit cfc907c4
au commit d575e61a
. À l’aide de notre souris et de nos petits doigts, utilisons cet intervalle et retirons un point
$ git diff cfc907c4..d575e61a
Nous avons là la différence du code après notre futur push. Si nous n’avons fait que regrouper des commits, la commande ne doit rien renvoyer. Ou alors peut être que quelqu’un d’autre a ajouté des commits ?
Quelques mots pour la fin
Certains utilisent git rerere
comme un outil pour simplifier la résolution de conflits. Je nous le déconseille vivement ! Bien au contraire, je souhaite nous pousser à garder un œil très attentif sur les impacts qu’a Git sur notre code. Vérifions les différences, nos commits, nos rebases et les impacts de nos push. Usons et abusons du git log -p
et du git show <git-ref>
pour lister et afficher dans le détail chacun de nos commits. Et ne soyons pas radins sur les descriptions de ceux-ci !