Al llarg d'aquest mòdul han anat apareixent missatges que hem anat deixant per a després:

Your branch is ahead of 'origin/main' by 2 commits.
branch 'main' set up to track 'origin/main'.
  main  b4d7e93 [origin/main: behind 3] Ajusta l'estil del peu de pàgina

I també comportaments que Git ha endevinat pel seu compte: git push a seques sabent on ha d'anar, git pull sense arguments, i aquell git switch documentacio/actualitzar-notes que va crear una branca local que no existia.

Tot això apunta al mateix concepte, i és el que tanca el mòdul: les branques de seguiment. Un mecanisme senzill —dues línies de configuració per branca— del qual depenen la meitat de les comoditats de Git i la majoria dels seus missatges d'estat.

De passada aclarirem d'una vegada per totes la confusió que arrosseguem des de la lliçó 04-01: main, origin/main i el main del servidor són tres coses diferents que es diuen semblant i que la gent barreja constantment. Quan acabis aquesta lliçó, aquesta distinció serà automàtica, i amb ella el mòdul sencer encaixarà.

Contingut

  1. Què és una branca de seguiment
  2. On viu: branch.<branca>.remote i branch.<branca>.merge
  3. Les tres coses que es diuen main
  4. D'on surt «ahead of 'origin/main' by 2 commits»
  5. git branch -vv: l'estat de totes d'una ullada
  6. Establir, canviar i treure l'upstream
  7. Crear una branca local a partir d'una de remota: el DWIM de Git
  8. El cas ambigu de diversos remots
  9. @{upstream} i @{push}: referir-se al seguiment
  10. Tancament del mòdul

  1. Què és una branca de seguiment

La definició:

Una branca de seguiment (tracking branch) és una branca local que té registrada una associació amb una referència remota concreta. A aquella referència associada se l'anomena el seu upstream («aigües amunt»).

Dit planerament: la teva branca main «sap» que la seva parella al servidor és origin/main. I amb aquesta dada, Git pot:

  • Enviar sense arguments: git push sap a quin remot i a quina branca.
  • Rebre sense arguments: git pull també.
  • Informar-te del desfasament: «vas 2 commits per davant i 3 per darrere».
  • Oferir dreceres: @{upstream} per referir-te a la parella sense escriure'n el nom.

Sense aquesta associació, tot funciona igual, però cal escriure-ho tot:

# Sense seguiment
git push origin funcionalitat/ordre-alfabetic
git pull origin funcionalitat/ordre-alfabetic
git log funcionalitat/ordre-alfabetic..origin/funcionalitat/ordre-alfabetic

# Amb seguiment
git push
git pull
git log @{u}..

Un avís terminològic important, perquè la documentació de Git no sempre és consistent. Hi ha dos usos de «branca de seguiment» que convé no barrejar:

Expressió A què es refereix
Branca de seguiment (tracking branch) Una branca local que té un upstream configurat. Per exemple, la teva main
Branca de seguiment remota (remote-tracking branch) La referència remota en si: origin/main. No és una branca teva

I recorda a més l'altre sentit de la paraula upstream, el de la lliçó 04-01: el nom convencional del remot que apunta al projecte original del qual vas fer un fork (mòdul 7). Tres usos de dues paraules. Aquí, sempre que diguem upstream, ens referim a la referència remota associada a una branca local.

  1. On viu: branch.<branca>.remote i branch.<branca>.merge

Com tot a Git, això no és màgia: són dues línies en un fitxer de text.

cat .git/config
[remote "origin"]
	url = https://git.exemple.cat/equip/gestor-tasques.git
	fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
	remote = origin
	merge = refs/heads/main
[branch "correccio/focus-despres-esborrar"]
	remote = origin
	merge = refs/heads/correccio/focus-despres-esborrar

Cada branca amb seguiment té la seva pròpia secció:

Clau Valor Què significa
branch.main.remote origin Amb quin remot parla aquesta branca
branch.main.merge refs/heads/main Quina branca d'aquell remot és la seva parella

Un detall que despista en llegir-ho per primera vegada: merge = refs/heads/main és el nom de la branca al servidor, no refs/remotes/origin/main. Té la seva lògica: la configuració diu «la meva parella és la branca main del repositori remot»; ja s'encarrega el refspec de saber que aquella branca es desa localment a refs/remotes/origin/main.

Es consulten com qualsevol altra configuració:

git config branch.main.remote
git config branch.main.merge
origin
refs/heads/main

I també existeix una clau opcional que potser trobaràs:

git config branch.main.rebase

Si val true, git pull en aquella branca farà servir --rebase en comptes de fusionar (lliçó 05-01).

Interioritza això: el seguiment és configuració local del teu repositori. No viatja amb el push, no la veu ningú més i cada persona de l'equip té la seva. En Bruno pot tenir la seva main seguint origin/main mentre la Carla la té seguint mirall/main, i cap dels dos se n'assabenta.

  1. Les tres coses que es diuen main

Aquest és l'apartat clau de la lliçó, i mereix tota la teva atenció. Aquestes tres coses es diuen semblant i són diferents:

main origin/main main del servidor
Què és La teva branca local La teva foto del servidor La branca real del repositori remot
On és .git/refs/heads/main, al teu disc .git/refs/remotes/origin/main, al teu disc En una altra màquina
Qui la mou Tu, en confirmar o fusionar Git, en fer fetch/pull/push Qualsevol de l'equip, en enviar
Hi pots confirmar No No directament
Es pot quedar antiquada És teva: és on la vas deixar Sí, constantment És la veritat del moment
Es veu amb git branch git branch -r git ls-remote origin
flowchart TB
    subgraph TU["EL TEU DISC"]
        M["<b>main</b><br/>refs/heads/main<br/>La mous tu en confirmar"]
        OM["<b>origin/main</b><br/>refs/remotes/origin/main<br/>Foto del servidor.<br/>La mou fetch/pull/push"]
    end
    subgraph SRV["UNA ALTRA MÀQUINA"]
        SM["<b>main</b><br/>refs/heads/main<br/>La mou qui enviï"]
    end

    M -.->|"seguiment configurat:<br/>branch.main.remote = origin<br/>branch.main.merge = refs/heads/main"| OM
    OM <-->|"fetch: actualitza la foto<br/>push: actualitza el servidor"| SM

    style M fill:#fff0e8
    style OM fill:#e8f0ff
    style SM fill:#e8f4e8

