A la lliçó anterior vam fer servir git rebase per a una sola cosa: canviar la base d'una branca. Però el mecanisme que hi ha a sota —descompondre una branca en una llista de commits i tornar-los a aplicar un a un— permet molt més si algú et deixa editar aquesta llista abans que s'executi.

Això és git rebase -i. Git t'obre un fitxer de text amb els commits que reaplicarà, un per línia, i espera que tu decideixis què fer amb cadascun: deixar-lo tal qual, canviar-li el missatge, unir-lo amb l'anterior, partir-lo en dos, moure'l de lloc o esborrar-lo. Quan deses i tanques, Git executa les teves instruccions al peu de la lletra.

És l'eina que converteix una branca real —amb els seus dubtes, les seves marxes enrere i els seus wip— en una seqüència de commits que una altra persona pot llegir i entendre. En Bruno la necessita ara mateix: la seva branca funcionalitat/exportacio-csv té sis commits, tres dels quals es diuen apany, wip 2 i wip 3, i vol publicar-la perquè la revisin.

I amb ella torna, més forta que mai, la regla d'or: el rebase interactiu reescriu commits, així que només s'aplica a feina que encara no has publicat (o a una branca publicada que només fas servir tu i de la qual pots avisar).

Contingut

  1. Què és la llista de tasques
  2. Les ordres disponibles
  3. El punt de partida: la branca d'en Bruno
  4. Sessió completa: de sis commits a dos
  5. squash davant de fixup
  6. Reordenar i eliminar commits
  7. Dividir un commit en dos amb edit
  8. Canviar només un missatge amb reword
  9. --autosquash: commit --fixup i commit --squash
  10. --exec: validar cada commit
  11. break, label, reset i merge
  12. Què fer si et perds a mitges

  1. Què és la llista de tasques

En llançar un rebase interactiu, Git prepara la llista de commits que reaplicarà i te l'obre al teu editor:

git rebase -i main
pick b1c4f80 Comencar exportacio
pick 3d6e9a1 Afegir boto d'exportar
pick c8f2b47 mes coses del csv
pick 2a7f4c1 apany
pick 9e3b8d6 wip 2
pick 5c1a9f2 wip 3

# Rebase e91d4a8..5c1a9f2 onto e91d4a8 (6 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
#                    commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later)
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.

Tres detalls que cal assimilar abans de tocar res:

  • L'ordre és cronològic ascendent: el commit més antic a dalt. És al revés que git log, que mostra el més recent primer. Aquesta inversió és la primera font d'errors de qui comença.
  • Les línies s'executen de dalt a baix. Reordenar línies reordena commits.
  • Esborrar una línia equival a drop: aquell commit desapareix. Git t'ho avisa en majúscules per alguna cosa.

I una precisió sobre l'argument: git rebase -i main significa «reaplica sobre main tot el que hi ha a la meva branca i no a main», igual que a la lliçó anterior. Si només vols tocar els últims N commits de la branca on ets, sense canviar de base, es fa servir:

git rebase -i HEAD~4     # els 4 últims commits
git rebase -i 8d4e6b2    # tot el que hi ha DESPRÉS d'aquell commit

Compte amb això últim: git rebase -i <commit> no inclou <commit> a la llista, perquè <commit> és la base. Per tocar els últims quatre commits, la base és el cinquè començant pel final: HEAD~4.

Si el teu editor per defecte no és el que vols, es canvia amb git config --global core.editor "code --wait" (o nano, vim, el que facis servir). Es va explicar a la lliçó 01-05, i aquí ho agrairàs.

  1. Les ordres disponibles

Ordre Abreviatura Què fa
pick p Aplica el commit tal qual. És el valor per defecte
reword r Aplica el commit i obre l'editor per canviar-ne el missatge
edit e Aplica el commit i s'atura perquè el modifiquis (o el parteixis)
squash s Uneix el commit amb l'anterior i obre l'editor per combinar els dos missatges
fixup f Uneix el commit amb l'anterior i descarta el seu missatge
fixup -C Uneix amb l'anterior però conserva aquest missatge en lloc de l'anterior
drop d Elimina el commit. Equival a esborrar la línia
exec x Executa una ordre de shell en aquell punt de la seqüència
break b S'atura allà sense fer res, perquè hi facis un cop d'ull
label l Posa un nom temporal al HEAD actual
reset t Torna a un label previ
merge m Crea un commit de fusió amb un label

