Amb l'autenticació resolta, el canal està obert. L'Ana ha pujat gestor-tasques al servidor de l'equip —el mecanisme exacte de l'enviament el desgranarem a la lliçó següent— i per primera vegada al curs hi ha objectes Git en una màquina diferent de la seva.
Ara toca l'altre sentit del viatge: rebre. I aquí ensopeguem amb la confusió més estesa de tot Git, la que provoca més ensurts i més preguntes als fòrums: la diferència entre git fetch i git pull.
La confusió és comprensible, perquè els noms no ajuden: fetch és «anar a buscar» i pull és «estirar d'alguna cosa». Sonen a sinònims. No ho són en absolut:
git fetchdescarrega i actualitza les teves referències remotes. No toca ni un sol fitxer del teu directori de treball. És una operació completament inofensiva.git pullfa unfetchi, tot seguit, integra el que ha descarregat a la teva branca actual. Sí que modifica els teus fitxers i pot provocar conflictes.
Entendre bé aquesta diferència canvia del tot la relació que tens amb els remots: passes de «executo pull i a veure què passa» a «miro què hi ha, decideixo i després integro». En aquesta lliçó la desgranem, i la Carla clona per fi el projecte i es posa al dia.
Contingut
- El punt de partida: el projecte ja és al servidor
git fetch: què fa exactament- L'abans i el després d'un
fetch FETCH_HEAD: la referència que gairebé ningú coneix- Inspeccionar el que has portat abans d'integrar-ho
- Integrar a mà:
mergesobreorigin/main git pull=fetch+ integració- Els modes de
git pull - Netejar referències obsoletes:
--prune - La Carla s'incorpora i es posa al dia
- Quan les històries divergeixen
- El punt de partida: el projecte ja és al servidor
Situació actual de l'equip:
flowchart TB
S["git.exemple.cat<br/>gestor-tasques.git (bare)<br/>main → c2a8f1e"]
A["Ana · Ubuntu<br/>main → c2a8f1e<br/>origin/main → c2a8f1e"]
B["Bruno · macOS<br/>main → c2a8f1e<br/>origin/main → c2a8f1e"]
C["Carla · Windows 11<br/>(encara res)"]
A --> S
S --> B
S -.->|"clone pendent"| C
L'Ana ha publicat. En Bruno ja ha actualitzat el seu clon. La Carla encara no té res.
Per reproduir-ho a la teva màquina, muntem l'escenari complet amb el que hem après a les lliçons anteriors:
mkdir -p /tmp/equip && cd /tmp/equip
# El servidor
git init --bare gestor-tasques.git
# El repositori de l'Ana
git init -b main ana
cd ana
echo "<h1>Gestor de tasques</h1>" > index.html
git add . && git commit -m "Estructura inicial del gestor de tasques"
echo "body { font-family: sans-serif; }" > estils.css
git add . && git commit -m "Afegeix els estils base del llistat"
echo "console.log('tasques');" > app.js
git add . && git commit -m "Afegeix l'esborrat de tasques al llistat"
echo "# Gestor de tasques" > README.md
git add . && git commit -m "Documenta la instal·lació al README"
git remote add origin /tmp/equip/gestor-tasques.git
git push -u origin main
cd ..
# El clon d'en Bruno
git clone /tmp/equip/gestor-tasques.git brunoQuatre commits al servidor i dos repositoris sincronitzats. A partir d'aquí treballem.
git fetch: què fa exactament
git fetch: què fa exactamentEn Bruno es posa a treballar al matí. L'Ana fa des d'ahir que afegeix coses i ha enviat dos commits nous. En Bruno no ho sap: recorda de la lliçó 04-01 que el seu origin/main és una foto amb data d'ahir i que ningú no l'avisarà.
Simulem la feina de l'Ana:
cd /tmp/equip/ana
echo "console.log('comptador');" >> app.js
git commit -am "Afegeix el comptador de tasques pendents"
echo "footer { color: gray; }" >> estils.css
git commit -am "Ajusta l'estil del peu de pàgina"
git pushI ara en Bruno:
remote: Enumerating objects: 8, done. remote: Counting objects: 100% (8/8), done. remote: Compressing objects: 100% (4/4), done. remote: Total 6 (delta 2), reused 0 (delta 0), pack-reused 0 Unpacking objects: 100% (6/6), 612 bytes | 612.00 KiB/s, done. From /tmp/equip/gestor-tasques.git c2a8f1e..b4d7e93 main -> origin/main
La línia que importa és l'última, i convé llegir-la amb cura:
c2a8f1e..b4d7e93 main -> origin/main
└───┬────────┘ └─┬─┘ └────┬────┘
d'on a on la branca la referència
ha avançat al local que
servidor s'ha actualitzatEl que s'ha actualitzat és origin/main, no main. Aquesta fletxa ho diu literalment: la branca main del servidor s'ha desat a la teva referència origin/main. És el refspec de la lliçó 04-02 en acció.
Les tres coses que fa un fetch
- Es connecta al remot i li pregunta quines referències té.
- Descarrega els objectes que a tu et falten (commits, arbres, blobs) i els desa a
.git/objects/. - Actualitza les referències remotes a
.git/refs/remotes/origin/segons el refspec configurat.
Les tres coses que NO fa
- No toca el teu directori de treball. Ni un fitxer. Ni un.
- No mou cap de les teves branques locals. El teu
maincontinua exactament on era. - No pot provocar conflictes. No hi ha res a combinar: només s'afegeixen objectes i es mouen punters de referències que no són teves.
La demostració és immediata:
On branch main Your branch is behind 'origin/main' by 2 commits, and can be fast-forwarded. (use "git pull" to update your local branch) nothing to commit, working tree clean
«nothing to commit, working tree clean»: el fetch no ha canviat res al disc de treball. L'única novetat és que ara Git sap que hi ha dos commits esperant, perquè pot comparar main amb origin/main. D'on surt exactament aquest missatge i com es calcula és el tema de la lliçó 04-06.
Dues referències, dos commits diferents. Aquest és l'estat normal després d'un fetch, i no té res d'anòmal.
La conseqüència pràctica més important de tota la lliçó:
git fetchés absolutament segur. No pot trencar res, no pot perdre feina, no pot provocar cap conflicte i no pot deixar-te a mitges de res. El pots executar quan vulguis, fins i tot amb canvis sense confirmar al directori de treball. L'única conseqüència és que estaràs més ben informat.
Variants útils
# Portar de tots els remots configurats
git fetch --all
# Només una branca concreta
git fetch origin main
# Portar també les etiquetes (lliçó 05-05)
git fetch --tags
# Veure què es portaria, sense portar res
git fetch --dry-run
# Sortida detallada de totes les referències, no només les que canvien
git fetch --verboseI una que no descarrega absolutament res i és utilíssima per diagnosticar:
b4d7e935f1c8a2e6d4b7f9a3c1e5b8d2f4a6c9e7 HEAD b4d7e935f1c8a2e6d4b7f9a3c1e5b8d2f4a6c9e7 refs/heads/main 7f3c9a28e4b1d6f9a2c5e8b3d7f1a4c6e9b2d5f8 refs/heads/documentacio/actualitzar-notes
Pregunta al servidor quines referències té i a quins commits apunten, sense descarregar ni un sol objecte. És la manera més barata de comprovar si hi ha novetats o si el remot respon.
- L'abans i el després d'un
fetch
fetchVist al graf, que és on s'entén millor:
flowchart TB
subgraph ABANS["ABANS del fetch — repositori d'en Bruno"]
direction LR
A1["1a4c8d6"] --> A2["8b6d3c2"] --> A3["4e7f2a9"] --> A4["c2a8f1e<br/><b>main</b><br/><b>origin/main</b>"]
end
subgraph DESPRES["DESPRÉS del fetch — repositori d'en Bruno"]
direction LR
B1["1a4c8d6"] --> B2["8b6d3c2"] --> B3["4e7f2a9"] --> B4["c2a8f1e<br/><b>main</b>"] --> B5["9e2f4a7"] --> B6["b4d7e93<br/><b>origin/main</b>"]
end
ABANS --> DESPRES
Fixa't en els dos detalls que resumeixen tota la lliçó:
- Han aparegut dos commits nous (
9e2f4a7ib4d7e93) a la base de dades d'objectes d'en Bruno. Hi són, complets, consultables sense connexió. mainno s'ha mogut. Continua ac2a8f1e, exactament on era. Nomésorigin/mainha avançat.
I com que els objectes ja són al seu disc, en Bruno els pot examinar amb tot el que va aprendre al mòdul 2, sense xarxa i sense haver integrat res:
Aquest és un dels grans avantatges del model distribuït: descarregar i decidir són dos actes separats. Pots portar-te la feina de tot l'equip al tren, amb connexió intermitent, i estudiar-la tranquil·lament després.
FETCH_HEAD: la referència que gairebé ningú coneix
FETCH_HEAD: la referència que gairebé ningú coneixCada git fetch escriu un fitxer a l'arrel de .git/:
b4d7e935f1c8a2e6d4b7f9a3c1e5b8d2f4a6c9e7 branch 'main' of /tmp/equip/gestor-tasques.git 7f3c9a28e4b1d6f9a2c5e8b3d7f1a4c6e9b2d5f8 not-for-merge branch 'documentacio/actualitzar-notes' of /tmp/equip/gestor-tasques.git
FETCH_HEAD registra què s'ha portat a la darrera operació de descàrrega. Té tres columnes:
| Columna | Contingut |
|---|---|
| 1 | El hash del commit descarregat |
| 2 | Buida si aquella branca és candidata a integrar-se; not-for-merge si no |
| 3 | De quina branca i de quin URL ve |
Aquest not-for-merge és la clau: marca les branques que s'han portat pel refspec però que no són la que integraries. Només la branca de seguiment de la teva branca actual queda sense la marca.
Es pot fer servir com qualsevol referència:
Per a què serveix a la pràctica
Primer: és el mecanisme intern de git pull. Quan executes git pull, Git fa un fetch i després un git merge FETCH_HEAD. Saber-ho desmitifica del tot l'ordre: no hi ha màgia, hi ha dos passos encadenats.
Segon: permet portar d'un URL sense registrar cap remot. Aquest cas passa més del que sembla, per exemple quan revises la feina d'algú extern:
# Portar una branca d'un repositori amb el qual no vull relació permanent
git fetch https://git.exemple.cat/carla/gestor-tasques.git funcionalitat/prototip
# No s'ha creat cap referència remota, però el commit és aquí:
git log --oneline FETCH_HEAD -3
git switch -c revisio-prototip FETCH_HEADÉs efímer: cada fetch sobreescriu FETCH_HEAD. Si necessitaràs aquell commit més endavant, crea una branca o una referència amb nom; si no, el recol·lector d'escombraries se l'acabarà enduent.
- Inspeccionar el que has portat abans d'integrar-ho
Aquí hi ha l'hàbit professional que distingeix qui entén Git de qui el pateix: entre el fetch i la integració hi ha un forat, i aquest forat és per mirar.
En Bruno ja té els commits de l'Ana al seu disc. Abans de barrejar-los amb la seva feina, els estudia.
Què em falta a mi?
Recorda del mòdul 2 la semàntica del rang a..b: «commits assolibles des de b però no des de a». És a dir: el que té el servidor i jo no.
Què tinc jo que ells no tinguin?
Buit: en Bruno no ha confirmat res des de la seva darrera sincronització. Si hi hagués commits aquí i al rang anterior, les històries haurien divergit; aquest cas el tractem a l'apartat 11.
Quins fitxers canvien i quant?
Una ullada ràpida a la superfície del canvi. I si en vols el detall:
diff --git a/app.js b/app.js
index 3f8a2c1..7d4e9b6 100644
--- a/app.js
+++ b/app.js
@@ -1 +1,2 @@
console.log('tasques');
+console.log('comptador');El unified diff de la lliçó 02-05, sense cap diferència: origin/main és una referència com qualsevol altra.
El graf complet
* b4d7e93 (origin/main) Ajusta l'estil del peu de pàgina * 9e2f4a7 Afegeix el comptador de tasques pendents * c2a8f1e (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
L'opció --all inclou totes les referències, també les remotes. És la vista que has de tenir a mà tan bon punt treballes amb un remot.
Qui ha fet què?
b4d7e93 Ana Ferrer: Ajusta l'estil del peu de pàgina 9e2f4a7 Ana Ferrer: Afegeix el comptador de tasques pendents
El resum en una taula
| Pregunta | Ordre |
|---|---|
| Què hi ha de nou al servidor? | git log --oneline main..origin/main |
| Què tinc jo sense enviar? | git log --oneline origin/main..main |
| Quants commits a cada banda? | git rev-list --left-right --count main...origin/main |
| Quins fitxers canvien? | git diff --stat main origin/main |
| Què canvia exactament? | git diff main origin/main |
| Com queda el graf? | git log --oneline --graph --all |
| Qui ho ha fet? | git log --format='%h %an: %s' main..origin/main |
Aquest és el flux recomanat: fetch → mirar → decidir → integrar. Costa vint segons i evita el desconcert d'un pull que de sobte ha modificat quinze fitxers que no esperaves.
- Integrar a mà:
merge sobre origin/main
merge sobre origin/mainEn Bruno ja ha vist què hi ha i li sembla bé. Ara sí, integra:
Updating c2a8f1e..b4d7e93 Fast-forward app.js | 1 + estils.css | 1 + 2 files changed, 2 insertions(+)
I això és exactament el git merge del mòdul 3. Res de nou. Com que en Bruno no tenia commits propis, main estava endarrerida sense haver divergit, i Git ha aplicat un fast-forward: moure el punter endavant. El mateix cas que vam estudiar amb les branques locals.
Ara les tres referències coincideixen: main, origin/main i la branca del servidor.
Aquest és el punt en què convé aturar-se un moment, perquè resumeix la promesa del final del mòdul 3:
Integrar la feina d'una altra persona és exactament el mateix
git mergeque has fet servir amb les teves pròpies branques. Fast-forward si no has divergit, fusió a tres bandes si sí, amb la possibilitat de conflictes que ja saps resoldre. Els remots no canvien res d'això: només canvien d'on venen els commits.
git pull = fetch + integració
git pull = fetch + integracióI amb això, git pull deixa de tenir misteri:
git pullés una drecera degit fetchseguit d'una integració de la branca de seguiment a la teva branca actual.
És literalment així. Aquests dos blocs fan el mateix:
flowchart TB
P["git pull"] --> F["1 · git fetch<br/>Descarrega objectes<br/>Actualitza origin/main"]
F --> M["2 · Integració<br/>merge (o rebase)<br/>d'origin/main a main"]
M --> R["Directori de treball<br/><b>modificat</b>"]
style F fill:#e8f4e8
style M fill:#f9e8e8
Les dues meitats tenen naturaleses oposades, i per això convé veure-les separades:
Pas 1: fetch |
Pas 2: integració | |
|---|---|---|
| Toca la xarxa | Sí | No |
| Toca el directori de treball | No | Sí |
| Pot donar conflictes | No | Sí |
| Es pot desfer fàcilment | No cal | Sí (merge --abort, reset) |
| És segur sense mirar | Sí | No |
Quan fer servir cadascun
Fes servir git fetch quan:
- Vols saber què hi ha de nou sense comprometre't a res.
- Tens canvis sense confirmar i no vols que res es mogui.
- Estàs enmig d'alguna cosa delicada.
- Vols revisar la feina d'una altra persona abans de barrejar-la.
- Et quedaràs sense connexió i ho vols tenir tot descarregat.
Fes servir git pull quan:
- Comences la jornada i només et vols posar al dia.
- Estàs segur que no has divergit.
- Treballes en una branca que només toques tu.
Recomanació honesta: si estàs començant, acostuma't a git fetch + mirar + integrar. És un pas més i quinze segons, però et dona un model mental correcte i evita el 90 % dels ensurts. Quan ja entenguis perfectament què passarà, git pull és una drecera legítima.
- Els modes de
git pull
git pullLa part de fetch del pull sempre és igual. El que canvia és com integra, i aquí hi ha tres comportaments possibles. Aquest és el motiu pel qual a la lliçó 01-06 vam configurar:
--ff-only: només si és un avanç net
Integra únicament si es pot resoldre amb un fast-forward, és a dir, si la teva branca no té cap commit propi. Si has divergit, s'atura sense fer res:
Això no és un error: és la configuració funcionant. Git t'està dient «aquí hi ha una decisió a prendre i no la prendré jo». És exactament el que volíem quan vam fixar pull.ff only a la configuració inicial: no crear mai commits de fusió per sorpresa.
És el mode més conservador i el millor mentre aprens.
--no-rebase: fusionar sempre
Integra amb git merge. Si pot fer fast-forward, el fa; si has divergit, crea un commit de fusió amb dos pares.
Aquest era el comportament per defecte històric de Git, i és la raó per la qual tants repositoris són plens de commits titulats Merge branch 'main' of https://…. Cadascun d'ells és algú que va executar git pull havent divergit, sense adonar-se que estava creant un commit.
Aquest soroll a l'historial és, precisament, un dels problemes que abordarà el mòdul 5.
--rebase: reaplicar els teus commits a sobre
En comptes de fusionar, reaplica els teus commits locals a sobre del que ha portat del servidor, produint un historial lineal sense commits de fusió.
És una tècnica potent i molt utilitzada, però reescriu els teus commits locals —els canvia el hash— i té regles importants sobre quan es pot i quan no. Es mereix una lliçó sencera, i la té: lliçó 05-01: Rebase. Fins llavors, queda't només amb que l'opció existeix i amb el que fa en una frase.
Comparativa
| Mode | Si no has divergit | Si has divergit | Historial |
|---|---|---|---|
--ff-only |
Fast-forward | S'atura | Lineal |
--no-rebase (merge) |
Fast-forward | Commit de fusió | Amb bifurcacions |
--rebase |
Fast-forward | Reaplica els teus commits | Lineal |
I la configuració corresponent, que ja coneixes de la lliçó 01-06:
# El que tenim configurat al curs
git config --global pull.ff only
# Alternatives (no les apliquis ara)
git config --global pull.rebase true # sempre rebase
git config --global pull.rebase false # sempre mergeTambé es pot afinar per branca:
Un detall: pull amb canvis sense confirmar
Si tens modificacions sense confirmar i la integració necessita tocar aquests mateixos fitxers, Git s'hi nega:
error: Your local changes to the following files would be overwritten by merge: app.js Please commit your changes or stash them before you merge. Aborting.
I fa bé: està protegint feina que només existeix al teu disc. Les opcions són confirmar, o desar els canvis temporalment amb git stash (que és el tema de la lliçó 05-04).
Fixa't que un git fetch en aquesta mateixa situació hauria funcionat sense cap problema, perquè no toca el directori de treball. Una raó més per preferir-lo.
- Netejar referències obsoletes:
--prune
--pruneAmb el temps apareix un problema menor però molest. L'equip integra la branca funcionalitat/comptador-tasques i l'esborra del servidor. Però al repositori d'en Bruno, la referència origin/funcionalitat/comptador-tasques continua allà per sempre.
Per què? Perquè el refspec per defecte només diu què portar, no què esborrar. Un fetch normal no elimina mai referències remotes.
origin/HEAD -> origin/main origin/main origin/funcionalitat/comptador-tasques ← ja no existeix al servidor origin/funcionalitat/filtre-pendents ← ja no existeix al servidor origin/documentacio/actualitzar-notes
Dues referències fantasma. I git remote show origin les delata:
Remote branches:
main tracked
documentacio/actualitzar-notes tracked
funcionalitat/comptador-tasques stale (use 'git remote prune' to remove)
funcionalitat/filtre-pendents stale (use 'git remote prune' to remove)Aquest stale és exactament el que vam veure a la lliçó 04-02: «tu tens aquesta referència, però al servidor ja no hi és.»
Les dues maneres de netejar
From /tmp/equip/gestor-tasques.git - [deleted] (none) -> origin/funcionalitat/comptador-tasques - [deleted] (none) -> origin/funcionalitat/filtre-pendents
Pruning origin URL: /tmp/equip/gestor-tasques.git * [pruned] origin/funcionalitat/comptador-tasques * [pruned] origin/funcionalitat/filtre-pendents
I per veure què s'esborraria sense esborrar res:
Fes-ho automàtic
Com que això s'ha de fer sempre i no té cap inconvenient, el raonable és configurar-ho d'una vegada:
A partir d'ara, tots els teus fetch i pull netegen les referències mortes. És un dels ajustos que més agraeix un repositori d'equip amb rotació de branques.
I si a més vols netejar etiquetes esborrades al servidor:
Amb aquesta darrera cal anar amb més compte, perquè les etiquetes solen ser permanents i esborrar-ne una localment pot no ser el que vols. Les etiquetes són el tema de la lliçó 05-05.
Molt important: prune esborra referències remotes, mai branques locals ni commits. Si tens una branca local funcionalitat/comptador-tasques, continua allà intacta després del prune. Només desapareix la nota que deia «el servidor tenia aquesta branca».
- La Carla s'incorpora i es posa al dia
Ha arribat el moment que vam anunciar en tancar el mòdul 3.
La Carla, des del seu Windows 11, amb Git instal·lat (lliçó 01-02), configurat (lliçó 01-06) i la seva clau SSH generada, carregada i pujada (lliçó 04-03), obre el Git Bash i escriu l'ordre que en Bruno va executar a la lliçó 02-02:
cd ~/projectes
git clone [email protected]:equip/gestor-tasques.gitCloning into 'gestor-tasques'... remote: Enumerating objects: 47, done. remote: Counting objects: 100% (47/47), done. remote: Compressing objects: 100% (28/28), done. remote: Total 47 (delta 15), reused 0 (delta 0), pack-reused 0 Receiving objects: 100% (47/47), 8.42 KiB | 8.42 MiB/s, done. Resolving deltas: 100% (15/15), done.
Però aquesta vegada no és el mateix clon que va fer en Bruno. Aquell era un repositori de dos commits acabat de néixer. Aquest té mesos de feina a dins:
* b4d7e93 (HEAD -> main, origin/main, origin/HEAD) Ajusta l'estil del peu de pàgina * 9e2f4a7 Afegeix el comptador de tasques pendents * c2a8f1e Fusiona el missatge de llista buida |\ | * 6d3f8b2 Mostra un missatge quan el llistat és buit * | 3b9e7d1 Afegeix l'exportació del llistat de tasques a CSV |/ * f7a3e92 Fusiona el filtre de tasques pendents |\ | * b2e6d3f Corregeix el focus del camp després d'afegir una tasca * | 8d4e6b2 Fusiona el comptador de tasques pendents |\ \ | |/ | * 9d1e4b7 Marca les tasques com a completades en fer clic * | c5d9b1e Documenta la instal·lació al README |/
Aquí hi ha tota la història del mòdul 3, amb les seves fusions a tres bandes, el seu squash i el seu conflicte resolt, descarregada íntegrament al portàtil de la Carla. Pot consultar qualsevol commit de qualsevol moment sense connexió, perquè —com sap des de la lliçó 04-01— el seu repositori és complet i independent.
I el que vam dir a la lliçó 02-02 sobre el que deixa configurat el clon:
origin [email protected]:equip/gestor-tasques.git (fetch) origin [email protected]:equip/gestor-tasques.git (push)
* main remotes/origin/HEAD -> origin/main remotes/origin/documentacio/actualitzar-notes remotes/origin/main
Fixa't en un detall que confon molta gent quan s'incorpora a un projecte: la Carla només té una branca local (main), encara que el servidor en tingui diverses. Les altres existeixen al seu repositori com a referències remotes, no com a branques pròpies. Per treballar en una:
branch 'documentacio/actualitzar-notes' set up to track 'origin/documentacio/actualitzar-notes'. Switched to a new branch 'documentacio/actualitzar-notes'
Git ha vist que no existeix aquesta branca local però sí origin/documentacio/actualitzar-notes, ha deduït què volia i ha creat la branca local amb el seu seguiment configurat. Aquest comportament s'anomena DWIM i és un dels temes de la lliçó 04-06.
La seva primera jornada de feina
La Carla ja és operativa. La seva rutina diària serà aquesta, i és la de qualsevol persona que treballa amb un repositori compartit:
# 1. En començar el dia: veure què ha passat mentre dormia
git fetch --prune
git log --oneline main..origin/main# 3. Obrir una branca per a la seva feina
git switch -c funcionalitat/ordre-per-data
# 4. Treballar, confirmar… i enviar (lliçó 04-05)Tres ordres al matí. Aquest és tot el ritual.
- Quan les històries divergeixen
Falta un cas, i cal anomenar-lo encara que no el desenvolupem aquí.
Suposa que la Carla ha confirmat dos commits a main mentre l'Ana n'enviava tres més. Ara les dues històries han divergit: cadascuna té commits que l'altra no té. És l'escenari que vam estudiar a la lliçó 03-01 amb branques locals, però ara una de les dues «branques» és en una altra màquina.
On branch main Your branch and 'origin/main' have diverged, and have 2 and 3 different commits each, respectively. (use "git pull" if you want to integrate the remote branch with yours)
Amb pull.ff only configurat:
Hi insistim: això no és una fallada, és la protecció funcionant. Git es nega a decidir per tu entre fusionar i reescriure, i fa bé: són decisions amb conseqüències diferents sobre l'historial de l'equip.
Les sortides existeixen i són diverses —fusionar explícitament, reaplicar els teus commits amb rebase, o replantejar la feina—, i cadascuna té les seves implicacions i les seves contraindicacions. Tot això, amb els criteris per triar i els procediments complets, és el tema de la lliçó 09-03: Resolent Divergències amb el Remot.
Aquí n'hi ha prou que sàpigues reconèixer el missatge, que entenguis per què apareix (les dues parts han avançat per separat des d'un ancestre comú) i que tinguis clar que no s'ha trencat ni s'ha perdut res: simplement hi ha una decisió pendent.
El mateix passarà en el sentit contrari, quan intentis enviar i el servidor rebutgi el teu push. És exactament la mateixa situació vista des de l'altra banda, i la veurem a la lliçó següent.
Errors Habituals i Consells
Error 1: creure que git fetch no ha fet res perquè «els fitxers continuen igual». Ha fet just el que havia de fer: descarregar objectes i actualitzar origin/main. Comprova-ho amb git log --oneline main..origin/main i veuràs tot el que ha portat.
Error 2: executar git pull a cegues i sorprendre's del resultat. Si has divergit i tens el pull en mode merge, acabes de crear un commit de fusió que potser no volies. fetch + mirar + integrar costa quinze segons i elimina les sorpreses.
Error 3: interpretar fatal: Not possible to fast-forward com un error greu. És pull.ff only fent la seva feina: hi ha divergència i Git no decideix per tu. Vegeu 09-03.
Error 4: no fer servir mai --prune. Al cap d'un any tens quaranta referències origin/… de branques que es van esborrar fa mesos, l'autocompletat és inservible i git branch -r no es pot llegir. Configura fetch.prune true i oblida-te'n.
Error 5: pensar que origin/main s'actualitza sola. És un fitxer del teu disc. Només canvia amb fetch, pull, clone o un push amb èxit. Si un company et diu «ja està pujat» i tu no ho veus, la teva primera ordre és git fetch.
Error 6: fer pull amb canvis sense confirmar i encallar-se amb l'error. El missatge és clar: confirma o desa'ls temporalment. I recorda que un fetch en aquesta mateixa situació hauria funcionat sense cap problema.
Error 7: confondre git remote prune amb git prune. Són ordres diferents: la primera neteja referències remotes obsoletes; la segona és una operació de manteniment de la base de dades d'objectes que elimina objectes inassolibles. No les barregis.
Consell 1: converteix git fetch --prune en la teva primera ordre del dia. És gratis, és segur i et situa. Després decideixes què fer.
Consell 2: desa un àlies per veure el graf amb les referències remotes.
Amb git gr veus d'un cop d'ull on ets tu, on és el servidor i quines branques hi ha pel mig. Els àlies es tracten a fons a la lliçó 06-04.
Consell 3: fes servir git ls-remote per comprovar el servidor sense descarregar res. És instantani i perfecte per respondre a «ja està pujat?» o «aquest remot respon?».
Consell 4: si viatjaràs o et quedaràs sense connexió, fes git fetch --all --tags abans. T'emportes tota la feina de l'equip al disc i la pots revisar, comparar branques i consultar l'historial sense xarxa.
Consell 5: git fetch és segur. Executa'l sense por. No hi ha cap situació en què un fetch faci malbé res. Interioritzar-ho és el que permet treballar amb remots amb tranquil·litat.
Exercicis
Exercici 1: demostrar que el fetch no toca res
Munta un servidor bare i dos clons que simulin l'Ana i la Carla. Després:
- L'Ana confirma i envia dos commits.
- La Carla, abans de fer res, anota el hash del seu
maini del seuorigin/main, i desa una còpia del contingut d'un fitxer. - La Carla executa
git fetch. - Demostra amb ordres que: (a)
origin/mainha canviat, (b)mainno, (c) el fitxer del disc és idèntic i (d) els commits nous ja són a la seva base de dades d'objectes. - Ara integra i comprova quin tipus d'integració ha fet Git i per què.
Exercici 2: el forat entre el fetch i la integració
Partint de l'exercici anterior, i sense integrar, respon amb una ordre a cada pregunta:
- Quants commits em falten?
- Qui els ha escrit i quan?
- Quins fitxers modifiquen i amb quantes línies cadascun?
- Què canvia exactament a
app.js? - Tinc jo algun commit que el servidor no tingui?
- Com queda el graf amb totes les referències?
Exercici 3: referències fantasma
- Crea al servidor tres branques a més de
main. - Clona i comprova que apareixen les tres com a referències remotes.
- Esborra'n dues al servidor.
- Fes un
git fetchnormal i comprova que les referències fantasma continuen allà. - Explica per què el
fetchno les ha eliminades. - Neteja-les de les dues maneres possibles i configura Git perquè no torni a passar.
Solucions
Solució 1:
mkdir -p /tmp/ex-fetch && cd /tmp/ex-fetch
git init --bare servidor.git
git clone servidor.git ana
cd ana
echo "<h1>Gestor de tasques</h1>" > index.html
echo "console.log('tasques');" > app.js
git add . && git commit -m "Estructura inicial del gestor de tasques"
git push -u origin main
cd ..
git clone servidor.git carla# 1. L'Ana treballa i envia
cd /tmp/ex-fetch/ana
echo "console.log('comptador');" >> app.js
git commit -am "Afegeix el comptador de tasques pendents"
echo "footer { color: gray; }" > estils.css
git add . && git commit -m "Ajusta l'estil del peu de pàgina"
git push# 2. La Carla anota el seu estat ABANS
cd /tmp/ex-fetch/carla
git rev-parse main origin/main
md5sum app.js5a1d8f39c2e7b4a6f1d8c3e9b2a5f7d4c6e8b1a3 5a1d8f39c2e7b4a6f1d8c3e9b2a5f7d4c6e8b1a3 7c1e9a4b8d2f6c3a5e7b9d1f4a8c2e6b app.js
Exactament el mateix hash d'abans del fetch.
Mateixa suma de comprovació i git status --short sense cap línia: el directori de treball està intacte.
# 4d. …però els commits nous JA són al seu repositori
git cat-file -t e4b7c92
git log --oneline main..origin/main
git show --stat e4b7c92 | head -8commit e4b7c923f8a1d6b4e2c7f9a3d5b8c1e6f4a2d7b9 Author: Ana Ferrer <[email protected]> Date: Sat Aug 1 10:14:22 2026 +0200 Ajusta l'estil del peu de pàgina estils.css | 1 +
Aquesta és la demostració clau: la Carla pot examinar el contingut complet de commits que no ha integrat, sense connexió, perquè els objectes són al seu .git/objects/. El que no ha canviat és la seva branca ni els seus fitxers.
Updating 5a1d8f3..e4b7c92 Fast-forward app.js | 1 + estils.css | 1 + 2 files changed, 2 insertions(+)
Fast-forward, i la raó és al graf: la Carla no havia confirmat res, així que el seu main era un avantpassat directe d'origin/main. No hi havia divergència i no calia cap commit de fusió: n'hi havia prou amb moure el punter. Es pot comprovar formalment:
Solució 2:
Tornant a l'estat just després del fetch i abans del merge:
e4b7c92 · Ana Ferrer · fa 4 minuts · Ajusta l'estil del peu de pàgina 9f2a5c8 · Ana Ferrer · fa 5 minuts · Afegeix el comptador de tasques pendents
diff --git a/app.js b/app.js
index 3f8a2c1..7d4e9b6 100644
--- a/app.js
+++ b/app.js
@@ -1 +1,2 @@
console.log('tasques');
+console.log('comptador');El -- separa les referències de les rutes, perquè Git no dubti si app.js és un fitxer o una branca.
Res. No he divergit, així que la integració serà neta. La versió compacta de la mateixa pregunta:
Zero commits meus exclusius, dos de seus. (Aquesta sintaxi s'explica a fons a la lliçó 04-06.)
* e4b7c92 (origin/main, origin/HEAD) Ajusta l'estil del peu de pàgina * 9f2a5c8 Afegeix el comptador de tasques pendents * 5a1d8f3 (HEAD -> main) Estructura inicial del gestor de tasques
Una sola línia recta amb main al darrere: la imatge visual de «puc fer fast-forward».
Solució 3:
mkdir -p /tmp/ex-prune && cd /tmp/ex-prune
git init --bare servidor.git
git clone servidor.git feina
cd feina
echo "inici" > f.txt
git add . && git commit -m "Estructura inicial del gestor de tasques"
git push -u origin main
# 1. Tres branques més al servidor
for r in funcionalitat/comptador funcionalitat/filtre documentacio/notes; do
git switch -c "$r" > /dev/null 2>&1
git commit --allow-empty -m "Treball a $r" > /dev/null
git push -u origin "$r" > /dev/null 2>&1
done
git switch main
cd ..origin/HEAD -> origin/main origin/documentacio/notes origin/funcionalitat/comptador origin/funcionalitat/filtre origin/main
# 3. Esborrar-ne dues AL SERVIDOR
cd /tmp/ex-prune/feina
git push origin --delete funcionalitat/comptador
git push origin --delete funcionalitat/filtreorigin/HEAD -> origin/main origin/documentacio/notes origin/funcionalitat/comptador origin/funcionalitat/filtre origin/main
Les dues fantasma continuen allà. I git remote show ho confirma:
Remote branches:
documentacio/notes tracked
main tracked
refs/remotes/origin/funcionalitat/comptador stale (use 'git remote prune' to remove)
refs/remotes/origin/funcionalitat/filtre stale (use 'git remote prune' to remove)5. Per què el fetch no les ha eliminades: perquè el refspec per defecte, +refs/heads/*:refs/remotes/origin/*, només descriu què portar. És una instrucció de còpia, no de sincronització: diu «les branques que existeixin allà, desa-les aquí», i no diu res sobre què fer amb les que ja no existeixen. Esborrar referències és un comportament addicional que cal demanar explícitament, i Git és conservador per defecte: no elimina coses sense que li ho manis.
From /tmp/ex-prune/servidor - [deleted] (none) -> origin/funcionalitat/comptador - [deleted] (none) -> origin/funcionalitat/filtre
# 6b. Segona manera (comprovant-ho primer en sec)
git remote prune --dry-run origin
git remote prune origin# 6c. Que no torni a passar
git config --global fetch.prune true
git config --global --get fetch.pruneA partir d'ara, tots els fetch i pull netegen sols. I una comprovació tranquil·litzadora final:
# Ni els objectes: el commit de la branca esborrada continua accessible pel seu hash
git cat-file -t 4c9e2a7 2>/dev/null || echo "(ja recollit)"prune esborra referències remotes, res més.
Conclusió
Aquesta lliçó ha desfet la confusió més estesa de Git. L'essencial:
git fetchdescarrega objectes i actualitza les teves referències remotes. No toca el directori de treball, no mou les teves branques i no pot provocar conflictes. És una operació completament segura que pots executar en qualsevol moment, fins i tot amb canvis sense confirmar.git pullésgit fetch+ una integració a la teva branca actual. La primera meitat és inofensiva; la segona modifica els teus fitxers i pot donar conflictes. Veure-les per separat és el que treu el misteri a l'ordre.- La sortida
c2a8f1e..b4d7e93 main -> origin/mainsignifica: la brancamaindel servidor s'ha desat a la teva referència localorigin/main. El teumainno s'ha mogut. FETCH_HEADdesa què s'ha portat a la darrera descàrrega, amb la marcanot-for-mergea les branques que no són candidates a integrar-se. És el mecanisme intern delpulli permet portar d'un URL sense registrar cap remot.- Entre el
fetchi la integració hi ha un forat, i és per mirar:git log main..origin/main,git diff --stat main origin/main,git log --oneline --graph --all. Vint segons que eliminen les sorpreses. - Integrar la feina remota és el mateix
git mergedel mòdul 3: fast-forward si no has divergit, fusió a tres bandes si sí. Els remots només canvien d'on venen els commits. - Els modes del
pull:--ff-only(el nostre, perpull.ff only) s'atura davant la divergència en comptes de decidir per tu;--no-rebasefusiona i crea commits de fusió;--rebasereaplica els teus commits i s'estudia a la lliçó 05-01. git fetch --pruneigit remote pruneeliminen les referències de branques ja esborrades al servidor, que unfetchnormal no neteja mai. Configurafetch.prune truei oblida-te'n. Només esborra referències remotes: mai branques locals ni commits.- La Carla ja és a dins: ha clonat un repositori amb història completa, té
originconfigurat, una branca local i referències remotes per a les altres. - Si les històries divergeixen,
pull --ff-onlys'atura. No és una fallada: és una decisió pendent. El tractament complet, a la lliçó 09-03.
El que ve
Ja saps rebre. Falta l'altra meitat, i és la que té conseqüències per a tot l'equip: enviar.
A la lliçó 04-05: Enviant Canvis veurem què envia exactament git push (objectes i l'actualització d'una referència), què fa aquell -u que apareix a tots els tutorials, com s'escriu un refspec d'enviament i per què es relaciona directament amb el que vam estudiar a 04-02. I sobretot: per què un push pot ser rebutjat, què significa exactament non-fast-forward, i per què --force-with-lease és gairebé sempre la resposta correcta quan --force pot destruir la feina d'un company sense deixar rastre.
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ó