La regla mnemotècnica

Quan alguna cosa de remots et desconcerti, pregunta't: de quina de les tres estic parlant?

Exemples d'aplicació immediata:

  • «El meu company diu que ja està pujat però jo no ho veig.» → Esteu parlant del main del servidor; el que tu mires és el teu origin/main, que està antiquat. Solució: git fetch.
  • «He fet fetch i els meus fitxers no han canviat.» → El fetch actualitza origin/main; els teus fitxers depenen de main. Correcte.
  • «He fet git reset --hard origin/main i he perdut els meus commits.» → Has mogut la teva main on és la teva foto del servidor. Els commits continuen al reflog (lliçó 09-04), però la branca ja no els assoleix.
  • «git push diu everything up-to-date però el meu canvi no hi és.» → La teva main és on és el teu origin/main; el canvi no es va arribar a confirmar.

Un experiment que fixa la idea millor que qualsevol explicació:

# 1. Confirmar alguna cosa: es mou NOMÉS main
git commit --allow-empty -m "Prova"
git rev-parse main origin/main
d5e9b2f8a3c1e6b4d7f2a9c5e8b1d4f6a3c7e9b2   ← main ha avançat
b4d7e935f1c8a2e6d4b7f9a3c1e5b8d2f4a6c9e7   ← origin/main continua igual
# 2. Enviar: ara es mouen totes dues (i la del servidor)
git push
git rev-parse main origin/main
d5e9b2f8a3c1e6b4d7f2a9c5e8b1d4f6a3c7e9b2
d5e9b2f8a3c1e6b4d7f2a9c5e8b1d4f6a3c7e9b2

Un push amb èxit actualitza també el teu origin/main, i és lògic: Git acaba de parlar amb el servidor i sap amb certesa on ha quedat la seva referència.

  1. D'on surt «ahead of 'origin/main' by 2 commits»

git status
On branch main
Your branch is ahead of 'origin/main' by 2 commits.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean

Aquest missatge no surt de cap lloc màgic: Git compara la teva branca amb el seu upstream i compta commits a cada banda. Només ho pot fer perquè el seguiment està configurat; sense ell, git status no diria res del servidor.

L'ordre que fa el càlcul

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

Desglossem-ho, perquè cada peça importa:

  • main...origin/maintres punts, no dos. És la diferència simètrica: tots els commits assolibles des d'una de les dues referències però no des de totes dues. És a dir, l'exclusiu de cada banda.
  • --left-right — marca cada commit segons de quina banda ve: < per a l'esquerra (main), > per a la dreta (origin/main).
  • --count — en comptes de llistar els commits, compta quants n'hi ha de cada banda.

El resultat es llegeix així:

2	0
│   └── commits que té origin/main i no main   → "behind" (per darrere)
└────── commits que té main i no origin/main   → "ahead"  (per davant)

Dos punts davant de tres punts, que és del que més es confon la gent:

Sintaxi Significat Ús típic
main..origin/main El que té origin/main i no main «Què em falta per portar?»
origin/main..main El que té main i no origin/main «Què em falta per enviar?»
main...origin/main L'exclusiu de cada banda «Quant hem divergit?»

Sense --count, es veu commit a commit:

git rev-list --left-right --oneline main...origin/main
<d5e9b2f Retorna el focus al camp després d'esborrar una tasca
<8a3f7c1 Corregeix el focus després d'esborrar una tasca
>b4d7e93 Ajusta l'estil del peu de pàgina

Dos de meus (<) i un de seu (>): hem divergit, i git status ho dirà així:

Your branch and 'origin/main' have diverged,
and have 2 and 1 different commits each, respectively.

Els quatre estats possibles

rev-list retorna Situació Què diu git status Què cal fer
0 0 Sincronitzats «up to date with 'origin/main'» Res
N 0 Per davant «ahead of 'origin/main' by N commits» git push
0 N Per darrere «behind 'origin/main' by N commits, and can be fast-forwarded» git pull
N M Divergits «have diverged» Integrar (vegeu 09-03)
flowchart TB
    B["Base comuna"]
    B --> A1["A"] --> A2["B<br/><b>main</b>"]
    B --> C1["C"] --> C2["D<br/><b>origin/main</b>"]

    N["main...origin/main → 2 i 2<br/>HAN DIVERGIT"]

    style A2 fill:#fff0e8
    style C2 fill:#e8f0ff

Un avís fonamental sobre aquests números

El desfasament es calcula contra el teu origin/main, que és una foto. No contra el servidor.

Si fa dos dies que no fas fetch, aquell «per davant 2 commits» pot ser completament fals: el servidor pot haver avançat quinze vegades. git status no consulta mai la xarxa.

Per això la primera ordre del dia és git fetch: sense ella, tots aquests números descriuen un passat.

I per això existeix aquesta opció, que fa les dues coses de cop:

git fetch && git status

Hi ha una opció de git status que promet fer-ho sola:

git status -uno --ahead-behind

Però --ahead-behind només controla si es calcula o no el desfasament; no porta res del servidor. L'única manera d'actualitzar la foto continua sent el fetch.

  1. git branch -vv: l'estat de totes d'una ullada

A la lliçó 03-06 vam esmentar -vv i vam dir que tindria sentit amb remots. Ha arribat el moment.

git branch -v
  correccio/focus-despres-esborrar  d5e9b2f Retorna el focus al camp després d'esborrar una tasca
  documentacio/actualitzar-notes    7f3c9a2 Actualitza les notes internes amb el nou flux
* main                              b4d7e93 Ajusta l'estil del peu de pàgina
  experiment/local                  3a7e9c2 Prova una idea sense publicar

Amb la doble v:

git branch -vv
  correccio/focus-despres-esborrar  d5e9b2f [origin/correccio/focus-despres-esborrar] Retorna el focus al camp després d'esborrar una tasca
  documentacio/actualitzar-notes    7f3c9a2 [origin/documentacio/actualitzar-notes: ahead 1] Actualitza les notes internes amb el nou flux