Les quatre primeres cobreixen el 95 % de l'ús real. drop i exec apareixen de tant en tant. break és una comoditat. label, reset i merge només apareixen quan fas servir --rebase-merges per reconstruir un historial amb fusions, i no les necessitarem al dia a dia: n'hi ha prou de saber què signifiquen si te les trobes.

Un apunt sobre squash i fixup: uneixen amb el commit anterior de la llista, és a dir, amb la línia de sobre. Per això mai no poden ser la primera línia (no hi ha res amb què unir-se) i per això l'ordre importa tant.

  1. El punt de partida: la branca d'en Bruno

En Bruno porta una setmana amb l'exportació de tasques a CSV. La branca funciona, però l'historial és un desastre:

git switch funcionalitat/exportacio-csv
git log --oneline main..HEAD
5c1a9f2 wip 3
9e3b8d6 wip 2
2a7f4c1 apany
c8f2b47 mes coses del csv
3d6e9a1 Afegir boto d'exportar
b1c4f80 Comencar exportacio

Amb --stat es veu què hi ha realment a cadascun:

git log --oneline --stat main..HEAD
5c1a9f2 wip 3
 app.js | 4 ++--
9e3b8d6 wip 2
 app.js | 7 +++++--
2a7f4c1 apany
 app.js | 2 +-
c8f2b47 mes coses del csv
 app.js | 18 ++++++++++++++++++
3d6e9a1 Afegir boto d'exportar
 index.html |  3 +++
 estils.css | 8 ++++++++
b1c4f80 Comencar exportacio
 app.js | 6 ++++++

Llegit amb calma, la branca fa dues coses independents:

  1. Una funció que converteix la llista de tasques a CSV i la descarrega (app.js): commits 1, 3, 4, 5 i 6.
  2. Un botó a la interfície que la invoca (index.html + estils.css): commit 2.

L'objectiu d'en Bruno és acabar amb exactament aquests dos commits, en aquest ordre, amb missatges que expliquin què fan. I l'important: el contingut final de la branca no canviarà ni una línia. Un rebase interactiu ben fet reorganitza com s'explica la història, no el que hi ha al final.

# L'empremta de l'estat final, per comprovar-ho després
git rev-parse HEAD^{tree}
7f4b2c9e1d8a3b6f5c2e9d4a7b1f8c3e6d5a2b9f

Aquest és el hash de l'arbre de la punta de la branca. En acabar el rebase ha de ser el mateix. És el millor control de qualitat que existeix per a aquesta operació.

  1. Sessió completa: de sis commits a dos

En Bruno llança el rebase interactiu:

git rebase -i main

I edita la llista que li apareix. El primer és reordenar per ajuntar el que va junt, i després decidir l'ordre de cada línia. Així queda el fitxer abans de desar:

pick b1c4f80 Comencar exportacio
squash c8f2b47 mes coses del csv
fixup 2a7f4c1 apany
fixup 9e3b8d6 wip 2
fixup 5c1a9f2 wip 3
pick 3d6e9a1 Afegir boto d'exportar

Llegeix-ho de dalt a baix com una recepta:

  1. pick b1c4f80 — aplica el primer commit tal qual. Serà la base del commit final de l'exportació.
  2. squash c8f2b47 — uneix-lo a l'anterior, i deixa'm combinar els missatges.
  3. fixup 2a7f4c1 — uneix-lo a l'anterior i llença el seu missatge (apany no aporta res).
  4. fixup 9e3b8d6 — igual amb wip 2.
  5. fixup 5c1a9f2 — igual amb wip 3.
  6. pick 3d6e9a1 — el commit del botó, mogut al final, es queda com està de moment.

En desar i tancar, Git comença a executar. Al pas del squash s'atura i obre l'editor amb els dos missatges:

# This is a combination of 2 commits.
# This is the 1st commit message:

Comencar exportacio

# This is the commit message #2:

mes coses del csv

# Please enter the commit message for your changes.

En Bruno esborra tot això i escriu un missatge decent:

Afegeix l'exportació de tasques a CSV

Genera un fitxer CSV amb el títol, l'estat i la data de creació
de cada tasca i el descarrega des del navegador sense crides al servidor.

Les cometes i els punts i comes del títol s'escapen segons l'RFC 4180.

Desa, tanca, i el rebase continua. Els tres fixup no pregunten res. Al final:

