Tancàvem el mòdul 4 amb un advertiment: a partir d'ara l'historial és compartit. L'Ana, en Bruno i la Carla treballen sobre el mateix repositori de git.exemple.cat, i cada push hi deixa commits que les altres dues persones poden baixar-se en qualsevol moment. Aquesta és exactament la circumstància que converteix git rebase en l'eina més útil i més temuda de Git.

git rebase serveix per a una cosa molt concreta: agafar una sèrie de commits i tornar-los a aplicar sobre una altra base. Serveix perquè una branca que va néixer fa tres dies torni a estar «al damunt» de main sense embrutar l'historial amb fusions; serveix per reordenar i netejar la teva feina abans d'ensenyar-la a ningú; i serveix per moure una branca que penjava del lloc equivocat. El que fa por d'ella és l'altra cara: no mou commits, els substitueix per uns de nous. I substituir commits que ja estan publicats és, com veurem, una manera força eficaç d'arruïnar la tarda als teus companys.

Aquesta lliçó explica el mecanisme amb prou detall perquè no hi hagi màgia: quins objectes es creen, quins hashos canvien, què es conserva, com es resolen els conflictes que apareixen a mitges i —sobretot— quan convé fer servir rebase i quan convé fer servir merge.

Contingut

  1. La regla d'or del mòdul
  2. Què fa git rebase realment
  3. Un rebase pas a pas a gestor-tasques
  4. Què es conserva i què canvia als commits reaplicats
  5. Rebase davant de merge
  6. git rebase --onto: moure un tram concret
  7. Conflictes durant un rebase: --continue, --skip, --abort
  8. La inversió d'ours i theirs
  9. Trencar la regla d'or: què passa exactament
  10. git pull --rebase
  11. rebase.autoStash i altres opcions còmodes
  12. La xarxa de seguretat

  1. La regla d'or del mòdul

Abans d'escriure ni una sola ordre, la regla que governa tot el mòdul 5:

No reescriguis historial que ja has publicat i que altres persones poden tenir descarregat.

Tot el que veurem a les lliçons 05-01, 05-02 i 05-03 crea commits nous que en substitueixen d'altres. Mentre aquests commits visquin només al teu disc, reescriure'ls és gratis: ningú més no els ha vist. Tan bon punt els envies a git.exemple.cat, deixen de ser teus: passen a formar part d'una realitat compartida, i canviar-los obliga els altres a refer la seva còpia.

La regla té una excepció molt acotada i molt coneguda: la teva pròpia branca de treball, publicada només per tenir-ne còpia o per a una revisió, que ningú més no fa servir com a base. És un cas legítim i habitual, però exigeix avisar i exigeix --force-with-lease (lliçó 04-05). Tornarem a la regla a cada apartat que reescrigui commits, fins que resulti carregós. Aquest és l'objectiu.

  1. Què fa git rebase realment

La frase que se sol repetir és «el rebase mou els teus commits al damunt d'una altra branca». És còmoda i és falsa, i la diferència importa.

Recorda el model de dades de la lliçó 01-04: un commit és un objecte immutable el hash SHA-1 del qual es calcula a partir de tot el seu contingut, i aquest contingut inclou el hash del seu pare. Per tant:

  • Si canvies el pare d'un commit, canvia el seu contingut.
  • Si canvia el seu contingut, canvia el seu hash.
  • Si canvia el seu hash, ja no és el mateix commit: és un altre objecte diferent.

Un commit no es pot «moure» igual que no es pot canviar el passat. El que fa git rebase és:

  1. Calcular la llista de commits que són a la teva branca i no a la nova base.
  2. Desar el canvi (el diff) que introdueix cadascun.
  3. Situar-se a la nova base.
  4. Aplicar aquests canvis un a un, creant un commit nou per cadascun.
  5. Moure la referència de branca a la punta de la cadena nova.

Els commits antics continuen existint a la base de dades d'objectes —els objectes de Git no s'esborren a l'instant—, però es queden sense cap branca que els assoleixi. Són, a efectes pràctics, història morta.

gitGraph
   commit id: "c5d9b1e"
   commit id: "b3f7c21"
   branch funcionalitat/etiquetes-color
   commit id: "7c2e5f9"
   commit id: "a4b1d83"
   checkout main
   commit id: "e91d4a8"

Aquest és el punt de partida: la Carla va crear la seva branca quan main era a b3f7c21, i mentre treballava l'Ana va publicar e91d4a8. Després del rebase:

gitGraph
   commit id: "c5d9b1e"
   commit id: "b3f7c21"
   commit id: "e91d4a8"
   branch funcionalitat/etiquetes-color
   commit id: "d5e8f31"
   commit id: "92c7a06"