* main                              b4d7e93 [origin/main: behind 3] Ajusta l'estil del peu de pàgina
  experiment/local                  3a7e9c2 Prova una idea sense publicar

Els claudàtors són la informació nova, i cada línia explica una cosa diferent:

Branca Claudàtors Lectura
correccio/focus-despres-esborrar [origin/correccio/…] upstream i està sincronitzada
documentacio/actualitzar-notes [origin/…: ahead 1] Un commit local sense enviar
main [origin/main: behind 3] Tres commits al servidor sense portar
experiment/local (res) No té upstream: és purament local

Aquest darrer cas és el que cal saber reconèixer. Una branca sense claudàtors no existeix a cap servidor: si perds el disc, perds aquella feina. És perfectament legítim tenir branques així (experiments, proves), però convé saber quines són.

I també pot aparèixer aquest:

  funcionalitat/antiga              9d1e4b7 [origin/funcionalitat/antiga: gone] Marca les tasques com a completades

gone significa que la branca tenia upstream però aquell upstream ja no existeix: algú va esborrar la branca al servidor i tu has fet fetch --prune. És el senyal habitual de «aquesta branca ja s'ha integrat i s'ha netejat; pots esborrar la teva».

Una ordre molt útil per a la neteja periòdica:

# Llistar les branques l'upstream de les quals ha desaparegut
git branch -vv | grep ': gone]'
  funcionalitat/antiga              9d1e4b7 [origin/funcionalitat/antiga: gone] Marca les tasques com a completades
  funcionalitat/exportar-csv        e9a2c5f [origin/funcionalitat/exportar-csv: gone] Ara sí

Aquestes són les candidates naturals a git branch -d, i complementa perfectament el --merged de la lliçó 03-06 (que fallava amb les branques integrades mitjançant squash: si la plataforma la va esborrar en integrar-la, aquí apareixerà encara que --merged no la detecti).

Altres maneres de consultar el seguiment:

# Només el nom de l'upstream de la branca actual
git rev-parse --abbrev-ref @{upstream}
origin/main
# Amb format a mida
git branch --format='%(refname:short) → %(upstream:short) [%(upstream:track)]'
correccio/focus-despres-esborrar → origin/correccio/focus-despres-esborrar []
documentacio/actualitzar-notes → origin/documentacio/actualitzar-notes [ahead 1]
main → origin/main [behind 3]
experiment/local →  []

Els marcadors %(upstream:short) i %(upstream:track) són els mateixos de git for-each-ref que vam fer servir a la lliçó 03-06 per al llistat bonic de branques.

  1. Establir, canviar i treure l'upstream

Hi ha quatre camins per configurar el seguiment, i convé conèixer-los tots perquè cadascun apareix en un moment diferent.

  1. git push -u (el més habitual)

git push -u origin funcionalitat/ordre-alfabetic

Envia i configura el seguiment en un sol pas. És el camí natural quan publiques una branca nova, com vam veure a la lliçó 04-05.

  1. Automàticament, amb push.autoSetupRemote

git config --global push.autoSetupRemote true

Ja el tenim configurat des de la lliçó 01-06. Amb ell, un git push a seques sobre una branca sense upstream la crea al servidor i configura el seguiment, sense -u:

git switch -c funcionalitat/ordre-alfabetic
git commit -am "Mostra les tasques ordenades alfabèticament"
git push
 * [new branch]      funcionalitat/ordre-alfabetic -> funcionalitat/ordre-alfabetic
branch 'funcionalitat/ordre-alfabetic' set up to track 'origin/funcionalitat/ordre-alfabetic'.

Sense aquest ajust, aquell git push hauria fallat amb el clàssic:

fatal: The current branch funcionalitat/ordre-alfabetic has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin funcionalitat/ordre-alfabetic

Un missatge que has vist mil vegades si has fet servir Git sense aquesta configuració.

  1. git branch --set-upstream-to (a posteriori)

Per a una branca que ja existeix als dos llocs però no està aparellada:

git branch --set-upstream-to=origin/main main
branch 'main' set up to track 'origin/main'.

Hi ha una forma curta amb -u (que aquí significa el mateix que a push):

git branch -u origin/main

Sense nom de branca, s'aplica a l'actual. És l'ordre que necessites quan has creat el repositori amb init i has afegit el remot després, o quan la configuració s'ha perdut per algun motiu.

Es pot apuntar a una branca amb un altre nom, cosa útil en casos de migració:

git branch --set-upstream-to=origin/desenvolupament main

Ara la teva main local segueix origin/desenvolupament. És estrany, però perfectament vàlid: l'associació no exigeix que els noms coincideixin.

  1. En crear la branca

# Explícit
git switch -c la-meva-copia --track origin/main

# O directament des de la referència remota (el DWIM de l'apartat 7)
git switch main

Treure el seguiment

git branch --unset-upstream
git status
On branch main
nothing to commit, working tree clean

Fixa't en el que ha desaparegut: la línia del desfasament. Sense upstream, Git no té amb què comparar i git status es queda mut respecte al servidor. És la millor demostració que aquell missatge depèn enterament d'aquesta configuració.

El tornem a posar:

git branch -u origin/main

Resum

Ordre Quan
git push -u origin <branca> En publicar una branca per primera vegada
git push (amb push.autoSetupRemote) Igual, però automàtic
git branch -u origin/<branca> La branca ja existeix a totes dues bandes, sense aparellar
git switch -c <local> --track origin/<branca> En crear-ne una de local des d'una de remota, amb nom diferent
git switch <branca> (DWIM) En crear-ne una de local des d'una de remota, amb el mateix nom
git branch --unset-upstream Per desvincular-la

  1. Crear una branca local a partir d'una de remota: el DWIM de Git

La Carla acaba de clonar i vol treballar en la branca de documentació de l'Ana. Només té una branca local:

git branch
* main
git branch -r
  origin/HEAD -> origin/main
  origin/correccio/focus-despres-esborrar
  origin/documentacio/actualitzar-notes
  origin/main

Les referències remotes no són branques seves, com sabem des de la lliçó 04-01: són de només lectura i no hi pot confirmar. Necessita una branca local.