Successfully rebased and updated refs/heads/funcionalitat/exportacio-csv.
git log --oneline main..HEAD
a3f9c14 Afegir boto d'exportar
d72e6b8 Afegeix l'exportació de tasques a CSV

Falta polir el missatge del segon, però això ho veiem a l'apartat 8. Abans, la comprovació que havíem promès:

git rev-parse HEAD^{tree}
7f4b2c9e1d8a3b6f5c2e9d4a7b1f8c3e6d5a2b9f

Idèntic al d'abans del rebase. El contingut de la branca no ha canviat; només la seva història. Aquesta és la garantia que converteix aquesta operació en una cosa raonable i no en una temeritat.

gitGraph
   commit id: "e91d4a8"
   branch abans
   commit id: "b1c4f80"
   commit id: "3d6e9a1"
   commit id: "c8f2b47"
   commit id: "2a7f4c1"
   commit id: "9e3b8d6"
   commit id: "5c1a9f2"
   checkout main
   branch despres
   commit id: "d72e6b8"
   commit id: "a3f9c14"

  1. squash davant de fixup

Totes dues uneixen un commit amb l'anterior. L'única diferència és al missatge, però determina quina fer servir en cada cas:

squash fixup
Contingut resultant El dels dos commits combinat El dels dos commits combinat
Missatge resultant Els dos missatges junts, editables Només el del commit anterior
Obre l'editor? No
Quan fer-lo servir Els dos commits aporten informació al missatge El segon era un apany, una errada, un wip
Variant fixup -C conserva el missatge del segon

A la pràctica: si el missatge del commit que absorbeixes val alguna cosa, squash; si és escombraria (apany, wip, ara sí, punt i coma), fixup. I com que un squash de cinc commits t'obliga a netejar a mà cinc missatges a l'editor, tan bon punt en tinguis tres o més sol sortir a compte fer servir fixup a tots i escriure el missatge una sola vegada amb reword.

  1. Reordenar i eliminar commits

Ja has vist reordenar: a la sessió d'en Bruno, moure 3d6e9a1 de la segona posició a l'última va bastar per agrupar la feina per temes.

Eliminar és igual de directe: posa drop davant de la línia, o esborra-la sencera. Les dues formes són equivalents; drop és preferible perquè deixa constància visible de la intenció i evita l'esborrat accidental d'una línia:

pick b1c4f80 Comencar exportacio
drop 4c8f1a5 Afegir console.log per depurar
pick 3d6e9a1 Afegir boto d'exportar

Dos advertiments que valen per a les dues operacions:

  • Reordenar i eliminar poden provocar conflictes. Si el commit c8f2b47 modificava una línia que b1c4f80 acabava de crear i els separes, el segon ja no troba el context que esperava. Es resol com qualsevol conflicte de rebase (lliçó 05-01): resoldre, git add, git rebase --continue. Recorda que les bandes ours/theirs continuen invertides.
  • Eliminar un commit del mig deixa la branca amb menys canvis, no necessàriament amb menys codi. Si fas drop del commit que creava una funció i deixes el que la crida, la branca compila malament. --exec (apartat 10) serveix precisament per detectar-ho.

  1. Dividir un commit en dos amb edit

És l'operació estrella del rebase interactiu i la que més impressiona la primera vegada. Suposem que en Bruno mira d72e6b8 i decideix que barreja dues coses: la funció que genera el CSV i la que dispara la descàrrega. El vol partir.

Pas 1: marcar el commit amb edit.

git rebase -i main
edit d72e6b8 Afegeix l'exportació de tasques a CSV
pick a3f9c14 Afegir boto d'exportar

Git aplica el commit i s'atura:

Stopped at d72e6b8...  Afegeix l'exportació de tasques a CSV
You can amend the commit now, with

  git commit --amend

Once you are satisfied with your changes, run

  git rebase --continue

Pas 2: desfer el commit però conservar-ne els canvis.

git reset HEAD^
Unstaged changes after reset:
M	app.js

Això és el que fa la màgia: git reset sense opcions (equival a --mixed) mou HEAD un commit enrere i deixa tots els canvis al directori de treball, sense preparar. El commit ha desaparegut; el seu contingut continua al fitxer. És l'única aparició de reset en aquest mòdul; els seus tres modes i el seu ús com a eina de desfer s'estudien a fons a la lliçó 09-02.

git status --short
 M app.js

