L'Ana i en Bruno tenen dues funcionalitats acabades i provades, cadascuna a la seva branca. I tanmateix main —el projecte de debò, el que es publica— continua exactament on era fa una setmana, a c5d9b1e. Aïllar la feina era la meitat del problema; aquesta lliçó resol l'altra meitat.

Fusionar (merge) és l'operació de portar la feina d'una branca a una altra. És la raó de ser de les branques: si no es poguessin tornar a ajuntar, aïllar la feina no serviria de res.

Git distingeix dos escenaris molt diferents en fusionar, i confondre'ls és l'origen de la majoria de dubtes. En un, no hi ha res a fusionar de debò i n'hi ha prou de moure un punter. En l'altre, hi ha dues línies de treball divergents i cal crear un commit nou amb dos pares. Els veurem tots dos en directe, entendrem quan es produeix cadascun, i aprendrem a forçar o prohibir cada comportament quan ens interessi.

En aquesta lliçó totes les fusions sortiran bé a la primera. Quan dues persones toquen les mateixes línies apareixen els conflictes, i aquests tenen lliçó pròpia (03-05).

Contingut

  1. Com es fusiona: sempre des de la branca de destinació
  2. Escenari 1: fast-forward, el punter avança
  3. Desfer una fusió local
  4. --no-ff: forçar el commit de fusió
  5. Escenari 2: la fusió a tres bandes
  6. L'ancestre comú i git merge-base
  7. Anatomia del commit de fusió
  8. Llegir un historial amb fusions
  9. --ff-only: prohibir els commits de fusió

  1. Com es fusiona: sempre des de la branca de destinació

Abans de res, la regla que evita el 90 % dels errors de principiant:

Et situes a la branca que vol rebre la feina i fusiones la que l'aporta.

git switch <branca-destinacio>
git merge <branca-origen>

git merge només mou la branca on ets. La branca que anomenes com a argument no es toca en absolut: continua apuntant exactament al mateix commit abans i després.

Aplicat al cas de l'Ana: vol que main rebi el comptador de tasques.

git switch main
git merge funcionalitat/comptador-tasques

A l'inrevés —ser a funcionalitat/comptador-tasques i fer git merge main— és una operació diferent i vàlida, però significa una altra cosa: portar a la meva branca de treball l'últim de main, una cosa que es fa per mantenir-se al dia. Canviar l'ordre per descuit és un clàssic. Abans de fusionar, comprova on ets:

git branch --show-current
main

  1. Escenari 1: fast-forward, el punter avança

Situació de partida:

git log --oneline --graph --all
* b2e6d3f (funcionalitat/filtre-pendents) Corregeix el focus del camp després d'afegir una tasca
* 7c1f4a9 Aplica estil a les tasques completades
* 3d5b8e1 Afegeix el filtre de tasques pendents
| * 9d1e4b7 (funcionalitat/comptador-tasques) Marca les tasques com a completades en fer clic
| * 6f2b9d4 Afegeix el comptador de tasques pendents
|/
* c5d9b1e (HEAD -> main) Documenta la instal·lació al README
* 4e7f2a9 Afegeix l'esborrat de tasques al llistat
* 8b6d3c2 Afegeix els estils base del llistat
* 1a4c8d6 Estructura inicial del gestor de tasques

Fixa't en la relació entre main i funcionalitat/comptador-tasques. main és a c5d9b1e, i c5d9b1e és un avantpassat directe de 9d1e4b7. Amb el vocabulari de la lliçó 03-01: main està endarrerida, no ha divergit. No té cap commit propi que la branca de l'Ana no tingui.

En aquesta situació, «fusionar» és una paraula massa gran. No hi ha res a combinar: tot el que hi ha a main ja està contingut a l'altra branca. N'hi ha prou d'avançar el punter de main fins a 9d1e4b7.

git switch main
git merge funcionalitat/comptador-tasques
Updating c5d9b1e..9d1e4b7
Fast-forward
 app.js     | 24 ++++++++++++++++++++++--
 estils.css |  6 ++++++
 2 files changed, 28 insertions(+), 2 deletions(-)