Fixa't bé en els identificadors: 7c2e5f9 i a4b1d83 han desaparegut del dibuix, i al seu lloc hi ha d5e8f31 i 92c7a06. El contingut dels canvis és el mateix, els missatges són els mateixos, l'autor és el mateix. Els commits, no.

  1. Un rebase pas a pas a gestor-tasques

La Carla porta dos dies amb funcionalitat/etiquetes-color: vol que cada tasca pugui portar un color. Mentrestant, l'Ana ha publicat un canvi a main. La Carla vol posar-se al dia sense crear un commit de fusió.

# 1. Punt de partida: portar l'últim del servidor
git switch funcionalitat/etiquetes-color
git fetch origin
git log --oneline --graph --all -6
* e91d4a8 (origin/main, main) Extreu la creació de l'element de tasca a la seva funció
| * a4b1d83 (HEAD -> funcionalitat/etiquetes-color) Pinta l'etiqueta de color al llistat
| * 7c2e5f9 Afegeix el camp color al model de tasca
|/
* b3f7c21 Desa les tasques a localStorage
* c5d9b1e Documenta la instal·lació al README

La bifurcació és clara: dos commits per una banda, un per l'altra, i b3f7c21 com a ancestre comú. Comprovem quins commits es reaplicaran abans de tocar res:

# 2. Quins commits reaplicarà el rebase (els que són a la meva branca i no a main)
git log --oneline main..HEAD
a4b1d83 Pinta l'etiqueta de color al llistat
7c2e5f9 Afegeix el camp color al model de tasca

Aquesta és exactament la llista de què parlàvem: el rang main..HEAD que vas aprendre a la lliçó 02-06. Ara sí:

# 3. El rebase
git rebase main
Successfully rebased and updated refs/heads/funcionalitat/etiquetes-color.
# 4. El resultat
git log --oneline --graph --all -6
* 92c7a06 (HEAD -> funcionalitat/etiquetes-color) Pinta l'etiqueta de color al llistat
* d5e8f31 Afegeix el camp color al model de tasca
* e91d4a8 (origin/main, main) Extreu la creació de l'element de tasca a la seva funció
* b3f7c21 Desa les tasques a localStorage
* c5d9b1e Documenta la instal·lació al README

Ja no hi ha bifurcació: una sola línia recta. I els hashos dels dos commits de la Carla han canviat. Val la pena verificar amb git show que el canvi és idèntic:

# 5. El commit vell continua existint, encara que cap branca no l'assoleixi
git show --stat 7c2e5f9 | head -6
git show --stat d5e8f31 | head -6
commit 7c2e5f9...
Author: Carla Vidal <[email protected]>
Date:   Mon Jul 27 09:14:22 2026 +0200

    Afegeix el camp color al model de tasca
commit d5e8f31...
Author: Carla Vidal <[email protected]>
Date:   Mon Jul 27 09:14:22 2026 +0200

    Afegeix el camp color al model de tasca

Mateix autor, mateixa data d'autoria, mateix missatge, mateix diff. Objecte diferent.

Un detall important per al final: com que la branca de la Carla no estava publicada, aquí no hi ha res a negociar. Si ho estigués, el següent git push seria rebutjat per non-fast-forward (lliçó 04-05) i caldria fer servir --force-with-lease.

  1. Què es conserva i què canvia als commits reaplicats

Aquesta taula resol la majoria dels dubtes de qui comença:

Element del commit Es conserva després del rebase?
Missatge Sí (llevat que el canviïs amb -i)
Autor i correu (author)
Data d'autoria (author date)
Confirmador (committer) No: passes a ser-ho tu
Data de confirmació (committer date) No: s'hi posa la d'ara
Contingut del canvi (el diff) Sí, si no hi ha conflicte
Arbre resultant (tree) Depèn de la base: gairebé sempre canvia
Pare (parent) No: aquest és l'objectiu
Hash No

Dues conseqüències pràctiques:

  • git log mostra per defecte la data d'autoria, així que després d'un rebase l'historial continua ensenyant les dates originals. Si vols veure la de confirmació: git log --pretty=fuller.
  • Si al teu equip algú mira «qui va confirmar què», el rebase et posa a tu com a committer de commits aliens. No és cap problema, però convé saber-ho.

  1. Rebase davant de merge

Totes dues integren la feina d'una branca amb la d'una altra. La diferència no és tècnica sinó narrativa: quina història vols que expliqui el repositori.