El camí explícit seria aquest:

git switch -c documentacio/actualitzar-notes --track origin/documentacio/actualitzar-notes

Llarg i redundant. Per això Git té una drecera:

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'

Això és el DWIM de Git (Do What I Mean, «fes el que vull dir»). El raonament intern és:

  1. Existeix una branca local anomenada documentacio/actualitzar-notes? No.
  2. Existeix exactament una referència remota amb aquest nom? , origin/documentacio/actualitzar-notes.
  3. Llavors la persona vol treballar en aquella branca: creo la local a partir d'ella i configuro el seguiment.

I el resultat és exactament el mateix que la forma llarga:

git branch -vv
* documentacio/actualitzar-notes 7f3c9a2 [origin/documentacio/actualitzar-notes] Actualitza les notes internes amb el nou flux
  main                           b4d7e93 [origin/main] Ajusta l'estil del peu de pàgina

El mateix funciona amb checkout per compatibilitat, encara que com sabem des de la lliçó 03-02 és preferible switch:

git checkout documentacio/actualitzar-notes    # equivalent, però val més switch

Els límits del DWIM

No funciona si el nom local ja existeix:

git switch main

Si ja tens main local, això simplement canvia a la teva branca existent. No crea res ni reconfigura res.

No funciona si no has fet fetch:

git switch funcionalitat/acabada-de-crear
fatal: invalid reference: funcionalitat/acabada-de-crear

Si un company acaba de crear aquella branca i tu no has portat les referències, Git no la coneix. Solució: git fetch primer. És un error molt habitual en incorporar-se a una feina en curs.

No funciona amb diversos remots ambigus: és l'apartat següent.

Desactivar-lo

Hi ha una configuració per exigir sempre la forma explícita:

git config --global checkout.guess false

Amb ella, git switch <branca> només funciona amb branques locals existents. La majoria de la gent prefereix el DWIM activat, però convé saber que existeix si el comportament automàtic t'incomoda.

  1. El cas ambigu de diversos remots

Quan hi ha més d'un remot amb una branca del mateix nom, el DWIM no pot endevinar i es rendeix.

Suposem que en Bruno té dos remots, com a la lliçó 04-02:

git remote -v
origin		https://git.exemple.cat/equip/gestor-tasques.git (fetch)
origin		https://git.exemple.cat/equip/gestor-tasques.git (push)
personal	[email protected]:bruno/gestor-tasques.git (fetch)
personal	[email protected]:bruno/gestor-tasques.git (push)
git branch -r
  origin/main
  origin/funcionalitat/ordre-alfabetic
  personal/main
  personal/funcionalitat/ordre-alfabetic

I ara:

git switch funcionalitat/ordre-alfabetic
fatal: 'funcionalitat/ordre-alfabetic' matched multiple (2) remote tracking branches

Git s'hi nega, i fa bé: triar per tu significaria configurar un seguiment cap a un repositori que potser no és el que vols, amb el risc d'acabar enviant feina al lloc equivocat.

Les tres maneres de resoldre-ho

1. Ser explícit amb la referència remota (la més clara):

git switch -c funcionalitat/ordre-alfabetic origin/funcionalitat/ordre-alfabetic

O amb --track, que a més deixa constància de la intenció:

git switch -c funcionalitat/ordre-alfabetic --track origin/funcionalitat/ordre-alfabetic

2. Fer servir la sintaxi de desambiguació:

git switch --track origin/funcionalitat/ordre-alfabetic

Amb --track i sense -c, Git crea una branca local amb el mateix nom curt que la remota.

3. Configurar un remot preferent:

git config checkout.defaultRemote origin

A partir d'aquí, davant de l'ambigüitat Git triarà sempre origin sense preguntar:

git switch funcionalitat/ordre-alfabetic
branch 'funcionalitat/ordre-alfabetic' set up to track 'origin/funcionalitat/ordre-alfabetic'.
Switched to a new branch 'funcionalitat/ordre-alfabetic'

És la solució recomanada si treballes habitualment amb diversos remots, per exemple en el flux de forks del mòdul 7, on tenir origin i upstream alhora és el normal.

Noms locals diferents

Amb diversos remots, de vegades convé desacoblar els noms per no perdre's:

git switch -c ordre-equip     origin/funcionalitat/ordre-alfabetic
git switch -c ordre-personal  personal/funcionalitat/ordre-alfabetic
git branch -vv
  ordre-equip     a1e5c93 [origin/funcionalitat/ordre-alfabetic] Mostra les tasques ordenades alfabèticament
* ordre-personal  4f8c2a1 [personal/funcionalitat/ordre-alfabetic] Prova una altra manera d'ordenar

Dues branques locals amb noms clars, cadascuna seguint un remot diferent. Recorda que el seguiment no exigeix que els noms coincideixin.

  1. @{upstream} i @{push}: referir-se al seguiment

Git ofereix una sintaxi per referir-se a l'upstream d'una branca sense escriure'n el nom. És un estalvi petit però molt còmode, i apareix constantment en àlies i scripts.

# Les tres maneres de dir el mateix
git log origin/main..main
git log @{upstream}..
git log @{u}..

@{upstream} (abreujat @{u}) resol a l'upstream de la branca actual:

git rev-parse --abbrev-ref @{u}
origin/main

I es pot aplicar a una altra branca:

git rev-parse --abbrev-ref documentacio/actualitzar-notes@{u}
origin/documentacio/actualitzar-notes

Els usos més pràctics, i tots independents del nom de la branca on siguis:

# Què tinc sense enviar?
git log --oneline @{u}..

# Què em falta per portar?
git log --oneline ..@{u}

# Quant hem divergit?
git rev-list --left-right --count @{u}...

# Què canvia respecte al servidor?
git diff @{u}

Fixa't en l'elegància de @{u}..: en ometre el costat dret, Git fa servir HEAD. I ..@{u} omet l'esquerre, amb el mateix efecte. Són els rangs del mòdul 2 aprofitant els valors per defecte.

@{push}, el germà menys conegut