Analitzem la sortida línia a línia:

  • Updating c5d9b1e..9d1e4b7: d'on a on ha mogut main.
  • Fast-forward: el nom de l'escenari. No s'ha creat cap commit.
  • El resum de fitxers: els canvis acumulats dels dos commits de l'Ana, en el mateix format de git diff --stat que ja coneixes.

Abans i després, al disc:

ABANS:   .git/refs/heads/main → c5d9b1e…
DESPRÉS: .git/refs/heads/main → 9d1e4b7…

Això és tot el que ha passat: 41 bytes reescrits. No hi ha commit nou, no hi ha objectes nous, no hi ha res a calcular.

graph RL
    subgraph despres["DESPRÉS"]
        D2["c5d9b1e"] --> C2["4e7f2a9"]
        E2["6f2b9d4"] --> D2
        F2["9d1e4b7<br/><b>main</b> · comptador-tasques"] --> E2
    end
    subgraph abans["ABANS"]
        D1["c5d9b1e<br/><b>main</b>"] --> C1["4e7f2a9"]
        E1["6f2b9d4"] --> D1
        F1["9d1e4b7<br/>comptador-tasques"] --> E1
    end

L'historial resultant:

git log --oneline --graph
* 9d1e4b7 (HEAD -> main, funcionalitat/comptador-tasques) Marca les tasques com a completades en fer clic
* 6f2b9d4 Afegeix el comptador de tasques pendents
* c5d9b1e Documenta la instal·lació al README
* 4e7f2a9 Afegeix l'esborrat de tasques al llistat
* 8b6d3c2 Afegeix els estils base del llistat
* 1a4c8d6 Estructura inicial del gestor de tasques

Una línia perfectament recta. Les dues branques apunten ara al mateix commit.

I aquí hi ha el detall que cal sospesar: mirant aquest historial, és impossible saber que hi va haver una branca. 6f2b9d4 i 9d1e4b7 semblen dos commits normals fets directament sobre main. La informació que formaven una unitat de treball —una funcionalitat— s'ha perdut.

De vegades això és exactament el que vols (historial net i lineal). D'altres vegades no. Per al segon cas hi ha --no-ff, però abans hem de desfer el que hem fet.

  1. Desfer una fusió local

L'Ana s'ha quedat amb el dubte i vol provar l'altra manera. Com que la fusió és local i encara no ha sortit del seu portàtil, desfer-la és trivial: n'hi ha prou de tornar el punter de main on era.

git reset --hard c5d9b1e
HEAD is now at c5d9b1e Documenta la instal·lació al README
git log --oneline --graph --all
* b2e6d3f (funcionalitat/filtre-pendents) Corregeix el focus del camp després d'afegir una tasca
* 7c1f4a9 Aplica estil a les tasques completades
* 3d5b8e1 Afegeix el filtre de tasques pendents
| * 9d1e4b7 (funcionalitat/comptador-tasques) Marca les tasques com a completades en fer clic
| * 6f2b9d4 Afegeix el comptador de tasques pendents
|/
* c5d9b1e (HEAD -> main) Documenta la instal·lació al README
...

Tot com era. Els dos commits de l'Ana no s'han perdut perquè funcionalitat/comptador-tasques continua apuntant-hi: són perfectament assolibles.

Tres advertiments sobre el que acabem de fer:

  • git reset --hard és una eina esmolada. Mou el punter de la branca i sobreescriu el directori de treball, descartant qualsevol canvi sense confirmar. L'estudiarem amb tot detall a la lliçó 09-02.
  • Aquí és segur perquè la feina està protegida per una altra branca i perquè no hi havia res sense confirmar.
  • Només val per a fusions locals. Si la fusió ja s'hagués compartit amb la resta de l'equip, moure el punter cap enrere causaria problemes als altres; allà l'eina correcta és git revert (lliçó 05-06). Com que tot aquest mòdul passa en un únic repositori local, no tenim aquest problema.

Una drecera útil: Git desa automàticament la posició anterior a la referència ORIG_HEAD just abans d'operacions com merge o reset. Així que això hauria estat equivalent sense necessitat de recordar el hash:

git reset --hard ORIG_HEAD

  1. --no-ff: forçar el commit de fusió

L'Ana ho torna a intentar, aquesta vegada demanant explícitament que es creï un commit de fusió encara que no calgui:

git merge --no-ff funcionalitat/comptador-tasques

