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 fetch descarrega i actualitza les teves referències remotes. No toca ni un sol fitxer del teu directori de treball. És una operació completament inofensiva.
  • git pull fa un fetch i, 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

  1. El punt de partida: el projecte ja és al servidor
  2. git fetch: què fa exactament
  3. L'abans i el després d'un fetch
  4. FETCH_HEAD: la referència que gairebé ningú coneix
  5. Inspeccionar el que has portat abans d'integrar-ho
  6. Integrar a mà: merge sobre origin/main
  7. git pull = fetch + integració
  8. Els modes de git pull
  9. Netejar referències obsoletes: --prune
  10. La Carla s'incorpora i es posa al dia
  11. Quan les històries divergeixen

  1. 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 bruno

Quatre commits al servidor i dos repositoris sincronitzats. A partir d'aquí treballem.

  1. git fetch: què fa exactament

En 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 push

I ara en Bruno:

cd /tmp/equip/bruno
git fetch origin
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 actualitzat

El 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

  1. Es connecta al remot i li pregunta quines referències té.
  2. Descarrega els objectes que a tu et falten (commits, arbres, blobs) i els desa a .git/objects/.
  3. Actualitza les referències remotes a .git/refs/remotes/origin/ segons el refspec configurat.

Les tres coses que NO fa

  1. No toca el teu directori de treball. Ni un fitxer. Ni un.
  2. No mou cap de les teves branques locals. El teu main continua exactament on era.
  3. 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:

git status
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.

git log --oneline -1 main
git log --oneline -1 origin/main
c2a8f1e Documenta la instal·lació al README
b4d7e93 Ajusta l'estil del peu de pàgina

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 --verbose

I una que no descarrega absolutament res i és utilíssima per diagnosticar:

git ls-remote origin
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.

  1. L'abans i el després d'un fetch

Vist 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çó:

  1. Han aparegut dos commits nous (9e2f4a7 i b4d7e93) a la base de dades d'objectes d'en Bruno. Hi són, complets, consultables sense connexió.
  2. main no s'ha mogut. Continua a c2a8f1e, exactament on era. Només origin/main ha 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:

git show b4d7e93
git log -p main..origin/main
git diff main origin/main

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.

  1. FETCH_HEAD: la referència que gairebé ningú coneix

Cada git fetch escriu un fitxer a l'arrel de .git/:

cat .git/FETCH_HEAD
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:

git log --oneline FETCH_HEAD
git diff FETCH_HEAD
git merge FETCH_HEAD

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.

  1. 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?

git log --oneline main..origin/main
b4d7e93 Ajusta l'estil del peu de pàgina
9e2f4a7 Afegeix el comptador de tasques pendents

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?

git log --oneline origin/main..main
(sense sortida)

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?

git diff --stat main origin/main
 app.js     | 1 +
 estils.css | 1 +
 2 files changed, 2 insertions(+)

Una ullada ràpida a la superfície del canvi. I si en vols el detall:

git diff main origin/main
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

git log --oneline --graph --all -6
* 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è?

git log --oneline --format='%h %an: %s' main..origin/main
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.

  1. Integrar a mà: merge sobre origin/main

En Bruno ja ha vist què hi ha i li sembla bé. Ara sí, integra:

git merge origin/main
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.

git log --oneline -1
b4d7e93 (HEAD -> main, origin/main) Ajusta l'estil del peu de pàgina

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 merge que 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.

  1. git pull = fetch + integració

I amb això, git pull deixa de tenir misteri:

git pull és una drecera de git fetch seguit d'una integració de la branca de seguiment a la teva branca actual.

És literalment així. Aquests dos blocs fan el mateix:

# El camí llarg, en dos passos
git fetch origin
git merge origin/main
# La drecera
git pull
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 No
Toca el directori de treball No
Pot donar conflictes No
Es pot desfer fàcilment No cal Sí (merge --abort, reset)
És segur sense mirar 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.

  1. Els modes de git pull

La 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:

git config --global pull.ff only

--ff-only: només si és un avanç net

git pull --ff-only

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:

fatal: Not possible to fast-forward, aborting.

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

git pull --no-rebase

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

git pull --rebase

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 merge

També es pot afinar per branca:

git config branch.main.rebase true

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.

  1. Netejar referències obsoletes: --prune

Amb 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.

git branch -r
  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

# Opció 1: durant el fetch
git fetch --prune
From /tmp/equip/gestor-tasques.git
 - [deleted]         (none)     -> origin/funcionalitat/comptador-tasques
 - [deleted]         (none)     -> origin/funcionalitat/filtre-pendents
# Opció 2: només netejar, sense portar res
git remote prune origin
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:

git remote prune --dry-run origin
git fetch --prune --dry-run

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:

git config --global fetch.prune true

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:

git config --global fetch.pruneTags true

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».

  1. 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.git
Cloning 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:

cd gestor-tasques
git log --oneline --graph -12
* 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:

git remote -v
origin	[email protected]:equip/gestor-tasques.git (fetch)
origin	[email protected]:equip/gestor-tasques.git (push)
git branch -a
* 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:

git switch documentacio/actualitzar-notes
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
b4d7e93 Ana Ferrer: Ajusta l'estil del peu de pàgina
# 2. Si hi ha novetats i no he divergit, posar-se al dia
git pull --ff-only
Updating 9e2f4a7..b4d7e93
Fast-forward
 estils.css | 1 +
 1 file changed, 1 insertion(+)
# 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.

  1. 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.