Existeix una segona referència, @{push}, que apunta a on aniria un git push. Normalment coincideix amb @{upstream}, però no sempre: si treballes amb remote.pushDefault configurat o amb refspecs d'enviament diferents dels de descàrrega, poden diferir.

git rev-parse --abbrev-ref @{push}

És la referència correcta per respondre a «quins commits sortirien si fes push ara?»:

git log --oneline @{push}..

En el flux de forks del mòdul 7 —on llegeixes d'upstream i escrius a origin— aquella distinció esdevé molt útil.

I un parell d'àlies que valen el seu pes en or:

git config --global alias.senseenviar "log --oneline @{u}.."
git config --global alias.pendent "log --oneline ..@{u}"
git config --global alias.desfasament "rev-list --left-right --count @{u}..."
git senseenviar
d5e9b2f Retorna el focus al camp després d'esborrar una tasca
8a3f7c1 Corregeix el focus després d'esborrar una tasca

Els àlies, a fons, a la lliçó 06-04.

  1. Tancament del mòdul

Recapitulem el que hem construït en aquestes sis lliçons, perquè el mòdul 4 és un salt conceptual i convé veure'l sencer.

Vam començar desfent el mite: un remot no és un servidor màgic, és un nom curt per a un URL desat a .git/config. Vam veure que en un sistema distribuït no hi ha servidor central tècnicament: tots els repositoris són iguals i l'«oficial» ho és per acord de l'equip. Vam entendre per fi què és un repositori bare —el contingut de .git/ sense directori de treball— i per què és la manera correcta de muntar un repositori que rep enviaments. I vam descobrir que les referències remotes viuen a .git/refs/remotes/, al teu disc, i que origin/main és una foto amb data, no una finestra en directe.

Després, l'Ana va publicar el projecte. Vam registrar el remot amb git remote add —una operació purament local que ni tan sols valida l'URL—, vam aprendre a inspeccionar-lo, reanomenar-lo i canviar-li l'adreça, i vam desgranar el refspec +refs/heads/*:refs/remotes/origin/*, aquella línia que gairebé ningú sap llegir i que explica d'on surt la barra d'origin/main.

Vam resoldre l'autenticació: per què llegir un repositori públic no requereix credencials i escriure-hi sempre sí, què és un testimoni personal d'accés i per què va substituir la contrasenya, com es genera i es fa servir un parell de claus SSH, i com cada sistema operatiu desa les credencials —amb el Git Credential Manager obrint el camí a la Carla des de Windows 11.

Vam atacar la confusió més estesa de Git: fetch davant de pull. El primer descarrega i actualitza referències remotes sense tocar ni un sol fitxer, i és absolutament segur; el segon hi afegeix una integració que sí que modifica la teva feina i pot donar conflictes. Vam aprendre a mirar al forat entre tots dos, a netejar referències fantasma amb --prune, i la Carla va clonar per fi un repositori amb tota la història del mòdul 3 a dins.

Vam girar el canal amb git push: què envia exactament, què fa el -u, com s'escriu un refspec d'enviament, per què el servidor rebutja els enviaments non-fast-forward —per no destruir feina aliena— i per què --force-with-lease és gairebé sempre la resposta correcta quan de debò cal forçar.

I hem acabat lligant els caps: les branques de seguiment, dues línies de configuració per branca de les quals depenen git push sense arguments, git pull sense arguments, els missatges de git status i el DWIM de git switch.

La idea que sosté tot el mòdul, i que convé endur-se: els remots no han canviat res del que ja sabies. Fusionar la feina d'en Bruno és el mateix git merge del mòdul 3. Els conflictes es resolen igual. Les branques continuen sent fitxers de 41 bytes. L'única cosa que s'ha afegit és el transport: com aconsegueixen els objectes viatjar d'un .git/ a un altre. Un cop han arribat, tot funciona com ja sabies.

El problema que queda obert

L'equip ja col·labora. Tots tres envien, reben i integren, i el projecte avança. Però si l'Ana mira l'historial de main amb ulls crítics, comença a veure coses que no li agraden:

git log --oneline --graph -14
*   4f8c2a1 Merge branch 'main' of https://git.exemple.cat/equip/gestor-tasques
|\
| * d5e9b2f Retorna el focus al camp després d'esborrar una tasca
* | 8a3f7c1 Corregeix el focus després d'esborrar una tasca
|/
*   e7b3f2a Merge branch 'main' of https://git.exemple.cat/equip/gestor-tasques
|\
| * b4d7e93 Ajusta l'estil del peu de pàgina
* | 3e9c2a5 apany
|/
* 9e2f4a7 Afegeix el comptador de tasques pendents
* c2a8f1e Fusiona el missatge de llista buida

Dos problemes diferents convivint:

  • Commits de fusió que no expliquen res. Aquells Merge branch 'main' of https://… no són decisions de disseny: són el rastre de dues persones que van fer pull alhora. Embruten el graf i no aporten informació.
  • Commits que no haurien d'existir tal com són. apany no descriu res. I a les branques de treball hi ha wip, wip 2, ara sí i algun console.log oblidat que es va enviar sense voler.

Falta a més tot un repertori d'operacions que la feina en equip fa imprescindibles: endur-se un commit concret d'una branca a una altra sense arrossegar la resta; desar la feina a mitges per atendre una urgència i recuperar-la després; marcar versions amb etiquetes que es publiquin al servidor; i desfer un commit ja enviat sense reescriure l'historial que els altres ja tenen.

Al mòdul 5: Operacions Avançades de Git entrem en aquest terreny. Veurem git rebase —que reescriu commits per produir un historial lineal, i del qual hem parlat tres vegades sense desenvolupar-lo—, el rebase interactiu per reordenar, unir i reescriure commits abans de publicar-los, git cherry-pick per trasplantar commits solts, git stash per apartar feina temporalment, les etiquetes per marcar versions, i git revert per desfer sense esborrar.

És un mòdul de precisió, i arriba en el moment adequat: ara que l'historial és compartit, reescriure'l té conseqüències per a altres persones. Tot el que has après aquí sobre push, sobre el rebuig non-fast-forward i sobre --force-with-lease serà exactament el que et permetrà distingir què es pot reescriure i què no.

Errors Habituals i Consells

