Quatres astuces pour maitriser ses rebases

Publié le 23 juin 2021 par Willow Barraco | git

Cet article est publié sous licence CC BY-NC-SA

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 du c2392943 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 ?

HubSpot Video

 

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 "%s\n" "Your rebase caused no code changes, good for you" >&2
		return
	fi

	printf "%s\n" "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 "%s\n" "Cool! All changes kept" >&2
	else
		printf "%s\n\n" "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 !


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