Pas 3: preparar i confirmar per parts. Aquí entra git add -p, que vas aprendre a la lliçó 02-04: preparar només uns fragments del fitxer.

git add -p app.js

En Bruno accepta amb y els fragments de la funció tasquesACSV i rebutja amb n els de descarregaFitxer. Després:

git commit -m "Afegeix la conversió de tasques a format CSV"
[detached HEAD 6e1d9f3] Afegeix la conversió de tasques a format CSV
 1 file changed, 14 insertions(+)

I la resta:

git add app.js
git commit -m "Descarrega el CSV generat des del navegador"
[detached HEAD b8c2a70] Descarrega el CSV generat des del navegador
 1 file changed, 9 insertions(+)

Pas 4: continuar.

git rebase --continue
Successfully rebased and updated refs/heads/funcionalitat/exportacio-csv.
git log --oneline main..HEAD
f04b7e2 Afegir boto d'exportar
b8c2a70 Descarrega el CSV generat des del navegador
6e1d9f3 Afegeix la conversió de tasques a format CSV

Un commit s'ha convertit en dos, sense perdre una línia. Fixa't que el commit posterior (a3f9c14f04b7e2) també ha canviat de hash: en inserir commits abans que ell, el seu pare és un altre, i ja saps què implica això.

Truc: si en fer git reset HEAD^ t'adones que el repartiment no és net (els canvis estan entremesclats a la mateixa funció), pots editar els fitxers a mà abans de cada commit. No estàs obligat que cada meitat sigui un subconjunt exacte de l'original; només que el resultat final sigui el mateix. Verifica-ho amb el truc del rev-parse HEAD^{tree}.

  1. Canviar només un missatge amb reword

Al commit del botó li falta l'accent i la majúscula. Per canviar només el missatge, sense tocar res més:

git rebase -i main
pick 6e1d9f3 Afegeix la conversió de tasques a format CSV
pick b8c2a70 Descarrega el CSV generat des del navegador
reword f04b7e2 Afegir boto d'exportar

Git aplica els dos primers, i al tercer obre l'editor amb el missatge actual. En Bruno el canvia per:

Afegeix el botó d'exportar a la barra d'accions

Desa, tanca, i llest:

git log --oneline main..HEAD
9c7e5a1 Afegeix el botó d'exportar a la barra d'accions
b8c2a70 Descarrega el CSV generat des del navegador
6e1d9f3 Afegeix la conversió de tasques a format CSV

Tres commits, tres missatges que expliquen què fa cadascun, i una branca a punt per publicar i perquè la revisin.

Nota per al cas més freqüent: si el commit que vols reescriure és l'últim, no cal cap rebase interactiu. git commit --amend (lliçó 02-04) fa el mateix amb menys cerimònia. reword és per als que queden més enrere.

Què és un bon missatge de commit —l'imperatiu, la línia d'assumpte, el cos, els peus— és el tema de la lliçó 08-01. Aquí només ens ocupa el mecanisme de canviar-lo.

  1. --autosquash: commit --fixup i commit --squash

Hi ha un flux molt més còmode que anar marcant fixup a mà, i consisteix a decidir-ho en el moment de fer el commit, quan encara tens fresc quin commit corregeixes.

Imagina que en Bruno està treballant i descobreix una errada al commit 6e1d9f3, que va fer fa dos dies. En lloc de crear un commit anomenat apany:

# Corregeix l'errada a app.js
git add app.js
git commit --fixup 6e1d9f3
[funcionalitat/exportacio-csv 2b8f4d1] fixup! Afegeix la conversió de tasques a format CSV

Git ha creat un commit normal, però amb un missatge especial: fixup! seguit de l'assumpte del commit que corregeix. Aquest prefix és una marca que Git sap interpretar.

git log --oneline main..HEAD
2b8f4d1 fixup! Afegeix la conversió de tasques a format CSV
9c7e5a1 Afegeix el botó d'exportar a la barra d'accions
b8c2a70 Descarrega el CSV generat des del navegador
6e1d9f3 Afegeix la conversió de tasques a format CSV

I ara, la part bonica:

git rebase -i --autosquash main

La llista que apareix ja ve ordenada i amb les ordres posades:

pick 6e1d9f3 Afegeix la conversió de tasques a format CSV
fixup 2b8f4d1 fixup! Afegeix la conversió de tasques a format CSV
pick b8c2a70 Descarrega el CSV generat des del navegador
pick 9c7e5a1 Afegeix el botó d'exportar a la barra d'accions