Error 1: confondre main, origin/main i el main del servidor. És l'error que engloba gairebé tots els altres. Davant de qualsevol dubte, pregunta't de quin dels tres estàs parlant. Els dos primers són al teu disc.

Error 2: fiar-se de l'«ahead/behind» sense haver fet fetch. git status no consulta mai la xarxa: compara contra la teva foto. Si fa dos dies que no fas fetch, aquests números descriuen un passat.

Error 3: confondre .. amb .... Dos punts és «el que té B i no A»; tres punts és «l'exclusiu de cada banda». Per al desfasament es fa servir ... amb --left-right --count.

Error 4: fatal: The current branch X has no upstream branch. La branca no té seguiment. S'arregla amb git push -u origin X, o d'una vegada per sempre amb push.autoSetupRemote true.

Error 5: que el DWIM falli perquè no has fet fetch. Si un company acaba de crear la branca i tu no has portat les referències, git switch <branca> donarà invalid reference. Un git fetch ho resol.

Error 6: ignorar les branques marcades com a gone. Signifiquen que la seva branca al servidor ha desaparegut, normalment perquè es va integrar. Són les candidates més clares a esborrar en local.

Error 7: no adonar-se que una branca no té upstream. Sense claudàtors a git branch -vv, aquella branca només existeix al teu disc. Legítim per a un experiment, perillós per a feina que importa.

Error 8: creure que el seguiment es comparteix amb l'equip. És configuració local del teu .git/config. Cada persona té la seva i ningú no veu la de ningú.

Consell 1: git fetch && git status com a rutina d'inici. Dues ordres que et diuen la veritat en comptes d'una foto vella.

Consell 2: fes servir git branch -vv per a la revisió setmanal. Veus d'un cop d'ull què està sense enviar, què està sense portar, què és només local i què es pot esborrar.

Consell 3: activa push.autoSetupRemote true. Elimina l'error de «no upstream branch» per sempre. Ja el tens des de la lliçó 01-06.

Consell 4: aprèn @{u}. git log @{u}.. per veure el que no has enviat i git log ..@{u} per al que no has portat funcionen en qualsevol branca sense canviar-hi una lletra. Desa'ls com a àlies.

Consell 5: amb diversos remots, configura checkout.defaultRemote origin. Evita l'error d'ambigüitat i t'estalvia escriure la referència completa cada vegada.

Exercicis

Exercici 1: les tres coses

Munta un servidor bare i dos clons. Després, i amb ordres, demostra cada afirmació:

  1. En confirmar en local, només es mou main.
  2. En fer push, es mouen main, origin/main i la branca del servidor.
  3. Quan l'altre clon envia alguna cosa, l'origin/main del primer no canvia fins que fa fetch.
  4. git status calcula el desfasament sense consultar la xarxa: provoca una situació en què el missatge sigui objectivament fals respecte al servidor i explica per què.
  5. Reprodueix el càlcul de git status a mà amb git rev-list.

Exercici 2: gestionar el seguiment

En un repositori connectat a un bare:

  1. Crea una branca local sense upstream i demostra amb dues ordres diferents que no en té.
  2. Comprova què diu git push en aquella branca si desactives push.autoSetupRemote.
  3. Configura-li l'upstream de les tres maneres possibles (desfent-ho entre l'una i l'altra) i verifica el resultat a .git/config cada vegada.
  4. Treu-li l'upstream i observa què desapareix de git status.
  5. Crea una branca local que segueixi una de remota amb un altre nom i demostra que funciona.

Exercici 3: DWIM i ambigüitat

  1. Munta dos servidors bare i un repositori de treball amb tots dos registrats.
  2. Crea a tots dos una branca amb el mateix nom, amb contingut diferent.
  3. Intenta fer servir el DWIM i captura l'error.
  4. Resol-ho de les tres maneres explicades a la lliçó.
  5. Demostra amb git branch -vv que en cada cas la branca local segueix el remot correcte.
  6. Comprova amb git log que el contingut és realment diferent segons el remot triat.

Solucions

Solució 1:

mkdir -p /tmp/ex-track && cd /tmp/ex-track
git init --bare servidor.git

git clone servidor.git ana
cd ana
echo "base" > f.txt && git add . && git commit -m "Estructura inicial del gestor de tasques"
git push -u origin main
cd ..
git clone servidor.git bruno
# 1. Confirmar mou NOMÉS main
cd /tmp/ex-track/ana
git rev-parse main origin/main
5a1d8f39c2e7b4a6f1d8c3e9b2a5f7d4c6e8b1a3
5a1d8f39c2e7b4a6f1d8c3e9b2a5f7d4c6e8b1a3
echo "comptador" >> f.txt
git commit -am "Afegeix el comptador de tasques pendents"
git rev-parse main origin/main
9e2f4a78c3b1d6f4a2e7c5b9d8f1a3c6e4b2d7f9   ← main ha avançat
5a1d8f39c2e7b4a6f1d8c3e9b2a5f7d4c6e8b1a3   ← origin/main NO

I al servidor tampoc no ha canviat res:

git --git-dir=/tmp/ex-track/servidor.git rev-parse main
5a1d8f39c2e7b4a6f1d8c3e9b2a5f7d4c6e8b1a3
# 2. El push mou les tres
git push
git rev-parse main origin/main
git --git-dir=/tmp/ex-track/servidor.git rev-parse main
9e2f4a78c3b1d6f4a2e7c5b9d8f1a3c6e4b2d7f9
9e2f4a78c3b1d6f4a2e7c5b9d8f1a3c6e4b2d7f9
9e2f4a78c3b1d6f4a2e7c5b9d8f1a3c6e4b2d7f9

Les tres alineades. El push actualitza el teu origin/main perquè Git acaba de parlar amb el servidor i sap amb certesa on ha quedat.

# 3. El que envia en Bruno no arriba sol a l'Ana
cd /tmp/ex-track/bruno
git pull
echo "filtre" >> f.txt
git commit -am "Afegeix el filtre de tasques pendents"
git push

