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
- Com es fusiona: sempre des de la branca de destinació
- Escenari 1: fast-forward, el punter avança
- Desfer una fusió local
--no-ff: forçar el commit de fusió- Escenari 2: la fusió a tres bandes
- L'ancestre comú i
git merge-base - Anatomia del commit de fusió
- Llegir un historial amb fusions
--ff-only: prohibir els commits de fusió
- 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 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.
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:
- Escenari 1: fast-forward, el punter avança
Situació de partida:
* 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.
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 mogutmain.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 --statque ja coneixes.
Abans i després, al disc:
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:
* 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.
- 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.
* 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:
--no-ff: forçar el commit de fusió
--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 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.
* 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:
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.
- Escenari 2: la fusió a tres bandes
Ara l'Ana integra la feina d'en Bruno. I la situació ha canviat completament:
* 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 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(+)
* 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 |
| Sí | No | S'agafa la versió d'ours |
| No | Sí | S'agafa la versió de theirs |
| Sí | Sí, igual | S'agafa aquella versió (no hi ha discrepància) |
| Sí | 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.
- L'ancestre comú i
git merge-base
git merge-baseGit no et pregunta quin és l'ancestre comú: el calcula. I el pots calcular tu:
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 $?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-pendentsFixa'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:
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.
- 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.
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 |
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-parentsegueix 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 1quin dels pares és el «bo» (lliçó 05-06).- És el motiu pel qual
HEAD^iHEAD~1no 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ó
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:
- Apareix una línia
Merge:amb els hashos abreujats dels dos pares. És l'indicador visual que ets davant d'una fusió. - No hi ha diff. Per defecte,
git showigit log -pno 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.
- 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:
Vista d'alt nivell, seguint només el primer pare:
* 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:
--no-merges és especialment útil combinat amb --author per veure el que ha escrit realment una persona sense comptar les seves integracions.
--ff-only: prohibir els commits de fusió
--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:
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
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:
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 canvienConsell 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:
- Una fusió que sigui fast-forward.
- 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:
- Els hashos complets dels seus dos pares, fent servir només ordres de lampisteria.
- Quin dels dos era la branca de destinació.
- Per què
git show <fusio>no mostra cap diff i com veure'l igualment. - Quants commits mostra
git log --onelinei quantsgit 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:
- Integrar a
mainuna funcionalitat de 12 commits desenvolupada durant dues setmanes. - Integrar una branca amb un únic commit que corregeix una errada al README.
- Posar al dia la teva branca de funcionalitat amb l'últim de
main, sabent que tu no has tocatmain. - 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 mainPredicció:
0 significa que main sí que és avantpassat de branca-ff: serà fast-forward.
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ó:
1 significa «no»: main té ara un commit propi. Han divergit, caldrà commit de fusió.
Sense conflicte: cada branca va tocar un fitxer diferent.
Solució 2:
O de manera més directa:
Era main, que és on érem en fusionar. El segon pare és la punta de branca-3b.
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)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:
-
--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 ambgit revert -m 1. A més, a--first-parentapareixerà com una sola entrada. -
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.
-
--ff-only. Si no has tocatmain, 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. -
--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 maini desprésgit merge <branca>.git mergemou 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-ancestorprediu l'escenari; la sintaxia...bserveix 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^2la portada. D'aquí--first-parent,git revert -m 1i el fet queHEAD^iHEAD~1difereixin a les fusions.git showno mostra diff per defecte; fes servir--cc. --no-ffforça el commit de fusió encara que no calgui, per documentar la integració;--ff-onlyel prohibeix, per garantir un historial lineal. Tots dos es poden fixar ambmerge.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
- Què és Git?
- Instal·lant Git
- Terminologia Bàsica de Git
- El Model de Dades de Git
- Configurant Git
- Configuració Inicial
Mòdul 2: Operacions Bàsiques de Git
- Creant un Repositori
- Clonant un Repositori
- Flux de Treball Bàsic de Git
- Preparant i Confirmant Canvis
- Inspeccionant Canvis amb git diff
- Visualitzant l'Historial de Confirmacions
Mòdul 3: Branques i Fusió
- Entenent les Branques
- Creant i Canviant Branques
- Fusionant Branques
- Estratègies de Fusió
- Resolent Conflictes de Fusió
- Gestió de Branques
Mòdul 4: Treballant amb Repositoris Remots
- Entenent els Repositoris Remots
- Afegint un Repositori Remot
- Autenticació amb Repositoris Remots
- Obtenint i Baixant Canvis
- Enviant Canvis
- Rastrejant Branques
Mòdul 5: Operacions Avançades de Git
- Rebase
- Rebase Interactiu
- Cherry-Picking de Confirmacions
- Desant Canvis Temporals
- Etiquetant Confirmacions
- Revertint Confirmacions
Mòdul 6: Eines i Tècniques de Git
- Usant Git Hooks
- Git Bisect
- Git Blame
- Git Log i Àlies
- Submòduls de Git
- Múltiples Còpies de Treball amb git worktree
Mòdul 7: Estratègies de Col·laboració i Flux de Treball
- Forks i Pull Requests
- Revisions de Codi amb Git
- Flux de Treball Git Flow
- GitHub Flow
- Trunk Based Development
- Integració Contínua amb Git
Mòdul 8: Bones Pràctiques i Consells de Git
- Escrivint Bons Missatges de Confirmació
- Mantenint un Historial Net
- Ignorant Fitxers amb .gitignore
- Atributs de Fitxer amb .gitattributes
- Bones Pràctiques de Seguretat
- Consells de Rendiment
Mòdul 9: Resolució de Problemes i Depuració
- Problemes Habituals de Git
- Desfent Canvis
- Resolent Divergències amb el Remot
- Recuperant Confirmacions Perdudes
- Tractant amb Repositoris Corruptes
- Tècniques Avançades de Depuració