En Bruno només ha de desar i tancar sense tocar res. Git ha mogut el commit de correcció just darrere del seu destí i l'ha marcat com a fixup.

Ordre Prefix del missatge Al rebase es converteix en Efecte sobre el missatge
git commit --fixup <sha> fixup! fixup Es descarta el de l'apany
git commit --squash <sha> squash! squash S'obre l'editor per combinar-los
git commit --fixup=reword:<sha> amend! fixup -C Només canvia el missatge de l'original

Per no haver-se de recordar de --autosquash cada vegada:

git config --global rebase.autoSquash true

I un avís: --autosquash aparella els commits pel text de l'assumpte, no pel hash. Si dos commits de la branca tenen exactament el mateix assumpte, l'aparellament pot ser ambigu. A la pràctica gairebé mai no passa, i sempre pots revisar la llista abans de desar (que és justament el que l'editor t'està demanant).

Aquest flux —treballar, corregir amb --fixup, i aplanar-ho tot amb un rebase -i --autosquash just abans de publicar— és probablement la manera més productiva de fer servir Git cada dia. T'allibera de pensar en l'historial mentre programes, i et deixa netejar-lo de cop al final.

  1. --exec: validar cada commit

Un historial net que no compila no val de res. Quan reordenes, uneixes o divideixes commits, és fàcil que algun d'intermedi quedi trencat: el que crida una funció que encara no existeix, el que importa un mòdul que arriba dos commits després.

--exec executa una ordre després de cada commit reaplicat i atura el rebase si l'ordre retorna un codi d'error diferent de zero:

git rebase -i --exec "npm test" main

Git insereix automàticament una línia exec després de cada pick:

pick 6e1d9f3 Afegeix la conversió de tasques a format CSV
exec npm test
pick b8c2a70 Descarrega el CSV generat des del navegador
exec npm test
pick 9c7e5a1 Afegeix el botó d'exportar a la barra d'accions
exec npm test

Si el segon npm test falla:

Executing: npm test
...
warning: execution failed: npm test
You can fix the problem, and then run

  git rebase --continue

El rebase es queda aturat en aquell punt, amb el repositori exactament en l'estat d'aquell commit. Pots arreglar el que calgui, fer git commit --amend, i continuar.

També pots escriure les línies exec a mà on t'interessi, en lloc de a tots els commits. I no ha de ser per força una bateria de tests; qualsevol comprovació ràpida val:

pick 6e1d9f3 Afegeix la conversió de tasques a format CSV
exec node --check app.js
pick b8c2a70 Descarrega el CSV generat des del navegador
exec node --check app.js
exec grep -rn "console.log" app.js && exit 1 || exit 0

L'última línia és un petit guardià: falla si queda algun console.log oblidat. Automatitzar aquesta mena de comprovacions de manera permanent, perquè s'executin soles a cada commit, és el tema dels hooks (lliçó 06-01).

  1. break, label, reset i merge

Tres ordres menys freqüents, però convé reconèixer-les.

break atura el rebase en aquell punt sense fer res més. És una pausa deliberada:

pick 6e1d9f3 Afegeix la conversió de tasques a format CSV
break
pick b8c2a70 Descarrega el CSV generat des del navegador

Útil per mirar l'estat del projecte enmig de la seqüència, executar alguna cosa a mà o simplement respirar. Es continua amb git rebase --continue.

label, reset i merge només apareixen quan fas servir git rebase -i --rebase-merges, que reconstrueix un historial conservant-ne els commits de fusió en lloc d'aplanar-lo. Git genera llavors una llista amb aquest aspecte:

label onto

reset onto
pick 6e1d9f3 Afegeix la conversió de tasques a format CSV
label exportacio

reset onto
pick 9c7e5a1 Afegeix el botó d'exportar a la barra d'accions
label boto

reset onto
merge -C 4a7d2f8 exportacio # Fusiona l'exportació
merge -C 8e3b6c1 boto # Fusiona el botó

Es llegeix així: label X desa un marcador al punt actual, reset X torna a aquell marcador, i merge -C <sha> X crea un commit de fusió amb la branca marcada com a X, reutilitzant el missatge del commit <sha>. És un llenguatge petit per descriure un graf. No el necessitaràs gairebé mai; n'hi ha prou de no espantar-se si apareix.

  1. Què fer si et perds a mitges