git fetch
git status
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:

git pull
fatal: Not possible to fast-forward, aborting.

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.

git config --global alias.gr "log --oneline --graph --all --decorate -20"

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:

  1. L'Ana confirma i envia dos commits.
  2. La Carla, abans de fer res, anota el hash del seu main i del seu origin/main, i desa una còpia del contingut d'un fitxer.
  3. La Carla executa git fetch.
  4. Demostra amb ordres que: (a) origin/main ha canviat, (b) main no, (c) el fitxer del disc és idèntic i (d) els commits nous ja són a la seva base de dades d'objectes.
  5. 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:

  1. Quants commits em falten?
  2. Qui els ha escrit i quan?
  3. Quins fitxers modifiquen i amb quantes línies cadascun?
  4. Què canvia exactament a app.js?
  5. Tinc jo algun commit que el servidor no tingui?
  6. Com queda el graf amb totes les referències?

Exercici 3: referències fantasma

  1. Crea al servidor tres branques a més de main.
  2. Clona i comprova que apareixen les tres com a referències remotes.
  3. Esborra'n dues al servidor.
  4. Fes un git fetch normal i comprova que les referències fantasma continuen allà.
  5. Explica per què el fetch no les ha eliminades.
  6. 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.js
5a1d8f39c2e7b4a6f1d8c3e9b2a5f7d4c6e8b1a3
5a1d8f39c2e7b4a6f1d8c3e9b2a5f7d4c6e8b1a3
7c1e9a4b8d2f6c3a5e7b9d1f4a8c2e6b  app.js
# 3. El fetch
git fetch origin
From /tmp/ex-fetch/servidor
   5a1d8f3..e4b7c92  main       -> origin/main
# 4a. origin/main SÍ que ha canviat
git rev-parse origin/main
e4b7c923f8a1d6b4e2c7f9a3d5b8c1e6f4a2d7b9
# 4b. main NO s'ha mogut
git rev-parse main
5a1d8f39c2e7b4a6f1d8c3e9b2a5f7d4c6e8b1a3

Exactament el mateix hash d'abans del fetch.

# 4c. El fitxer del disc és idèntic
md5sum app.js
git status --short
7c1e9a4b8d2f6c3a5e7b9d1f4a8c2e6b  app.js

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 -8
commit
e4b7c92 Ajusta l'estil del peu de pàgina
9f2a5c8 Afegeix el comptador de tasques pendents
commit 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.

# 5. Integrar
git merge origin/main
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:

git merge-base --is-ancestor 5a1d8f3 e4b7c92 && echo "Era avantpassat: fast-forward possible"
Era avantpassat: fast-forward possible

Solució 2:

Tornant a l'estat just després del fetch i abans del merge:

# 1. Quants commits em falten?
git rev-list --count main..origin/main
2
# 2. Qui i quan
git log --format='%h · %an · %ar · %s' main..origin/main
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
# 3. Quins fitxers i quantes línies
git diff --stat main origin/main
 app.js     | 1 +
 estils.css | 1 +
 2 files changed, 2 insertions(+)
# 4. Què canvia exactament en un fitxer
git diff main origin/main -- app.js
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.

# 5. Tinc jo res sense enviar?
git log --oneline origin/main..main
(sense sortida)

Res. No he divergit, així que la integració serà neta. La versió compacta de la mateixa pregunta:

git rev-list --left-right --count main...origin/main
0	2

Zero commits meus exclusius, dos de seus. (Aquesta sintaxi s'explica a fons a la lliçó 04-06.)

# 6. El graf amb tot
git log --oneline --graph --all --decorate
* 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 ..
# 2. Un clon nou les veu totes
git clone servidor.git altre
cd altre
git branch -r
  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/filtre
To /tmp/ex-prune/servidor.git
 - [deleted]         funcionalitat/comptador
 - [deleted]         funcionalitat/filtre
# 4. Un fetch normal a l'altre clon
cd /tmp/ex-prune/altre
git fetch origin
git branch -r
  origin/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:

git remote show origin | grep -A5 "Remote branches"
  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.

# 6a. Primera manera: durant el fetch
git fetch --prune
From /tmp/ex-prune/servidor
 - [deleted]         (none)     -> origin/funcionalitat/comptador
 - [deleted]         (none)     -> origin/funcionalitat/filtre
git branch -r
  origin/HEAD -> origin/main
  origin/documentacio/notes
  origin/main
# 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.prune
true

A partir d'ara, tots els fetch i pull netegen sols. I una comprovació tranquil·litzadora final:

# El prune NO toca les branques locals
git branch
* main
# 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 fetch descarrega 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 és git 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/main significa: la branca main del servidor s'ha desat a la teva referència local origin/main. El teu main no s'ha mogut.
  • FETCH_HEAD desa què s'ha portat a la darrera descàrrega, amb la marca not-for-merge a les branques que no són candidates a integrar-se. És el mecanisme intern del pull i permet portar d'un URL sense registrar cap remot.
  • Entre el fetch i 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 merge del 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, per pull.ff only) s'atura davant la divergència en comptes de decidir per tu; --no-rebase fusiona i crea commits de fusió; --rebase reaplica els teus commits i s'estudia a la lliçó 05-01.
  • git fetch --prune i git remote prune eliminen les referències de branques ja esborrades al servidor, que un fetch normal no neteja mai. Configura fetch.prune true i 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é origin configurat, una branca local i referències remotes per a les altres.
  • Si les històries divergeixen, pull --ff-only s'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

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