# Al repositori de l'Ana, SENSE fer fetch:
cd /tmp/ex-track/ana
git rev-parse origin/main
git --git-dir=/tmp/ex-track/servidor.git rev-parse main
9e2f4a78c3b1d6f4a2e7c5b9d8f1a3c6e4b2d7f9   ← la foto de l'Ana, antiquada
c3b8f5d2a9e4f7b1c6d3a8e5b2f9c4d7a1e6b3f8   ← la realitat del servidor
# 4. git status menteix (sense saber-ho)
git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

El missatge és objectivament fals. El servidor té un commit que l'Ana no té, així que la seva branca està endarrerida. Però git status compara contra origin/main, que és una foto presa abans de l'enviament d'en Bruno, i no consulta mai la xarxa. No està mentint: està dient la veritat sobre una informació caducada.

git fetch
git status
On branch main
Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)

Ara sí. L'única manera que aquests números siguin certs és fer fetch abans.

# 5. El càlcul a mà
git rev-list --left-right --count main...origin/main
0	1

Zero commits exclusius de main (res per enviar), un d'exclusiu d'origin/main (per portar). És exactament «behind by 1 commit». I amb detall:

git rev-list --left-right --oneline main...origin/main
>c3b8f5d Afegeix el filtre de tasques pendents

El > indica que ve de la banda dreta, origin/main.

Solució 2:

mkdir -p /tmp/ex-up && cd /tmp/ex-up
git init --bare servidor.git
git clone servidor.git feina
cd feina
echo "base" > f.txt && git add . && git commit -m "Base"
git push -u origin main
# 1. Branca local sense upstream
git switch -c funcionalitat/ordre-alfabetic
echo "ordre" >> f.txt
git commit -am "Mostra les tasques ordenades alfabèticament"

# Prova a: sense claudàtors a branch -vv
git branch -vv
* funcionalitat/ordre-alfabetic a1e5c93 Mostra les tasques ordenades alfabèticament
  main                          5f8b2e1 [origin/main] Base
# Prova b: @{u} no resol
git rev-parse --abbrev-ref @{u}
fatal: no upstream configured for branch 'funcionalitat/ordre-alfabetic'
# Prova c: tampoc no hi ha secció a la configuració
git config --get-regexp '^branch\.'
branch.main.remote origin
branch.main.merge refs/heads/main

Només apareix main. La branca nova no té cap entrada.

# 2. Què diu el push sense autoSetupRemote
git config --local push.autoSetupRemote false
git push
fatal: The current branch funcionalitat/ordre-alfabetic has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin funcionalitat/ordre-alfabetic

Git no endevina: exigeix que diguis on va la branca la primera vegada.

# 3a. Forma 1: push -u
git push -u origin funcionalitat/ordre-alfabetic
git config --get-regexp '^branch\.funcionalitat'
branch.funcionalitat/ordre-alfabetic.remote origin
branch.funcionalitat/ordre-alfabetic.merge refs/heads/funcionalitat/ordre-alfabetic
# Desfer-ho per provar la següent
git branch --unset-upstream
git config --get-regexp '^branch\.funcionalitat'
(sense sortida)
# 3b. Forma 2: branch --set-upstream-to
git branch --set-upstream-to=origin/funcionalitat/ordre-alfabetic
branch 'funcionalitat/ordre-alfabetic' set up to track 'origin/funcionalitat/ordre-alfabetic'.
git branch --unset-upstream

# 3c. Forma 3: automàtica amb autoSetupRemote
git config --local push.autoSetupRemote true
git commit --allow-empty -m "Un altre commit"
git push
branch 'funcionalitat/ordre-alfabetic' set up to track 'origin/funcionalitat/ordre-alfabetic'.

Les tres produeixen exactament la mateixa configuració. Canvia el moment i la comoditat, no el resultat.

# 4. Treure l'upstream
git status
On branch funcionalitat/ordre-alfabetic
Your branch is up to date with 'origin/funcionalitat/ordre-alfabetic'.

nothing to commit, working tree clean
git branch --unset-upstream
git status
On branch funcionalitat/ordre-alfabetic
nothing to commit, working tree clean

Ha desaparegut la línia del desfasament. Sense upstream, Git no té amb què comparar. És la prova que aquell missatge depèn enterament d'aquesta configuració.

# 5. Branca local amb nom diferent de l'upstream
git switch main
git switch -c ordre-local --track origin/funcionalitat/ordre-alfabetic
branch 'ordre-local' set up to track 'origin/funcionalitat/ordre-alfabetic'.
Switched to a new branch 'ordre-local'
git branch -vv
  funcionalitat/ordre-alfabetic a7c2e94 Un altre commit
  main                          5f8b2e1 [origin/main] Base
* ordre-local                   a7c2e94 [origin/funcionalitat/ordre-alfabetic] Un altre commit
git commit --allow-empty -m "Treball des de la branca amb un altre nom"
git push
To /tmp/ex-up/servidor.git
   a7c2e94..b3f8d1c  ordre-local -> funcionalitat/ordre-alfabetic

Fixa't en l'última línia: ordre-local -> funcionalitat/ordre-alfabetic. La branca local es diu d'una manera i la del servidor d'una altra, i l'enviament funciona sense problema. El seguiment no exigeix que els noms coincideixin.

Solució 3:

mkdir -p /tmp/ex-dwim && cd /tmp/ex-dwim

# 1. Dos servidors i un repositori de treball
git init --bare equip.git
git init --bare personal.git

git init -b main feina
cd feina
echo "base" > f.txt && git add . && git commit -m "Base"
git remote add origin /tmp/ex-dwim/equip.git
git remote add personal /tmp/ex-dwim/personal.git
git push origin main
git push personal main
# 2. La mateixa branca, amb contingut diferent, a cada servidor
git switch -c funcionalitat/ordre-alfabetic
echo "ordre de l'equip" >> f.txt
git commit -am "Mostra les tasques ordenades alfabèticament"
git push origin funcionalitat/ordre-alfabetic

git reset --hard HEAD~1
echo "una altra manera d'ordenar" >> f.txt
git commit -am "Prova una altra manera d'ordenar"
git push personal funcionalitat/ordre-alfabetic

# Tornar a un estat net i esborrar la branca local
git switch main
git branch -D funcionalitat/ordre-alfabetic
git fetch --all
git branch -r
  origin/funcionalitat/ordre-alfabetic
  origin/main
  personal/funcionalitat/ordre-alfabetic
  personal/main