Un rebase interactiu llarg es pot convertir en un laberint: quatre conflictes, un edit a mig fer i la sensació de no saber en quin punt ets. Sortides, de menys a més dràstica:

1. Preguntar on ets. git status durant un rebase interactiu és extraordinàriament informatiu:

git status
interactive rebase in progress; onto e91d4a8
Last commands done (3 commands done):
   pick 6e1d9f3 Afegeix la conversió de tasques a format CSV
   fixup 2b8f4d1 fixup! Afegeix la conversió de tasques a format CSV
Next commands to do (2 remaining commands):
   pick b8c2a70 Descarrega el CSV generat des del navegador
   pick 9c7e5a1 Afegeix el botó d'exportar a la barra d'accions
You are currently rebasing branch 'funcionalitat/exportacio-csv' on 'e91d4a8'.

Et diu què has fet i què falta. La llista viva és, a més, a .git/rebase-merge/git-rebase-todo, i git rebase --edit-todo te l'obre per canviar el que queda per fer sense avortar. És la sortida elegant quan t'adones a mitges que vas planificar malament:

git rebase --edit-todo    # canvia les ordres pendents
git rebase --continue

2. Veure què està fallant ara.

git rebase --show-current-patch

Mostra el commit que s'està intentant aplicar, amb el seu diff complet. Molt útil quan el conflicte no té sentit a primera vista.

3. Avortar.

git rebase --abort

Desfà tot el rebase i retorna la branca al seu estat original, amb els hashos originals. És segur, és instantani i no deixa rastre. Davant del dubte, avorta: replanificar la llista amb el cap fred costa dos minuts; desembolicar un rebase mal portat, molt més.

4. Si ja has acabat i el resultat és un desastre. Aquí és on entra la xarxa de seguretat: els commits originals continuen a la base de dades d'objectes i el reflog guarda on era la teva branca abans de començar. El procediment complet per tornar enrere és el tema de la lliçó 09-04. I el reflex barat de sempre, que evita haver-lo de fer servir:

git branch copia-abans-del-rebase
git rebase -i main
# Malament? -> git reset --hard copia-abans-del-rebase

Errors Habituals i Consells

Error 1: llegir la llista com si fos git log. A l'editor, el commit més antic és a dalt. Marcar squash a la línia equivocada per aquesta inversió és l'error número u.

Error 2: posar squash o fixup a la primera línia. No hi ha cap commit anterior amb què unir-se i el rebase falla abans de començar. La primera línia sempre és pick, reword, edit o drop.

Error 3: esborrar una línia sense voler. Aquell commit desapareix. Fes servir drop explícit quan vulguis eliminar, i si esborres una línia per accident, tanca l'editor sense desar: Git avorta el rebase.

Error 4: rebasar interactivament una branca ja publicada i compartida. La regla d'or no té excepcions còmodes. Si altres persones treballen sobre aquella branca, no la reescriguis.

Error 5: fer git commit en lloc de git rebase --continue després de resoldre un conflicte. Llevat del flux d'edit (on sí que confirmes tu), qui crea el commit és el rebase.

Error 6: oblidar que el rebase executa els hooks. Si tens un pre-commit lent, un rebase de vint commits l'executa vint vegades. --no-verify el desactiva, amb la responsabilitat que això implica.

Error 7: aplanar-ho tot en un únic commit gegant «perquè quedi net». Un commit de 900 línies que diu «Afegeix l'exportació» és tan inútil com sis commits anomenats wip. L'objectiu és que cada commit sigui un canvi complet i comprensible, no que n'hi hagi un de sol.

Consell 1: comprova l'arbre abans i després. git rev-parse HEAD^{tree} ha de coincidir si la teva intenció era només reorganitzar. Si no coincideix, has perdut o duplicat alguna cosa.

Consell 2: adopta --fixup des d'avui. Tan bon punt tinguis el reflex de corregir amb git commit --fixup <sha> en lloc d'amb un commit anomenat apany, la neteja final es torna trivial. Activa rebase.autoSquash true.

Consell 3: fes rebases interactius petits i freqüents. Netejar cinc commits és còmode; netejar-ne quaranta, no. Un rebase -i al final de cada jornada de feina en una branca és un costum excel·lent.

Consell 4: fes servir --exec quan reordenis o divideixis. És l'única manera barata de saber que cap commit intermedi no ha quedat trencat.