Aspecte git merge git rebase
Què fa Crea un commit nou amb dos pares Crea commits nous, un per cada original
Historial resultant Bifurcat, amb nusos de fusió Lineal
Commits originals Es conserven intactes Se substitueixen (hashos nous)
Traçabilitat Reflecteix el que va passar de debò: qui va treballar en paral·lel i quan es va ajuntar Reflecteix una versió idealitzada: sembla que tot es va fer en ordre
Moment del conflicte Un de sol, en fusionar; es resol un cop Un per cada commit que xoqui; es pot repetir
Llegibilitat de git log Pitjor amb moltes branques curtes Millor: es llegeix de dalt a baix
git bisect (lliçó 06-02) Funciona, però amb nusos Més net
Seguretat sobre feina publicada Total: no reescriu res Perillosa: reescriu
Reversibilitat git revert -m 1 (lliçó 05-06) No s'aplica: cal revertir commit a commit

I el criteri, resumit en tres regles que sí que pots memoritzar:

  1. Rebase per al que encara és teu. La teva branca local, abans de publicar-la o abans de demanar-ne la integració: posa-la al dia sobre main amb rebase.
  2. Merge per ajuntar històries que ja són públiques. Integrar una branca de funcionalitat acabada a main és un fet que mereix quedar registrat; allà un merge (sovint --no-ff, lliçó 03-03) és l'opció honesta.
  3. Davant del dubte, merge. És l'opció que mai no destrueix res.

Hi ha equips que porten això a l'extrem en un sentit («historial lineal sempre») i d'altres en el contrari («no reescriure mai res»). Totes dues postures són defensables i les veurem amb nom propi al mòdul 7 (fluxos de treball) i a la lliçó 08-02, quan parlem de la política d'historial net. Aquí ens ocupa el mecanisme.

  1. git rebase --onto: moure un tram concret

git rebase <base> cobreix el 90 % dels casos, però té un supòsit implícit: que vols reaplicar tot el que hi ha entre l'ancestre comú i la teva branca. De vegades no és això el que vols.

Li va passar a la Carla. Va començar funcionalitat/ordre-per-data sense adonar-se que estava situada a funcionalitat/etiquetes-color en lloc de a main. Ara la seva branca nova arrossega els dos commits de les etiquetes de color, que no hi tenen res a veure.

gitGraph
   commit id: "e91d4a8"
   branch funcionalitat/etiquetes-color
   commit id: "d5e8f31"
   commit id: "92c7a06"
   branch funcionalitat/ordre-per-data
   commit id: "3f9a2c4"
   commit id: "6b1e7d5"

Vol que funcionalitat/ordre-per-data pengi directament de main (que és a e91d4a8) i contingui només 3f9a2c4 i 6b1e7d5. Per a això existeix la forma de tres arguments:

git rebase --onto <nova-base> <des-de> <fins-a>