Git obre l'editor amb un missatge proposat:

Merge branch 'funcionalitat/comptador-tasques'

# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.

L'Ana amplia la primera línia perquè digui alguna cosa útil i desa:

Fusiona el comptador de tasques pendents

Incorpora el comptador a la capçalera i el marcatge de tasques
completades en fer clic. Revisat amb en Bruno.
Merge made by the 'ort' strategy.
 app.js     | 24 ++++++++++++++++++++++--
 estils.css |  6 ++++++
 2 files changed, 28 insertions(+), 2 deletions(-)

Fixa't en la diferència: ara diu Merge made by the 'ort' strategy en lloc de Fast-forward. S'ha creat un commit.

git log --oneline --graph
*   8d4e6b2 (HEAD -> main) Fusiona el comptador de tasques pendents
|\
| * 9d1e4b7 (funcionalitat/comptador-tasques) Marca les tasques com a completades en fer clic
| * 6f2b9d4 Afegeix el comptador de tasques pendents
|/
* c5d9b1e Documenta la instal·lació al README
* 4e7f2a9 Afegeix l'esborrat de tasques al llistat
* 8b6d3c2 Afegeix els estils base del llistat
* 1a4c8d6 Estructura inicial del gestor de tasques
gitGraph
   commit id: "1a4c8d6"
   commit id: "8b6d3c2"
   commit id: "4e7f2a9"
   commit id: "c5d9b1e"
   branch comptador-tasques
   checkout comptador-tasques
   commit id: "6f2b9d4"
   commit id: "9d1e4b7"
   checkout main
   merge comptador-tasques id: "8d4e6b2"

El commit 8d4e6b2 no aporta cap canvi de codi: el contingut resultant és idèntic al de 9d1e4b7. El que aporta és informació sobre l'estructura de la feina: diu que aquells dos commits formaven una unitat i quan es va integrar al projecte.

Quan interessa --no-ff

A favor de --no-ff A favor del fast-forward
L'historial documenta quines funcionalitats es van integrar i quan Historial lineal, més fàcil de llegir commit a commit
Es pot revertir una funcionalitat sencera amb un sol git revert -m 1 Sense commits «buits» que no canvien codi
git log --first-parent dona un resum net de les integracions git bisect recorre menys soroll
És la pràctica que assumeixen fluxos com Git Flow (mòdul 7) Encaixa amb equips que prefereixen un historial d'una sola línia

Molts equips ho fixen a la configuració per no haver-se'n de recordar:

git config --global merge.ff false

Amb això, totes les fusions crearan commit de fusió. Una variant més matisada, molt estesa, és deixar-ho per branca o simplement escriure --no-ff quan s'integra una funcionalitat i permetre el fast-forward per a la resta.

És una decisió d'equip, no una qüestió tècnica: l'important és que tothom faci el mateix. Hi tornarem al mòdul 8.

  1. Escenari 2: la fusió a tres bandes

Ara l'Ana integra la feina d'en Bruno. I la situació ha canviat completament:

git log --oneline --graph --all
*   8d4e6b2 (HEAD -> main) Fusiona el comptador de tasques pendents
|\
| * 9d1e4b7 (funcionalitat/comptador-tasques) Marca les tasques com a completades en fer clic
| * 6f2b9d4 Afegeix el comptador de tasques pendents
|/
| * b2e6d3f (funcionalitat/filtre-pendents) Corregeix el focus del camp després d'afegir una tasca
| * 7c1f4a9 Aplica estil a les tasques completades
| * 3d5b8e1 Afegeix el filtre de tasques pendents
|/
* c5d9b1e Documenta la instal·lació al README
...