Consell 5: revisa la llista abans de desar, sempre. Aquell editor obert no és un tràmit: és la teva última oportunitat de veure el pla complet abans que s'executi.

Quan convé netejar i quant —si l'equip exigeix historial lineal, si s'aixafa cada branca en un commit en integrar-la, si es prohibeix reescriure— no és una decisió tècnica sinó de política d'equip, i es tracta a la lliçó 08-02: Mantenint un Historial Net.

Exercicis

Exercici 1: la neteja completa

Crea un repositori amb una branca main (un commit) i una branca funcionalitat/informe amb aquests cinc commits, en aquest ordre:

  1. Comencar l'informe (crea informe.js amb una funció)
  2. Afegir estils de l'informe (crea informe.css)
  3. mes coses (amplia informe.js)
  4. wip (toca informe.js)
  5. apany (toca informe.js)

Amb un sol git rebase -i, deixa la branca amb dos commits: un amb tot el d'informe.js i un missatge decent, i un altre amb els estils. Demostra amb git rev-parse HEAD^{tree} que el contingut final no ha canviat.

Exercici 2: dividir un commit

Partint del resultat de l'exercici 1, divideix el commit d'informe.js en dos: un amb la generació de dades i un altre amb el pintat. Fes servir edit, git reset HEAD^ i git add -p.

Exercici 3: --fixup i --exec

En una branca nova amb tres commits:

  1. Introdueix a propòsit un error de sintaxi al segon commit.
  2. Continua treballant i fes un tercer commit normal.
  3. Corregeix l'error amb git commit --fixup <sha del segon>.
  4. Aplica'l amb git rebase -i --autosquash.
  5. Torna a llançar un rebase amb --exec "node --check fitxer.js" i comprova que ara els tres commits passen la validació.

Solucions

Solució 1:

mkdir /tmp/ex-rebase-i && cd /tmp/ex-rebase-i
git init -b main
echo "# Projecte" > README.md && git add . && git commit -m "Base"

git switch -c funcionalitat/informe
echo "function generaInforme() {}" > informe.js && git add . && git commit -m "Comencar l'informe"
echo ".informe { margin: 1rem; }" > informe.css && git add . && git commit -m "Afegir estils de l'informe"
echo "function dadesInforme() {}" >> informe.js && git commit -am "mes coses"
echo "// pendent" >> informe.js && git commit -am "wip"
echo "function pintaInforme() {}" >> informe.js && git commit -am "apany"

git rev-parse HEAD^{tree}
3e8b1f7c4a9d25b6e8f1c3a7d942b6e5f8c1a3d7    (anota-ho)
git rebase -i main