La manera de llegir-la en veu alta és aquesta:

  • <fins-a> — la branca que vull moure (si l'omets, la branca actual).
  • <des-de> — el punt a partir del qual comencen els commits que vull endur-me. Aquest commit no s'inclou; funciona com el límit exclusiu d'un rang des-de..fins-a.
  • <nova-base> — on vull que quedin enganxats.

Aplicat al cas de la Carla:

# 1. Veure quins commits m'enduc (mateixa semàntica de rang)
git log --oneline funcionalitat/etiquetes-color..funcionalitat/ordre-per-data
6b1e7d5 Ordena les tasques per data de creació
3f9a2c4 Desa la data de creació de cada tasca
# 2. El rebase --onto
git rebase --onto main funcionalitat/etiquetes-color funcionalitat/ordre-per-data
Successfully rebased and updated refs/heads/funcionalitat/ordre-per-data.
gitGraph
   commit id: "e91d4a8"
   branch funcionalitat/etiquetes-color
   commit id: "d5e8f31"
   commit id: "92c7a06"
   checkout main
   branch funcionalitat/ordre-per-data
   commit id: "0c4f8a3"
   commit id: "5d2b9e7"

Els dos commits de la data s'han reaplicat sobre main amb hashos nous, i la branca de les etiquetes de color s'ha quedat on era, intacta.

Altres usos habituals de --onto, perquè reconeguis el patró:

# Treure els 3 primers commits d'una branca (començar a comptar 3 més enllà)
git rebase --onto main main~3 la-meva-branca

# Portar els commits d'una branca a una altra branca diferent
git rebase --onto correccio/urgent main funcionalitat/alguna-cosa

# Treure UN commit del mig: tot el que hi ha darrere seu, al damunt del seu pare
git rebase --onto 4e7f2a9 8b6d3c2 main

Aquest últim és un bon exercici mental: 8b6d3c2 és el commit que volem eliminar, 4e7f2a9 és el seu pare, i estem dient «agafa tot el que hi ha després de 8b6d3c2 i enganxa-ho directament sobre el seu pare». El rebase interactiu (lliçó 05-02) té una manera molt més còmoda de fer el mateix, però convé entendre que per sota és això.

  1. Conflictes durant un rebase

Un rebase aplica commits un a un, així que es pot aturar un cop per cada commit. La mecànica de resolució —els marcadors <<<<<<<, git status, editar, git add— és exactament la que vas aprendre a la lliçó 03-05; no la repetim. El que canvia és com se surt de l'embús.

Suposem que la Carla rebasa i el segon commit xoca:

git rebase main
Auto-merging app.js
CONFLICT (content): Merge conflict in app.js
error: could not apply 92c7a06... Pinta l'etiqueta de color al llistat
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the original branch: run "git rebase --abort".
Could not apply 92c7a06... Pinta l'etiqueta de color al llistat
git status
interactive rebase in progress; onto e91d4a8
Last command done (2 commands done):
   pick d5e8f31 Afegeix el camp color al model de tasca
   pick 92c7a06 Pinta l'etiqueta de color al llistat
No commands remaining.
You are currently rebasing branch 'funcionalitat/etiquetes-color' on 'e91d4a8'.
  (fix conflicts and then run "git rebase --continue")
  (use "git rebase --skip" to skip this patch)
  (use "git rebase --abort" to checkout the original branch)

Unmerged paths:
  (use "git restore --staged <file>..." to unstage)
  (use "git add <file>..." to mark resolution)
	both modified:   app.js

Aquest bloc de git status és el teu quadre de comandaments: et diu en quin commit de la llista vas i què queda. Les tres sortides:

Ordre Què fa Quan fer-la servir
git rebase --continue Confirma el commit amb la teva resolució i continua amb el següent El cas normal, després de resoldre i fer git add
git rebase --skip Descarta per complet el commit conflictiu i continua Quan aquell canvi ja és a la base i reaplicar-lo no aporta res
git rebase --abort Desfà tot el rebase i retorna la branca al seu estat original Quan t'has embolicat o el conflicte és pitjor del que esperaves

Tres advertiments importants:

  • --skip no vol dir «salta't el conflicte»: vol dir «llença aquest commit a les escombraries». Si el commit tenia canvis que no són enlloc més, els perds de la branca resultant. Fes-lo servir només quan estiguis segur que el seu contingut ja està aplicat.
  • --abort és completament segur. Retorna la branca exactament on era, amb els commits originals i els seus hashos originals. Quan dubtis, avorta i pensa amb el repositori en calma.
  • No facis git commit a mà durant un rebase. Després de resoldre i fer git add, és git rebase --continue qui crea el commit. Si fas git commit, --continue et dirà que no queda res per confirmar (i en versions antigues pot deixar la llista descompensada).

Un detall útil: si després de resoldre el conflicte el resultat és idèntic a la base (és a dir, el teu commit ja no aporta res), git rebase --continue ho detecta i et proposa saltar-lo. És normal i no indica cap problema.

  1. La inversió d'ours i theirs

Aquí hi ha el parany que confon tothom, veterans inclosos.

En una fusió normal (lliçó 03-05), ours és la teva branca —on ets— i theirs és la branca que portes. Intuïtiu.

En un rebase s'inverteix. I no és cap caprici: és la conseqüència lògica del mecanisme. Recorda que un rebase primer es col·loca a la nova base i després aplica els teus commits al damunt, un a un, com si fossin pedaços aliens. Per tant, a cada pas:

Banda En un merge En un rebase
ours / --ours / etapa :2: La teva branca actual La nova base (main, el que ja hi havia)
theirs / --theirs / etapa :3: La branca que fusiones El teu commit, el que s'està reaplicant
HEAD durant el conflicte La teva branca La base més els commits ja reaplicats

Dit d'una manera memorable: durant un rebase, «els seus» ets tu.

La conseqüència pràctica és directa. Si durant un rebase vols quedar-te amb la teva versió del fitxer:

# MALAMENT: això es queda amb la versió de main, no amb la teva
git checkout --ours app.js

# BÉ: durant un rebase, el teu commit és "theirs"
git checkout --theirs app.js
git add app.js
git rebase --continue

El mateix val per a -X ours / -X theirs quan els passes al rebase, i per als noms de les etapes de l'índex (:2: i :3:) que vas veure a 03-05. Si mai dubtes, no ho endevinis: mira el contingut abans de decidir.

git show :2:app.js | head -20    # "ours" = la base
git show :3:app.js | head -20    # "theirs" = el teu commit

Un consell que estalvia molts disgustos: configura l'estil zdiff3 (lliçó 03-05) també aquí. Veure l'ancestre comú dins del marcador fa l'orientació molt menys dependent de recordar qui és qui:

git config --global merge.conflictStyle zdiff3

I si el mateix conflicte es repeteix commit rere commit dins del mateix rebase, existeix git rerere (reuse recorded resolution), que memoritza com vas resoldre un conflicte i l'aplica sol la propera vegada que aparegui idèntic. Es va esmentar a 03-05 i la seva configuració se surt d'aquesta lliçó.

  1. Trencar la regla d'or: què passa exactament

Vegem-ho amb noms i cognoms, perquè el mecanisme del desastre explica la regla millor que qualsevol advertiment.

  1. En Bruno publica funcionalitat/exportacio-csv amb tres commits: 2a7f4c1, 9e3b8d6, 5c1a9f2.
  2. La Carla fa git fetch i crea la seva branca a partir d'aquella feina. Ara té aquests tres commits al seu disc.
  3. En Bruno rebasa la seva branca sobre main perquè main ha avançat. Els seus tres commits es converteixen en d8b2e50, 71f6c34, ae95d18.
  4. En Bruno fa git push --force-with-lease. El servidor ara apunta a ae95d18.
  5. La Carla fa git pull.

El que veu la Carla:

 * branch            funcionalitat/exportacio-csv -> FETCH_HEAD
 + ae95d18...5c1a9f2 funcionalitat/exportacio-csv -> origin/funcionalitat/exportacio-csv  (forced update)

I a partir d'aquí, segons la seva configuració de pull:

  • Amb pull.ff only (la que vam configurar a 01-06): el pull falla amb un avís de divergència. És el millor dels casos: la Carla se n'assabenta i pot preguntar.
  • Amb merge: Git fusiona la cadena vella amb la nova i apareixen els tres commits duplicats, cadascun dues vegades amb hashos diferents, més un commit de fusió, més un grapat de conflictes absurds on cada canvi xoca amb si mateix.
  • Amb rebase: la Carla reaplica els seus commits (que inclouen els tres vells d'en Bruno) sobre la cadena nova, i obté el mateix festival de duplicats.

I si la Carla resol malament i publica, els commits vells tornen al servidor i el rebase d'en Bruno queda desfet. És el cicle clàssic: algú rebasa, algú ho reverteix sense voler, i l'historial acaba amb tot duplicat.

Com s'evita, en ordre d'eficàcia:

  1. No reescriure branques compartides. Si més d'una persona hi treballa, s'integra amb merge i punt.
  2. Si la branca és teva i publicada només com a còpia, rebasa quan vulguis, però avisa abans de forçar i fes servir sempre --force-with-lease (mai --force), que es nega si algú ha publicat alguna cosa que tu no tens.
  3. Si has estat la víctima, la sortida és git reset --hard origin/<branca> per descartar la teva còpia vella i tornar a aplicar el teu al damunt. La casuística completa, amb els passos per no perdre feina, és a la lliçó 09-03.

  1. git pull --rebase

A la lliçó 04-04 vam deixar pendent aquesta opció. Ja la podem entendre del tot.

git pull és fetch + integració. Amb --rebase, la integració és un rebase dels teus commits locals sobre el que acabes de portar:

git pull --rebase origin main

Comparació dels tres modes, ara completa:

Mode Si no has treballat en local Si has treballat i hi ha divergència Historial
--ff-only Avança la branca Falla i t'avisa Intacte
--no-rebase (merge) Avança la branca Crea un commit de fusió Amb nusos
--rebase Avança la branca Reaplica els teus commits al damunt Lineal

Per què agrada tant: quan dues persones toquen coses diferents del mateix projecte i sincronitzen sovint, pull amb merge genera un reguitzell de commits «Merge branch 'main' of git.exemple.cat…» que no expliquen absolutament res. pull --rebase els elimina.

I per què cal fer-lo servir amb cap: reescriu els teus commits locals. Això és inofensiu si no els has publicat (que és el cas típic: acabes de fer-los i encara no hi ha hagut push), i és exactament l'escenari de l'apartat 9 si sí que els has publicat.

Per deixar-ho configurat:

# Que 'git pull' sempre rebasi, a tots els repositoris
git config --global pull.rebase true

# Només per a una branca concreta
git config branch.main.rebase true

# Recomanable si actives l'anterior: no aplanar les fusions que TU vas fer expressament
git config --global pull.rebase merges

pull.rebase merges (equivalent a --rebase-merges) mereix una nota: per defecte, un rebase descarta els commits de fusió de la cadena que reaplica i ho aplana tot en una línia. Si tenies fusions intencionades a la teva feina local, merges les conserva reconstruint-les.

Si deixes pull.ff only com a configuració global (la nostra recomanació de 01-06 i la més segura per aprendre), sempre pots demanar el rebase puntualment amb git pull --rebase. L'explícit guanya.

  1. rebase.autoStash i altres opcions còmodes

git rebase exigeix un directori de treball net: si tens canvis sense confirmar, es nega a començar.

error: cannot rebase: You have unstaged changes.
error: Please commit or stash them.

La solució manual és git stash, rebasar i git stash pop (lliçó 05-04). L'automàtica:

git config --global rebase.autoStash true

Amb això, Git aparta els canvis sol, rebasa i els retorna en acabar. Si en retornar-los hi ha conflicte, t'avisa i el desament temporal es queda estalvi a la pila. És una comoditat excel·lent, i el seu equivalent per a pull és rebase.autoStash combinat amb pull.rebase, o git pull --rebase --autostash.

Altres opcions que convé conèixer:

Opció / configuració Què fa
git rebase -i Rebase interactiu: el tema complet de la lliçó 05-02
--rebase-merges Conserva els commits de fusió en lloc d'aplanar-los
--keep-empty Conserva els commits que queden buits després de reaplicar-se
--no-verify No executa els hooks (mòdul 6) a cada commit reaplicat
-X ours / -X theirs Resolució automàtica de conflictes per una banda (recorda la inversió!)
--exec "<ordre>" Executa una ordre després de cada commit reaplicat (lliçó 05-02)
git config rebase.updateRefs true Actualitza també les altres branques que apuntaven a commits del tram reaplicat
git rebase --show-current-patch Ensenya el commit que està fallant ara mateix

rebase.updateRefs resol un problema molest: si tens branques apilades (b surt d'a, c surt de b) i rebases c, les referències intermèdies es quedaven apuntant als commits vells. Amb aquesta opció activada, Git les arrossega.

  1. La xarxa de seguretat

Un rebase pot sortir malament: pots fer --skip d'un commit que no tocava, resoldre un conflicte al revés, o adonar-te als deu minuts que la base era una altra.

Bona notícia: els commits originals continuen allà. Com vam veure a l'apartat 2, el rebase no esborra objectes, només deixa de referenciar-los, i Git guarda un registre de per on ha passat cada referència. Aquest registre és el reflog, i amb ell es recupera l'estat anterior a un rebase gairebé sempre.

Aquest curs dedica la lliçó 09-04: Recuperant Confirmacions Perdudes a aquest tema, inclòs el procediment exacte per desfer un rebase desastrós. Aquí n'hi ha prou que sàpigues dues coses:

  1. Que la xarxa existeix: un rebase mal fet és recuperable durant setmanes.
  2. Que no és excusa per saltar-se la regla d'or, perquè el reflog és local. Recupera el teu repositori; no arregla els dels teus companys que ja es van descarregar els commits que vas reescriure.

I el reflex barat que sí que pots adoptar avui: abans d'un rebase que t'imposi respecte, deixa una marca.

git branch copia-abans-del-rebase
git rebase main
# Si surt malament: git reset --hard copia-abans-del-rebase
# Si surt bé: git branch -d copia-abans-del-rebase

Una branca són 41 bytes. La tranquil·litat, bastant més.

Errors Habituals i Consells

Error 1: creure que el rebase «mou» els commits. Els substitueix per uns de nous amb hashos diferents. Tot el perill del rebase deriva d'aquest fet, i qui l'interioritza deixa de tenir sorpreses.

Error 2: rebasar una branca compartida. Si en Bruno i la Carla treballen tots dos a funcionalitat/exportacio-csv, cap dels dos no la rebasa. S'integra amb merge. La comoditat d'un historial lineal no compensa una tarda de duplicats.

Error 3: fer servir --ours durant un rebase pensant que és la teva versió. És la de la base. Durant un rebase, tu ets theirs. Davant del dubte, git show :2:fitxer i git show :3:fitxer.

Error 4: fer servir git rebase --skip per «treure'm el conflicte de sobre». Descarta el commit sencer. Si contenia feina real, desapareix de la branca resultant.

Error 5: fer git commit enmig d'un rebase. Qui confirma és git rebase --continue. Tu només resols i fas git add.

Error 6: git push --force en lloc de --force-with-lease. El primer trepitja el que hi hagi al servidor sense mirar; el segon es nega si algú ha publicat alguna cosa que tu no tens. La diferència es va explicar a 04-05 i aquí és on de debò importa.

Error 7: rebasar sense fetch previ. git rebase main fa servir el teu main local, que pot portar dos dies desactualitzat. L'ordre correcte és git fetch origin, actualitzar main, i llavors rebasar (o rebasar directament sobre origin/main).

Consell 1: rebasa aviat i sovint. Una branca que es posa al dia cada dia té conflictes petits; una que ho fa al cap de dues setmanes té un conflicte per commit i tots grossos.

Consell 2: mira sempre git log --oneline main..HEAD abans de rebasar. Aquesta llista és exactament el que es reescriurà. Si et sorprèn la seva llargada o el seu contingut, la base que anaves a fer servir no era la que et pensaves.

Consell 3: activa rebase.autoStash i merge.conflictStyle zdiff3. Dues línies de configuració que eliminen dues de les friccions més habituals.

Consell 4: si el rebase es posa lleig, avorta. git rebase --abort és gratis i sempre funciona. Tornar-ho a provar amb el cap fred, amb els commits més petits o amb --onto ben triat sol ser molt més ràpid que insistir.

Exercicis

Exercici 1: rebase bàsic i prova de la immutabilitat

Crea un repositori de pràctiques amb una branca main i una branca funcionalitat/alguna-cosa que en surti. Afegeix dos commits a cadascuna. Després:

  1. Anota els hashos dels dos commits de la branca de funcionalitat.
  2. Rebasa-la sobre main.
  3. Demostra amb ordres que els hashos han canviat, que la data d'autoria s'ha conservat i que la de confirmació no.
  4. Demostra que els commits originals continuen existint a la base de dades d'objectes.

Exercici 2: --onto per desembolicar una branca

Reprodueix el problema de la Carla: crea branca-a sobre main amb dos commits, i branca-b sobre branca-a amb dos més. Després, fent servir git rebase --onto, aconsegueix que branca-b pengi de main contenint només els seus dos commits propis. Verifica el resultat amb git log --graph --all --oneline.

Exercici 3: conflicte en rebase i la inversió de bandes

Provoca un conflicte durant un rebase (les dues branques modificant la mateixa línia d'un fitxer). Amb el rebase aturat:

  1. Mostra les tres versions del fitxer des de l'índex (etapes :1:, :2: i :3:) i identifica quina correspon al teu commit.
  2. Resol quedant-te amb la teva versió fent servir l'opció correcta de git checkout.
  3. Acaba el rebase.
  4. Repeteix l'experiment des de zero però avortant amb --abort, i comprova que la branca torna a tenir els seus hashos originals.

Solucions

Solució 1:

mkdir /tmp/ex-rebase && cd /tmp/ex-rebase
git init -b main
echo "linia base" > f.txt && git add . && git commit -m "Base"

git switch -c funcionalitat/alguna-cosa
echo "a" >> f.txt && git commit -am "Commit A"
echo "b" >> f.txt && git commit -am "Commit B"

git switch main
echo "una altra cosa" > g.txt && git add . && git commit -m "Commit a main 1"
echo "mes" >> g.txt && git commit -am "Commit a main 2"
# 1. Anotar hashos originals
git switch funcionalitat/alguna-cosa
git log --oneline main..HEAD
9f3c7a1 Commit B
2d8e4b6 Commit A
# 2 i 3. Rebase i comprovació de dates
git log --pretty='%h | autor: %ad | confirmat: %cd | %s' --date=iso main..HEAD
git rebase main
git log --pretty='%h | autor: %ad | confirmat: %cd | %s' --date=iso main..HEAD
2d8e4b6 | autor: 2026-07-28 10:02:11 +0200 | confirmat: 2026-07-28 10:02:11 +0200 | Commit A
7b1f9d4 | autor: 2026-07-28 10:02:11 +0200 | confirmat: 2026-07-28 10:05:47 +0200 | Commit A

El hash és diferent, la data d'autoria idèntica i la de confirmació és la del moment del rebase.

# 4. Els commits vells continuen existint
git cat-file -t 2d8e4b6
git show --stat 2d8e4b6 | head -3
commit

Existeix com a objecte encara que cap branca no l'assoleixi. És el que fa possible la recuperació de la lliçó 09-04.

Solució 2:

mkdir /tmp/ex-onto && cd /tmp/ex-onto
git init -b main
echo "base" > f.txt && git add . && git commit -m "Base"

git switch -c branca-a
echo "a1" >> f.txt && git commit -am "A1"
echo "a2" >> f.txt && git commit -am "A2"

git switch -c branca-b
echo "b1" > b.txt && git add . && git commit -m "B1"
echo "b2" >> b.txt && git commit -am "B2"
# Comprovar què m'enduc: només B1 i B2
git log --oneline branca-a..branca-b
c4a9f21 B2
81d3e07 B1
git rebase --onto main branca-a branca-b
git log --graph --all --oneline
* 5e2b8c4 (HEAD -> branca-b) B2
* a93f16d B1
| * 6f4c2e9 (branca-a) A2
| * 3b7d5a8 A1
|/
* 1c8e4f2 (main) Base

branca-b penja de main amb els seus dos commits i sense rastre d'A1 ni d'A2.

Solució 3:

mkdir /tmp/ex-conflicte-rebase && cd /tmp/ex-conflicte-rebase
git init -b main
printf 'primera\nsegona\ntercera\n' > f.txt && git add . && git commit -m "Base"

git switch -c la-meva-branca
sed -i 's/segona/segona MEVA/' f.txt && git commit -am "Canvi de la meva branca"

git switch main
sed -i 's/segona/segona DE MAIN/' f.txt && git commit -am "Canvi de main"

git switch la-meva-branca
git rebase main
CONFLICT (content): Merge conflict in f.txt
error: could not apply 4d9a7c2... Canvi de la meva branca
# 1. Les tres versions des de l'índex
git show :1:f.txt   # base comuna
git show :2:f.txt   # "ours" = main, la nova base
git show :3:f.txt   # "theirs" = EL MEU commit
primera
segona
tercera
primera
segona DE MAIN
tercera
primera
segona MEVA
tercera

L'etapa :3: és la meva: durant un rebase, el meu commit és theirs.

# 2 i 3. Quedar-me amb el meu i continuar
git checkout --theirs f.txt
git add f.txt
git rebase --continue
git log --oneline
b7e3f19 (HEAD -> la-meva-branca) Canvi de la meva branca
5a1c8d3 (main) Canvi de main
2f9b6e4 Base
# 4. El mateix escenari, avortant
git reset --hard b7e3f19    # (repeteix el muntatge des de zero si ho prefereixes)
git switch la-meva-branca
git log --oneline -1        # anota el hash
git rebase main             # conflicte
git rebase --abort
git log --oneline -1        # mateix hash que abans

--abort restaura la branca exactament com era: mateixos commits, mateixos hashos, mateix directori de treball.

Conclusió

git rebase deixa de fer por tan bon punt s'entén què fa de debò. L'essencial d'aquesta lliçó:

  • El rebase no mou commits: crea commits nous. Canvia el pare, després canvia el hash, després és un altre objecte. Els originals queden orfes però continuen a la base de dades.
  • git rebase <base> reaplica sobre <base> tot el que hi ha a la teva branca i no hi és; git rebase --onto <nova-base> <des-de> <fins-a> et deixa triar amb precisió quin tram moure i cap on.
  • Rebase davant de merge no és una qüestió tècnica sinó narrativa: el merge explica el que va passar, el rebase explica una versió ordenada. Rebase per al que encara és teu; merge per ajuntar històries públiques; davant del dubte, merge.
  • Els conflictes d'un rebase es resolen igual que els d'una fusió (lliçó 03-05), però se'n surt amb --continue, --skip (que descarta el commit) o --abort (que és sempre segur), i les bandes ours/theirs estan invertides: la base és ours, el teu commit és theirs.
  • La regla d'or: no reescriguis historial publicat que altres puguin tenir. Si ho fas, els teus companys acaben amb tot duplicat i l'única sortida és coordinar-se.
  • git pull --rebase aplica aquesta mateixa idea en sincronitzar i elimina els commits de fusió inútils; rebase.autoStash treu la fricció de tenir canvis sense confirmar.
  • El reflog és la xarxa de seguretat per al teu repositori (lliçó 09-04), no per al dels altres.

El que ve

Fins ara hem fet servir el rebase per a una sola cosa: canviar la base. Però el mateix mecanisme —descompondre una branca en una llista de commits i tornar-los a aplicar— permet molt més si et deixen editar aquesta llista abans que s'executi: canviar l'ordre, unir dos commits en un, partir-ne un en dos, corregir un missatge o eliminar un commit del tot.

Això és exactament el que fa git rebase -i, i és el que en Bruno necessita per convertir els seus commits apany, wip 2 i wip 3 en alguna cosa que es pugui ensenyar sense vergonya. Ho veiem a la lliçó 05-02: Rebase Interactiu.

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