main ja no és avantpassat de funcionalitat/filtre-pendents: té tres commits propis (els dos de l'Ana més la fusió) que la branca d'en Bruno no té. I la branca d'en Bruno en té tres més que main no té. Han divergit.

Aquí no val moure un punter. Git ha de combinar de debò dues versions diferents dels fitxers.

git merge funcionalitat/filtre-pendents

Git obre l'editor amb el missatge proposat —l'Ana l'accepta tal qual aquesta vegada— i respon:

Merge made by the 'ort' strategy.
 app.js     | 15 +++++++++++++++
 estils.css |  4 ++++
 index.html |  3 +++
 3 files changed, 22 insertions(+)
git log --oneline --graph
*   f7a3e92 (HEAD -> main) Merge branch 'funcionalitat/filtre-pendents'
|\
| * b2e6d3f (funcionalitat/filtre-pendents) Corregeix el focus del camp després d'afegir una tasca
| * 7c1f4a9 Aplica estil a les tasques completades
| * 3d5b8e1 Afegeix el filtre de tasques pendents
* |   8d4e6b2 Fusiona el comptador de tasques pendents
|\ \
| * | 9d1e4b7 (funcionalitat/comptador-tasques) Marca les tasques com a completades en fer clic
| * | 6f2b9d4 Afegeix el comptador de tasques pendents
|/ /
* / c5d9b1e Documenta la instal·lació al README
|/
* 4e7f2a9 Afegeix l'esborrat de tasques al llistat
* 8b6d3c2 Afegeix els estils base del llistat
* 1a4c8d6 Estructura inicial del gestor de tasques
gitGraph
   commit id: "1a4c8d6"
   commit id: "8b6d3c2"
   commit id: "4e7f2a9"
   commit id: "c5d9b1e"
   branch comptador-tasques
   checkout comptador-tasques
   commit id: "6f2b9d4"
   commit id: "9d1e4b7"
   checkout main
   merge comptador-tasques id: "8d4e6b2"
   branch filtre-pendents
   checkout filtre-pendents
   commit id: "3d5b8e1"
   commit id: "7c1f4a9"
   commit id: "b2e6d3f"
   checkout main
   merge filtre-pendents id: "f7a3e92"

(Al diagrama, filtre-pendents es dibuixa sortint de main; recorda que en realitat parteix de c5d9b1e, abans de la primera fusió.)

El projecte té per fi les dues funcionalitats. app.js conté el comptador de l'Ana i el filtre d'en Bruno, i cap dels dos no ha hagut de copiar res a mà.

Per què s'anomena «a tres bandes»

Perquè Git fa servir tres versions de cada fitxer per decidir el resultat:

Versió D'on surt Paper
base L'ancestre comú (c5d9b1e) Punt de referència neutral
ours La branca on ets (main, a 8d4e6b2) El teu costat
theirs La branca que fusiones (b2e6d3f) L'altre costat

I la regla, línia a línia, és la que vam anticipar a la lliçó 03-01:

Va canviar a ours Va canviar a theirs Resultat
No No Es conserva la línia de la base
No S'agafa la versió d'ours
No S'agafa la versió de theirs
Sí, igual S'agafa aquella versió (no hi ha discrepància)
Sí, diferent Conflicte: ho decideix una persona (lliçó 03-05)

En aquest cas no hi ha hagut conflictes perquè l'Ana va tocar la capçalera i la lògica del comptador, mentre que en Bruno va afegir el filtre més avall i unes regles noves a estils.css. Zones diferents dels mateixos fitxers.

Fixa't en la conseqüència important: que dues persones toquin el mateix fitxer no provoca cap conflicte. El conflicte apareix quan toquen les mateixes línies (o línies molt properes) de maneres diferents. És una font de por innecessària molt estesa.

  1. L'ancestre comú i git merge-base

Git no et pregunta quin és l'ancestre comú: el calcula. I el pots calcular tu:

git merge-base main funcionalitat/filtre-pendents
c5d9b1e7a3f2d8b4e6c1a9f5d3b7e2c8a4f6d1b9
git log --oneline -1 c5d9b1e
c5d9b1e Documenta la instal·lació al README

Efectivament: l'últim commit que les dues branques tenen en comú, el punt on es van separar.

Algunes variants útils:

# És A avantpassat de B? Respon amb el codi de sortida, sense imprimir res
git merge-base --is-ancestor main funcionalitat/filtre-pendents
echo $?
1

Un 1 significa «no». Si donés 0, la fusió seria un fast-forward. És la comprovació exacta que fa git merge per decidir l'escenari, i resulta molt pràctica en scripts.

# Veure la base de fusió de manera llegible, juntament amb les puntes
git log --oneline --graph --boundary main...funcionalitat/filtre-pendents

Fixa't en els tres punts. Ja vas veure a..b a la lliçó 02-06 (el que hi ha a b i no a a); a...b és la diferència simètrica: el que té cadascuna i l'altra no. És la manera natural de respondre a «en què es diferencien aquestes dues branques?».

Un truc molt utilitzat per veure què aporta una branca respecte al punt on es va separar, sense que aparegui tot el que ha avançat main mentrestant:

git diff main...funcionalitat/filtre-pendents

Amb tres punts a git diff, Git compara l'ancestre comú amb la punta de la segona branca. És a dir, mostra només el que ha fet en Bruno, ignorant el que hagi canviat a main des de llavors. És el que ensenyen les plataformes de revisió de codi quan mostren els canvis d'una proposta.

  1. Anatomia del commit de fusió

Un commit de fusió és un objecte commit normal i corrent amb una única peculiaritat: té més d'una línia parent. Recorda el model de dades de la lliçó 01-04, on vam veure que el camp parent es pot repetir.

git cat-file -p f7a3e92
tree 3f7b2e9c1a5d8b4f2e6a9c3d7b1f5e8a4c2d9b6f
parent 8d4e6b2f1a3c5e7b9d2f4a6c8e0b1d3f5a7c9e2b
parent b2e6d3f8a1c5e9d2b4f7a3c6e8d1b5f9a2c4e7d3
author Ana Ferrer <[email protected]> 1754049600 +0200
committer Ana Ferrer <[email protected]> 1754049600 +0200

Merge branch 'funcionalitat/filtre-pendents'

Dues línies parent. I l'ordre importa moltíssim:

Posició Nom Què és Es referencia com
Primer parent first parent El commit on era main abans de fusionar f7a3e92^1 o f7a3e92^
Segon parent second parent La punta de la branca fusionada f7a3e92^2
git log --oneline -1 f7a3e92^1
8d4e6b2 Fusiona el comptador de tasques pendents
git log --oneline -1 f7a3e92^2
b2e6d3f Corregeix el focus del camp després d'afegir una tasca

Aquell ordre és el que fa que la branca de destinació sigui sempre ^1 i la branca portada sigui ^2. Té conseqüències pràctiques concretes:

  • git log --first-parent segueix només el primer pare i produeix un resum net del projecte principal.
  • git revert -m 1 <fusio> desfà una fusió conservant la línia principal, indicant amb -m 1 quin dels pares és el «bo» (lliçó 05-06).
  • És el motiu pel qual HEAD^ i HEAD~1 no són sinònims en un commit de fusió, cosa que ja es va apuntar a la lliçó 02-06.

git show sobre una fusió

git show f7a3e92
commit f7a3e92b5d1c8f4a6e3b7d9f2a5c1e8b4d6f3a9c (HEAD -> main)
Merge: 8d4e6b2 b2e6d3f
Author: Ana Ferrer <[email protected]>
Date:   Fri Aug 1 12:20:00 2026 +0200

    Merge branch 'funcionalitat/filtre-pendents'

Dues coses que sorprenen:

  1. Apareix una línia Merge: amb els hashos abreujats dels dos pares. És l'indicador visual que ets davant d'una fusió.
  2. No hi ha diff. Per defecte, git show i git log -p no mostren canvis als commits de fusió.

No és cap error. Un commit de fusió té dos pares, així que «el diff» és ambigu: respecte a quin? I, en una fusió neta com aquesta, respecte a qualsevol dels dos el resultat seria simplement tot el que aportava l'altra branca, que ja és als seus propis commits. Mostrar-ho duplicaria informació.

Si tot i així ho necessites:

# Diff respecte al primer pare (el que la fusió va portar a main)
git show --first-parent f7a3e92

# Diff combinat: només les línies que difereixen d'AMBDÓS pares,
# és a dir, el que es va decidir a mà en resoldre conflictes
git show --cc f7a3e92

# Un diff per cada pare, per separat
git show -m f7a3e92

--cc és l'opció realment valuosa i mereix que la recordis: en una fusió neta no mostra pràcticament res, però en una fusió amb conflictes resolts mostra exactament el que va decidir la persona, que és justament el que interessa auditar. La farem servir a la lliçó 03-05.

  1. Llegir un historial amb fusions

L'historial de main ja no és una línia recta, i val la pena saber mirar-lo des de dues altures diferents.

Vista completa, amb tot el detall:

git log --oneline --graph

Vista d'alt nivell, seguint només el primer pare:

git log --oneline --graph --first-parent
*   f7a3e92 (HEAD -> main) Merge branch 'funcionalitat/filtre-pendents'
*   8d4e6b2 Fusiona el comptador de tasques pendents
* c5d9b1e Documenta la instal·lació al README
* 4e7f2a9 Afegeix l'esborrat de tasques al llistat
* 8b6d3c2 Afegeix els estils base del llistat
* 1a4c8d6 Estructura inicial del gestor de tasques

Sis entrades en lloc d'onze. Aquest és l'historial des del punt de vista del projecte: què es va integrar a main i en quin ordre, sense el detall intern de cada funcionalitat. Per a un responsable de producte o per redactar unes notes de versió, és infinitament més útil que la vista completa.

I aquí es veu per què val la pena escriure bons missatges als commits de fusió: Fusiona el comptador de tasques pendents informa; Merge branch 'funcionalitat/filtre-pendents' obliga a endevinar.

Altres filtres relacionats:

git log --merges       # només els commits de fusió
git log --no-merges    # només els commits de feina real

--no-merges és especialment útil combinat amb --author per veure el que ha escrit realment una persona sense comptar les seves integracions.

  1. --ff-only: prohibir els commits de fusió

L'oposat de --no-ff. Amb --ff-only, Git fusiona només si pot fer-ho avançant el punter; si calgués un commit de fusió, s'hi nega i no toca res:

git switch main
git merge --ff-only funcionalitat/filtre-pendents
fatal: Not possible to fast-forward, aborting.

Per a què serveix negar-se a fusionar? Per garantir un historial lineal. Hi ha equips que no volen veure ni un sol commit de fusió a main i prefereixen que qui integra posi primer la seva branca al dia (amb rebase, que veurem a la lliçó 05-01) i després fusioni en fast-forward.

També és una xarxa de seguretat excel·lent contra fusions accidentals. I de fet ja el tens actiu sense saber-ho: a la lliçó 01-06 vas configurar

git config --global pull.ff only

que aplica exactament aquesta política a git pull (que, com veurem al mòdul 4, és una obtenció seguida d'una fusió). La configuració equivalent per a git merge seria:

git config --global merge.ff only

Les tres polítiques, comparades

Opció Quan es pot avançar Quan les branques han divergit
Per defecte Fast-forward, sense commit nou Crea commit de fusió
--no-ff Crea commit de fusió igualment Crea commit de fusió
--ff-only Fast-forward, sense commit nou Falla i no fa res

Un parell d'opcions més que convé conèixer:

# Editar el missatge encara que Git no ho demanés
git merge --edit funcionalitat/filtre-pendents

# Acceptar el missatge proposat sense obrir l'editor
git merge --no-edit funcionalitat/filtre-pendents

# Fusionar però NO confirmar: deixa el resultat preparat per revisar-lo
git merge --no-commit funcionalitat/filtre-pendents

--no-commit és especialment interessant i el reprendrem a la lliçó següent, quan parlem d'estratègies: permet inspeccionar i ajustar el resultat de la fusió abans de segellar-lo.

Errors Habituals i Consells

Error 1: fusionar en la direcció equivocada. Ser a funcionalitat/comptador-tasques i executar git merge main quan el que volies era el contrari. La branca de funcionalitat rep main, main no se n'assabenta, i després no entens per què el projecte continua igual. Comprova sempre amb git branch --show-current abans de fusionar.

Error 2: creure que fusionar mou les dues branques. git merge mou només la branca on ets. Després d'integrar funcionalitat/filtre-pendents a main, aquella branca continua exactament a b2e6d3f. Continua existint i continua apuntant al mateix; esborrar-la quan ja no calgui és tema de la lliçó 03-06.

Error 3: témer que fusionar dues branques que toquen el mateix fitxer doni conflicte. No en dona si toquen zones diferents. Git treballa línia a línia, no fitxer a fitxer.

Error 4: no entendre per què git show d'una fusió no mostra canvis. És el comportament per defecte i té sentit: el diff seria ambigu amb dos pares. Fes servir --cc per veure el que es va decidir a mà o --first-parent per veure el que la fusió va portar.

Error 5: deixar el missatge de fusió per defecte en integracions importants. Merge branch 'funcionalitat/x' no diu res que el graf no digui ja. En una vista --first-parent, aquell missatge és tota la informació disponible sobre la integració: aprofita'l per explicar què s'integra i per què.

Consell 1: comprova abans de fusionar què t'emportaràs. Dues ordres que costen un segon:

git log --oneline main..funcionalitat/filtre-pendents   # quins commits arriben
git diff main...funcionalitat/filtre-pendents --stat    # quins fitxers canvien

Consell 2: fusiona amb el directori de treball net. Git es nega a fusionar si hi ha canvis sense confirmar que es poguessin perdre, però fins i tot quan t'hi deixa, barrejar els teus canvis a mitges amb el resultat d'una fusió és una recepta per no saber què és teu i què ha portat la fusió.

Consell 3: fusiona sovint en la direcció «de main cap a la teva branca». Portar main a la teva branca de funcionalitat cada pocs dies manté la divergència petita i converteix els conflictes grossos en conflictes trivials. És la millor prevenció que existeix davant de la lliçó 03-05.

Consell 4: fes servir --first-parent per presentar la feina. Quan algú et pregunti «què ha entrat al projecte aquest mes?», git log --oneline --first-parent --since="1 month ago" dona la resposta exacta.

Exercicis

Exercici 1: els dos escenaris, en un repositori de proves

Crea un repositori nou i provoca deliberadament els dos escenaris:

  1. Una fusió que sigui fast-forward.
  2. Una fusió a tres bandes, sense conflictes.

Abans d'executar cada git merge, prediu amb git merge-base --is-ancestor quin dels dos passarà.

Exercici 2: l'anatomia del commit de fusió

Sobre la fusió a tres bandes de l'exercici anterior, esbrina:

  1. Els hashos complets dels seus dos pares, fent servir només ordres de lampisteria.
  2. Quin dels dos era la branca de destinació.
  3. Per què git show <fusio> no mostra cap diff i com veure'l igualment.
  4. Quants commits mostra git log --oneline i quants git log --oneline --first-parent.

Exercici 3: triar la política

Per a cada situació, digues quina opció de fusió faries servir (--ff per defecte, --no-ff o --ff-only) i justifica-ho en una frase:

  1. Integrar a main una funcionalitat de 12 commits desenvolupada durant dues setmanes.
  2. Integrar una branca amb un únic commit que corregeix una errada al README.
  3. Posar al dia la teva branca de funcionalitat amb l'últim de main, sabent que tu no has tocat main.
  4. Un script d'integració contínua que ha de fallar si algú intenta introduir una fusió inesperada a main.

Solucions

Solució 1:

mkdir /tmp/practica-merge && cd /tmp/practica-merge
git init -b main
echo "linia 1" > fitxer.txt
git add . && git commit -m "Primer commit"

Escenari fast-forward:

git switch -c branca-ff
echo "linia 2" >> fitxer.txt
git commit -am "Afegeix la línia 2"
git switch main

Predicció:

git merge-base --is-ancestor main branca-ff; echo $?
0

0 significa que main que és avantpassat de branca-ff: serà fast-forward.

git merge branca-ff
Updating a1b2c3d..e4f5a6b
Fast-forward
 fitxer.txt | 1 +
 1 file changed, 1 insertion(+)

Escenari a tres bandes:

git switch -c branca-3b
echo "aportacio de la branca" > altre.txt
git add . && git commit -m "Afegeix altre.txt a la branca"

git switch main
echo "aportacio de main" > tercer.txt
git add . && git commit -m "Afegeix tercer.txt a main"

Predicció:

git merge-base --is-ancestor main branca-3b; echo $?
1

1 significa «no»: main té ara un commit propi. Han divergit, caldrà commit de fusió.

git merge --no-edit branca-3b
Merge made by the 'ort' strategy.
 altre.txt | 1 +
 1 file changed, 1 insertion(+)

Sense conflicte: cada branca va tocar un fitxer diferent.

Solució 2:

# 1. Pares, amb lampisteria
git cat-file -p HEAD | grep "^parent"
parent 7f3c9a2e5b1d8f4a6c2e9b3d7f1a5c8e4b2d6f9a
parent 2b1a3c5e7d9f0b2a4c6e8d0f2a4b6c8e0d2f4a6b

O de manera més directa:

git rev-parse HEAD^1 HEAD^2
# 2. La branca de destinació és el PRIMER pare
git log --oneline -1 HEAD^1
7f3c9a2 Afegeix tercer.txt a main

Era main, que és on érem en fusionar. El segon pare és la punta de branca-3b.

# 3. Sense diff per defecte...
git show HEAD
commit 5e8b1d4f2a6c9e3b7d1f5a8c4e2b6d9f3a7c1e5b (HEAD -> main)
Merge: 7f3c9a2 2b1a3c5
...

Perquè amb dos pares el diff és ambigu. Per veure'l:

git show --first-parent HEAD    # què va portar la fusió a main
git show --cc HEAD              # només el resolt a mà (aquí, res)
# 4. Recompte
git log --oneline | wc -l
4
git log --oneline --first-parent | wc -l
3

La segona vista omet el commit de la branca fusionada i mostra només la línia principal: primer commit, tercer.txt i la fusió.

Solució 3:

  1. --no-ff. Dotze commits són una unitat de treball amb identitat pròpia. El commit de fusió documenta quan va entrar la funcionalitat i permet revertir-la sencera amb git revert -m 1. A més, a --first-parent apareixerà com una sola entrada.

  2. Per defecte (fast-forward). Una errada no és una unitat de treball que mereixi un node al graf. Un commit de fusió aquí només afegeix soroll.

  3. --ff-only. Si no has tocat main, la teva branca hauria d'estar simplement endarrerida i el fast-forward funcionarà. Si falla, és que hi havia divergència inesperada, i la fallada és una informació valuosa: t'avisa abans de crear una fusió que no esperaves.

  4. --ff-only. És exactament el cas d'ús: convertir la política d'historial lineal en una comprovació automàtica que falla en lloc de crear el commit de fusió.

Conclusió

Ja saps ajuntar la feina que les branques mantenien separada:

  • Es fusiona des de la branca de destinació: git switch main i després git merge <branca>. git merge mou només la branca on ets.
  • Fast-forward: si la branca de destinació és avantpassada de la que fusiones, no hi ha res a combinar i Git es limita a avançar el punter. No es crea cap commit i l'historial queda lineal, a canvi de perdre la traça que hi va haver una branca.
  • Fusió a tres bandes: si les branques han divergit, Git combina les tres versions de cada fitxer (base, ours, theirs) i crea un commit de fusió amb dos pares. Les línies que només va canviar un costat s'agafen d'aquell costat; les que van canviar tots dos de manera diferent són un conflicte.
  • L'ancestre comú el calcula Git i el pots consultar amb git merge-base. --is-ancestor prediu l'escenari; la sintaxi a...b serveix per comparar dues branques pel seu punt de separació.
  • El commit de fusió és un commit normal amb dues línies parent. L'ordre importa: ^1 és la branca de destinació i ^2 la portada. D'aquí --first-parent, git revert -m 1 i el fet que HEAD^ i HEAD~1 difereixin a les fusions. git show no mostra diff per defecte; fes servir --cc.
  • --no-ff força el commit de fusió encara que no calgui, per documentar la integració; --ff-only el prohibeix, per garantir un historial lineal. Tots dos es poden fixar amb merge.ff.

main ja conté el comptador de tasques i el filtre de pendents. El projecte està sencer per primera vegada des que en Bruno s'hi va incorporar.

El que ve

A les sortides d'aquesta lliçó ha aparegut una vegada i una altra una paraula que hem deixat passar: Merge made by the **'ort'** strategy. Què és ort? N'hi ha d'altres? Es poden triar?

Sí, i en alguns casos triar bé estalvia molta feina. A la lliçó següent, Estratègies de Fusió, veurem les estratègies que Git sap aplicar (ort, resolve, octopus, ours, subtree), les opcions d'estratègia amb -X —inclòs el parany clàssic de confondre l'estratègia ours amb l'opció -X ours—, el squash merge, que aixafa una branca sencera en un sol commit sense registrar la fusió, i una taula de decisió per saber quina forma d'integrar triar segons l'historial que vulguis acabar tenint.

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