Llista editada (els commits d'informe.js junts a dalt, els estils al final):

pick 4a1c8e3 Comencar l'informe
fixup 9d2f7b5 mes coses
fixup 1e6a4c8 wip
fixup 7b3d9f2 apany
pick 5c8e1a4 Afegir estils de l'informe

Després, una segona passada per al missatge (o marca reword a la primera línia des del principi):

git rebase -i main
reword 8f2c5d1 Comencar l'informe
pick 3a9e7b4 Afegir estils de l'informe

Missatge nou: Afegeix la generació i el pintat de l'informe.

git log --oneline main..HEAD
git rev-parse HEAD^{tree}
c14b8e7 Afegir estils de l'informe
6d3f2a9 Afegeix la generació i el pintat de l'informe

L'arbre coincideix amb l'anotat: només ha canviat la història.

Solució 2:

git rebase -i main
edit 6d3f2a9 Afegeix la generació i el pintat de l'informe
pick c14b8e7 Afegir estils de l'informe
git reset HEAD^
git status --short
?? informe.js

(El fitxer era nou en aquell commit, així que apareix sense seguiment. Si ja existís, sortiria com a M.)

# Preparar només la part de generació de dades
git add -p informe.js     # acceptar els fragments de generaInforme/dadesInforme
git commit -m "Afegeix la generació de les dades de l'informe"

git add informe.js
git commit -m "Afegeix el pintat de l'informe"

git rebase --continue
git log --oneline main..HEAD
9e7c3b1 Afegir estils de l'informe
2f8a5d6 Afegeix el pintat de l'informe
b41e9c7 Afegeix la generació de les dades de l'informe

Si git add -p no ofereix fragments separables perquè el fitxer és nou, fes servir git add -N informe.js primer: registra el fitxer a l'índex com a buit i permet trossejar-ne el contingut.

Solució 3:

mkdir /tmp/ex-autosquash && cd /tmp/ex-autosquash
git init -b main
echo "const a = 1;" > app.js && git add . && git commit -m "Base"
git switch -c proves

echo "const b = 2;" >> app.js && git commit -am "Afegeix b"
echo "const c = ;" >> app.js && git commit -am "Afegeix c"     # error de sintaxi
echo "const d = 4;" >> app.js && git commit -am "Afegeix d"

git log --oneline main..HEAD
7c1a4e8 Afegeix d
3f9b2d6 Afegeix c
d82e5a1 Afegeix b
# 3. Corregir l'error apuntant al commit culpable
sed -i 's/const c = ;/const c = 3;/' app.js
git add app.js
git commit --fixup 3f9b2d6
[proves 4e8c1b9] fixup! Afegeix c
# 4. Aplicar-lo
git rebase -i --autosquash main

La llista ja ve preparada; n'hi ha prou de desar:

pick d82e5a1 Afegeix b
pick 3f9b2d6 Afegeix c
fixup 4e8c1b9 fixup! Afegeix c
pick 7c1a4e8 Afegeix d
git log --oneline main..HEAD
a92f6c3 Afegeix d
5d1e8b7 Afegeix c
d82e5a1 Afegeix b
# 5. Validar tots els commits
git rebase --exec "node --check app.js" main
Executing: node --check app.js
Executing: node --check app.js
Executing: node --check app.js
Successfully rebased and updated refs/heads/proves.

Si ho haguessis executat abans de l'--autosquash, el segon exec hauria fallat i el rebase s'hauria aturat al commit trencat.

Conclusió

El rebase interactiu és el taller de reparació de l'historial. El que hem après:

  • git rebase -i <base> obre una llista de tasques amb els commits que es reaplicaran, del més antic al més recent, i executa el que hi escriguis de dalt a baix.
  • Les quatre ordres que faràs servir sempre són pick (deixar), reword (canviar el missatge), edit (aturar-se per modificar o dividir) i squash/fixup (unir amb l'anterior, conservant o descartant el missatge). drop elimina i exec valida.
  • Reordenar línies reordena commits i permet agrupar per temes el que es va fer desordenat; esborrar una línia elimina el commit.
  • Dividir un commit és edit + git reset HEAD^ + diversos git add -p i git commit + git rebase --continue. És l'operació més potent del conjunt.
  • git commit --fixup <sha> amb rebase -i --autosquash converteix la neteja en un tràmit: marques les correccions sobre la marxa i Git les col·loca sol. Activa-ho amb rebase.autoSquash true.
  • --exec executa una comprovació després de cada commit i atura el rebase si falla: l'única manera barata de garantir que cap commit intermedi no queda trencat.
  • Si et perds: git status diu on ets, git rebase --edit-todo replanifica el que queda, --show-current-patch ensenya què falla i --abort ho desfà tot sense rastre.
  • git rev-parse HEAD^{tree} abans i després és la millor verificació que has reorganitzat la història sense alterar-ne el contingut.
  • I sobretot: la regla d'or. Això es fa abans de publicar.

El que ve

El rebase i el rebase interactiu treballen sempre amb el conjunt de commits d'una branca. Però de vegades el que necessites és molt més quirúrgic: endur-te un commit concret d'un lloc a un altre, deixant la resta on és.

L'Ana ho necessitarà demà. Ha corregit a main una fallada que fa que el focus es perdi en esborrar una tasca, i resulta que la versió 1.x que continua desplegada al client té la mateixa fallada i viu en una branca de manteniment a part. No vol fusionar main sencera allà: vol aquell commit i res més.

Per a això existeix git cherry-pick, i el veiem a la lliçó 05-03: Cherry-Picking de Confirmacions.

Dominant Git: De Principiant a Avançat

Mòdul 1: Introducció a Git

Mòdul 2: Operacions Bàsiques de Git

Mòdul 3: Branques i Fusió

Mòdul 4: Treballant amb Repositoris Remots

Mòdul 5: Operacions Avançades de Git

Mòdul 6: Eines i Tècniques de Git

Mòdul 7: Estratègies de Col·laboració i Flux de Treball

Mòdul 8: Bones Pràctiques i Consells de Git

Mòdul 9: Resolució de Problemes i Depuració

Mòdul 10: Git al Món Real

© Copyright 2026. Tots els drets reservats