La plupart des patchs échangés en revue de code, dans les rapports de bug ou par e-mail sont au format diff unifié (unified diff). git diff, diff -u et le « raw diff » de GitHub utilisent tous ce format. Il paraît chargé de symboles au premier abord, mais ses règles sont simples.
Vue d'ensemble
@@ -1,5 +1,6 @@ def greet(name):- print("Hello " + name)+ message = f"Hello, {name}!"+ print(message) def main(): greet("world")
De haut en bas se succèdent les en-têtes étendus de git, les en-têtes de fichier (---, +++) et les hunks (blocs commençant par @@). Un fichier peut contenir plusieurs hunks, et un patch peut concerner plusieurs fichiers.
En-têtes de fichier : --- et +++
--- a/greet.pyest le fichier avant modification,+++ b/greet.pyle fichier après modification.- git ajoute
a/etb/devant les chemins. C'est pourquoi on retire le premier composant du chemin avec-p1lorsqu'on applique le patch avec la commandepatch. - Un fichier créé apparaît comme
--- /dev/null, un fichier supprimé comme+++ /dev/null. - GNU diff ajoute parfois l'heure de modification après le nom du fichier.
L'en-tête de hunk @@ -a,b +c,d @@
@@ -1,5 +1,6 @@ signifie que le hunk montre « 5 lignes à partir de la ligne 1 du fichier avant modification, et 6 lignes à partir de la ligne 1 du fichier après modification ».
- Le premier nombre est le numéro de la ligne de départ ; celui qui suit la virgule est le nombre de lignes couvertes par le hunk (lignes de contexte comprises).
- Quand ce nombre vaut 1, il est omis avec la virgule.
@@ -7 +7 @@est un hunk d'une seule ligne, la ligne 7. - Quand il vaut 0, ce côté ne contient aucune ligne.
@@ -3,0 +4,2 @@signifie que deux nouvelles lignes ont été insérées après la ligne 3 de l'original. Une comparaison avec un fichier vide donne-0,0. - Du texte comme un nom de fonction peut suivre le second
@@(@@ -40,7 +40,8 @@ def main():). C'est une indication sur la fonction qui contient le hunk ; elle est ignorée lors de l'application.
Comptons nous-mêmes : dans l'exemple ci-dessus, les lignes de contexte (qui commencent par une espace) sont au nombre de 4 : def greet(name):, une ligne vide, def main(): et greet("world"). Avant modification, on a 4 lignes de contexte plus 1 ligne -, soit 5 ; après modification, 4 lignes de contexte plus 2 lignes +, soit 6 : l'en-tête est donc -1,5 +1,6.
Les trois sortes de lignes du corps
| Premier caractère | Signification |
|---|---|
| Espace | Ligne de contexte, identique des deux côtés |
- | Ligne présente seulement avant la modification (suppression) |
+ | Ligne présente seulement après la modification (ajout) |
Par défaut, le contexte comprend 3 lignes avant et après chaque modification (modifiable, par exemple avec git diff -U5). Les lignes de contexte servent à retrouver le bon emplacement lors de l'application du patch. Même si les numéros de ligne sont un peu décalés, patch applique la modification à un autre endroit (offset) tant que le contexte correspond.
Une ligne corrigée apparaît comme une paire : une ligne - suivie d'une ligne +. Le format unifié n'a pas de symbole propre pour « modifié » : l'astuce consiste à lire en vis-à-vis le groupe de lignes - consécutives et le groupe de lignes +.
\ No newline at end of file
@@ -1,2 +1,2 @@ host=example.com-port=8080\ No newline at end of file+port=8080
Cette mention indique que la ligne juste au-dessus est la dernière du fichier et qu'elle ne se termine pas par un caractère de saut de ligne. Dans l'exemple, le contenu est le même : seul le fichier après modification a reçu un saut de ligne final. Pour aller plus loin, voir Caractères de fin de ligne et différences invisibles.
Les en-têtes étendus de git
Les lignes qui suivent diff --git a/chemin b/chemin sont des informations propres à git.
index 3b18e51..a6f4c2d 100644— le début des empreintes (hash) du contenu avant et après modification, et le mode du fichier (100644 pour un fichier ordinaire, 100755 pour un exécutable)new file mode 100644/deleted file mode 100644— création ou suppression d'un fichierold mode 100644/new mode 100755— seul le droit d'exécution a changé (peut apparaître sans hunk de contenu)similarity index 90%,rename from ancien-chemin,rename to nouveau-chemin— renommage (le contenu est identique à 90 %)Binary files a/logo.png and b/logo.png differ— pour un fichier binaire, cette seule ligne remplace le contenu
Astuces pour la revue
- Regarder d'abord l'écart entre les nombres de lignes de l'en-tête de hunk donne une idée de l'ampleur de la modification.
-10,7 +10,30signifie 23 lignes de plus. - Pour écarter les hunks qui ne changent que des espaces, utilisez
git diff -w. C'est particulièrement utile pour un commit qui mélange nettoyage de l'indentation et vraies modifications (Options pour ignorer les espaces). - Pour voir ce qui a changé à l'intérieur d'une ligne, utilisez
git diff --word-diff, ou collez le diff dans un outil qui surligne l'intérieur des lignes, comme ce site. - Avant d'appliquer un patch, vérifiez qu'il s'applique proprement avec
git apply --check fichier.patch. Hors d'un dépôt git, utilisezpatch -p1 --dry-run < fichier.patch.
Essayer avec cet outil
La vue unifiée du comparateur de texte présente, dans le même ordre que ce format, le groupe - puis le groupe +, et surligne en plus les mots modifiés dans la ligne. Le bouton patch de la barre de résultats copie le texte au format diff unifié standard, et le bouton d'enregistrement voisin télécharge un fichier diff.patch. Un patch créé avec toutes les options d'ignorance désactivées peut être appliqué à l'original avec patch -p1 ou git apply.