Commits

Git add -p : indexer seulement une partie d’un fichier

Git add patch : indexez seulement une partie d’un fichier avec git add -p, vérifiez ce qui est indexé, puis sélectionnez des blocs dans GitMini.

Sommaire

Séparer deux changements dans le même fichier

Le mode patch de Git, lancé avec git add -p (forme longue git add --patch), n’indexe qu’une partie d’un fichier. C’est l’outil des jours où vous corrigez deux choses sans rapport dans le même fichier et voulez un commit pour chacune. Si l’index est nouveau pour vous, commencez par le guide de git add et du staging.

Dans le dépôt orbit-api, vous avez modifié src/search.ts à deux endroits : buildIndex ignore désormais les jetons vides, et search masque les projets archivés. Demandez le diff à Git :

git diff
diff --git a/src/search.ts b/src/search.ts
index 24c85be..87aea95 100644
--- a/src/search.ts
+++ b/src/search.ts
@@ -8,6 +8,7 @@ export function buildIndex(projects: Project[]): Map<string, Project[]> {
   const index = new Map<string, Project[]>();
   for (const project of projects) {
     for (const token of project.name.toLowerCase().split(/\s+/)) {
+      if (token === "") continue;
       const bucket = index.get(token) ?? [];
       bucket.push(project);
       index.set(token, bucket);
@@ -23,5 +24,7 @@ export function search(index: Map<string, Project[]>, query: string): Project[]
       hits.set(project.id, project);
     }
   }
-  return [...hits.values()].sort((a, b) => a.name.localeCompare(b.name));
+  return [...hits.values()]
+    .filter((hit) => !hit.archived)
+    .sort((a, b) => a.name.localeCompare(b.name));
 }

Git a découpé le diff en deux blocs, appelés « hunks » en anglais. Un bloc regroupe des modifications voisines avec quelques lignes inchangées autour, et chacun commence par un en-tête encadré de marqueurs @@. Ici, le premier bloc est la modification de buildIndex et le second celle de search. Les blocs sont l’unité que git add -p vous propose.

Sélectionner les blocs avec git add -p

Lancez la commande sur le fichier. Git affiche un bloc à la fois et demande quoi en faire :

git add -p src/search.ts
diff --git a/src/search.ts b/src/search.ts
index 24c85be..87aea95 100644
--- a/src/search.ts
+++ b/src/search.ts
@@ -8,6 +8,7 @@ export function buildIndex(projects: Project[]): Map<string, Project[]> {
   const index = new Map<string, Project[]>();
   for (const project of projects) {
     for (const token of project.name.toLowerCase().split(/\s+/)) {
+      if (token === "") continue;
       const bucket = index.get(token) ?? [];
       bucket.push(project);
       index.set(token, bucket);
Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? y
@@ -23,5 +24,7 @@ export function search(index: Map<string, Project[]>, query: string): Project[]
       hits.set(project.id, project);
     }
   }
-  return [...hits.values()].sort((a, b) => a.name.localeCompare(b.name));
+  return [...hits.values()]
+    .filter((hit) => !hit.archived)
+    .sort((a, b) => a.name.localeCompare(b.name));
 }
Stage this hunk [y,n,q,a,d,K,g,/,e,?]? n

Les réponses saisies ici sont y pour le premier bloc et n pour le second. Cette transcription vient de Git 2.23. Les versions récentes, vérifiées avec Git 2.54, font précéder le prompt d’un compteur et proposent quelques touches de plus, comme dans (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? ; les choix et l’index obtenu sont les mêmes. Les touches que vous utiliserez le plus :

  • y indexe le bloc, n le laisse de côté.
  • q quitte sans indexer le bloc courant ni les suivants.
  • a indexe le bloc courant et tous les blocs suivants du fichier, tandis que d ignore le bloc courant et tous les suivants.
  • s découpe le bloc courant en blocs plus petits, quand il contient des modifications séparées par des lignes inchangées.
  • e permet de modifier le bloc à la main.
  • ? affiche l’aide.

Le prompt ne liste que les touches disponibles pour le bloc courant, ce qui explique l’absence de s ci-dessus : chacun de ces blocs ne contient qu’une seule modification et ne peut pas être découpé. Sur un autre fichier, un bloc qui contient deux modifications proches propose s, et y répondre affiche Split into 2 hunks. avant de montrer la première moitié. Dans un script, printf 'y\nn\n' | git add -p src/search.ts fournit les deux mêmes réponses.

Vérifier les parties indexées et restantes

Le fichier a maintenant des changements à deux endroits :

git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   src/search.ts

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   src/search.ts

src/search.ts apparaît deux fois, une par emplacement. git diff --cached montre ce que contiendra le prochain commit, et ce n’est que le premier bloc :

git diff --cached
diff --git a/src/search.ts b/src/search.ts
index 24c85be..e6eaa83 100644
--- a/src/search.ts
+++ b/src/search.ts
@@ -8,6 +8,7 @@ export function buildIndex(projects: Project[]): Map<string, Project[]> {
   const index = new Map<string, Project[]>();
   for (const project of projects) {
     for (const token of project.name.toLowerCase().split(/\s+/)) {
+      if (token === "") continue;
       const bucket = index.get(token) ?? [];
       bucket.push(project);
       index.set(token, bucket);

Le guide de git diff --staged compare cette sortie à celle du simple git diff. Commitez, puis regardez ce qui reste :

git commit -m "Skip empty tokens when building the index"
git status -s
[main 5485416] Skip empty tokens when building the index
 1 file changed, 1 insertion(+)
 M src/search.ts

Le commit contient une seule ligne ajoutée. La modification de search est toujours dans votre répertoire de travail, listée comme modifiée et non indexée, prête pour son propre commit. Vos identifiants de commit seront différents de ceux affichés ici.

Faire la même sélection par blocs dans GitMini

GitMini propose le même choix sans le prompt. Cliquez sur src/search.ts dans la liste Unstaged : son diff s’ouvre au milieu de la fenêtre, avec une étiquette Unstaged et le nombre de lignes ajoutées et supprimées. Chaque bloc a son en-tête, comme @@ -8,7 +8,7 @@, et deux boutons : Stage hunk et Discard hunk. Les libellés de l’interface restent en anglais.

Diff de src/search.ts dans GitMini, ouvert depuis la liste Unstaged, avec deux blocs qui ont chacun un bouton Stage hunk et un bouton Discard hunk
Chaque bloc du diff Unstaged a ses propres boutons Stage hunk et Discard hunk.

Cliquez sur Stage hunk sur le premier bloc pour le placer dans l’index. Le fichier apparaît maintenant dans les deux listes, Staged et Unstaged, comme les deux sections de git status plus haut. Ouvrez-le depuis Staged et le même bloc affiche un bouton Unstage hunk, qui le retire de l’index. Le bouton de commit compte les fichiers : il affiche Commit 1 file même si un seul bloc de ce fichier est indexé.

L’unité, c’est le bloc. GitMini indexe, désindexe ou abandonne un bloc entier à la fois, tel qu’il est affiché, et les boutons de bloc sont disponibles sur les fichiers texte. Discard hunk retire le bloc de votre répertoire de travail et demande d’abord une confirmation : GitMini prévient que les modifications ne peuvent pas être restaurées depuis l’application. Quand il ne reste que le second bloc dans Unstaged, vous êtes dans le même état que la session de terminal ci-dessus.

Sources