# 3. El DWIM falla
git switch funcionalitat/ordre-alfabetic
fatal: 'funcionalitat/ordre-alfabetic' matched multiple (2) remote tracking branches

Git no pot endevinar quina vols, i triar malament significaria acabar enviant feina al repositori equivocat.

# 4a. Forma 1: referència remota explícita
git switch -c ordre-equip origin/funcionalitat/ordre-alfabetic
branch 'ordre-equip' set up to track 'origin/funcionalitat/ordre-alfabetic'.
Switched to a new branch 'ordre-equip'
# 4b. Forma 2: --track sense -c (pren el nom curt de la remota)
git switch main
git switch --track personal/funcionalitat/ordre-alfabetic
branch 'funcionalitat/ordre-alfabetic' set up to track 'personal/funcionalitat/ordre-alfabetic'.
Switched to a new branch 'funcionalitat/ordre-alfabetic'
# 4c. Forma 3: remot preferent
git switch main
git branch -D funcionalitat/ordre-alfabetic
git config checkout.defaultRemote origin
git switch funcionalitat/ordre-alfabetic
branch 'funcionalitat/ordre-alfabetic' set up to track 'origin/funcionalitat/ordre-alfabetic'.
Switched to a new branch 'funcionalitat/ordre-alfabetic'

Ja no hi ha ambigüitat: davant del dubte, Git tria origin.

# 5. Cada branca segueix el seu remot
git branch -vv
* funcionalitat/ordre-alfabetic a1e5c93 [origin/funcionalitat/ordre-alfabetic] Mostra les tasques ordenades alfabèticament
  main                          5f8b2e1 Base
  ordre-equip                   a1e5c93 [origin/funcionalitat/ordre-alfabetic] Mostra les tasques ordenades alfabèticament
# 6. El contingut és realment diferent
git log --oneline -1 origin/funcionalitat/ordre-alfabetic
git log --oneline -1 personal/funcionalitat/ordre-alfabetic
a1e5c93 Mostra les tasques ordenades alfabèticament
4f8c2a1 Prova una altra manera d'ordenar
git diff origin/funcionalitat/ordre-alfabetic personal/funcionalitat/ordre-alfabetic
diff --git a/f.txt b/f.txt
index 8c3d5a1..2e7b9f4 100644
--- a/f.txt
+++ b/f.txt
@@ -1,2 +1,2 @@
 base
-ordre de l'equip
+una altra manera d'ordenar

Dos commits diferents amb el mateix nom de branca en dos servidors diferents. Aquí es veu per què Git prefereix fallar abans que endevinar: triar malament hauria significat treballar sobre una base equivocada i, encara pitjor, enviar el resultat al lloc equivocat.

Conclusió

Amb aquesta lliçó tanquem el mòdul 4. El que hem après aquí:

  • Una branca de seguiment és una branca local associada a una referència remota, el seu upstream. Aquesta associació és el que permet git push i git pull sense arguments, els missatges de desfasament de git status i la drecera @{u}.
  • Viu en dues línies de .git/config: branch.<branca>.remote (amb quin remot) i branch.<branca>.merge (quina branca d'aquell remot). És configuració local: no viatja amb el push i cada persona té la seva.
  • main, origin/main i el main del servidor són tres coses diferents. Les dues primeres són al teu disc: la mous tu en confirmar, i la segona la mou Git en parlar amb el servidor. La tercera és en una altra màquina i la mou qualsevol de l'equip. Gairebé tots els desconcerts amb remots es resolen preguntant-se de quina de les tres s'està parlant.
  • L'«ahead/behind» es calcula amb git rev-list --left-right --count main...origin/main, fent servir la diferència simètrica de tres punts. I es calcula contra la teva foto, no contra el servidor: sense un fetch previ, aquests números poden ser falsos.
  • git branch -vv mostra l'upstream i el desfasament de totes les branques entre claudàtors. Sense claudàtors = sense upstream, la branca és purament local. Amb gone = la seva branca del servidor ja no existeix, normalment perquè es va integrar: candidata a esborrar.
  • L'upstream s'estableix amb git push -u, automàticament amb push.autoSetupRemote true, a posteriori amb git branch -u origin/<branca>, o en crear la branca amb --track. Es treu amb --unset-upstream, i llavors git status deixa d'informar del desfasament.
  • El DWIM de git switch <branca> crea una branca local a partir d'una de remota del mateix nom i configura el seguiment, sempre que no existeixi ja en local i hi hagi exactament una referència remota que hi coincideixi. Amb diversos remots falla: es resol sent explícit, amb --track, o configurant checkout.defaultRemote.
  • @{upstream} (o @{u}) es refereix a l'upstream de la branca actual sense anomenar-lo: git log @{u}.. (el que no has enviat) i git log ..@{u} (el que no has portat) funcionen en qualsevol branca.

El que ve

El projecte ha sortit del portàtil de l'Ana i circula entre tres màquines i tres sistemes operatius. L'equip col·labora de debò: tots tres envien, reben i integren, i gestor-tasques avança.

Però l'historial comença a acusar-ho. S'omple de commits de fusió que no expliquen res, de missatges com apany i wip 2, i de feina a mitges que es va publicar sense voler. I apareixen necessitats noves que la feina en solitari no plantejava: endur-se un commit concret d'una branca a una altra, apartar la feina sense acabar per atendre una urgència, marcar una versió publicada o desfer alguna cosa que ja és al servidor.

Al mòdul 5: Operacions Avançades de Git aprendrem a manipular l'historial amb precisió: git rebase per reescriure commits i obtenir una història lineal, el rebase interactiu per reordenar-los, unir-los i corregir-los abans de publicar-los, git cherry-pick per trasplantar commits solts, git stash per apartar feina temporalment, les etiquetes per marcar versions, i git revert per desfer sense esborrar.

I tot el que has après en aquest mòdul serà justament el que et permetrà fer-les servir amb criteri. Perquè a partir d'ara l'historial és compartit, i reescriure alguna cosa que una altra persona ja té descarregada no és una operació local: és una decisió que afecta tot l'equip.

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