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
- Què és una branca de seguiment
- On viu:
branch.<branca>.remoteibranch.<branca>.merge - Les tres coses que es diuen
main - D'on surt «ahead of 'origin/main' by 2 commits»
git branch -vv: l'estat de totes d'una ullada- Establir, canviar i treure l'upstream
- Crear una branca local a partir d'una de remota: el DWIM de Git
- El cas ambigu de diversos remots
@{upstream}i@{push}: referir-se al seguiment- Tancament del mòdul
- 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 pushsap a quin remot i a quina branca. - Rebre sense arguments:
git pulltambé. - 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.
- On viu:
branch.<branca>.remote i branch.<branca>.merge
branch.<branca>.remote i branch.<branca>.mergeCom tot a Git, això no és màgia: són dues línies en un fitxer de text.
[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-esborrarCada 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ó:
I també existeix una clau opcional que potser trobaràs:
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.
- Les tres coses que es diuen
main
mainAquest é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 | Sí | 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
maindel servidor; el que tu mires és el teuorigin/main, que està antiquat. Solució:git fetch. - «He fet
fetchi els meus fitxers no han canviat.» → Elfetchactualitzaorigin/main; els teus fitxers depenen demain. Correcte. - «He fet
git reset --hard origin/maini he perdut els meus commits.» → Has mogut la tevamainon és la teva foto del servidor. Els commits continuen al reflog (lliçó 09-04), però la branca ja no els assoleix. - «
git pushdiu everything up-to-date però el meu canvi no hi és.» → La tevamainés on és el teuorigin/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/maind5e9b2f8a3c1e6b4d7f2a9c5e8b1d4f6a3c7e9b2 ← main ha avançat b4d7e935f1c8a2e6d4b7f9a3c1e5b8d2f4a6c9e7 ← origin/main continua igual
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.
- D'on surt «ahead of 'origin/main' by 2 commits»
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
Desglossem-ho, perquè cada peça importa:
main...origin/main— tres 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:
<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í:
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:
Hi ha una opció de git status que promet fer-ho sola:
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.
git branch -vv: l'estat de totes d'una ullada
git branch -vv: l'estat de totes d'una ulladaA la lliçó 03-06 vam esmentar -vv i vam dir que tindria sentit amb remots. Ha arribat el moment.
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:
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/…] |
Té 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:
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:
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.
- 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.
git push -u (el més habitual)
git push -u (el més habitual)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.
- Automàticament, amb
push.autoSetupRemote
push.autoSetupRemoteJa 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-alfabeticUn missatge que has vist mil vegades si has fet servir Git sense aquesta configuració.
git branch --set-upstream-to (a posteriori)
git branch --set-upstream-to (a posteriori)Per a una branca que ja existeix als dos llocs però no està aparellada:
Hi ha una forma curta amb -u (que aquí significa el mateix que a push):
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ó:
Ara la teva main local segueix origin/desenvolupament. És estrany, però perfectament vàlid: l'associació no exigeix que els noms coincideixin.
- 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 mainTreure el seguiment
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:
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 |
- 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:
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:
Llarg i redundant. Per això Git té una drecera:
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:
- Existeix una branca local anomenada
documentacio/actualitzar-notes? No. - Existeix exactament una referència remota amb aquest nom? Sí,
origin/documentacio/actualitzar-notes. - 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:
* 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:
Els límits del DWIM
No funciona si el nom local ja existeix:
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:
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:
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.
- 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:
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)
origin/main origin/funcionalitat/ordre-alfabetic personal/main personal/funcionalitat/ordre-alfabetic
I ara:
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):
O amb --track, que a més deixa constància de la intenció:
2. Fer servir la sintaxi de desambiguació:
Amb --track i sense -c, Git crea una branca local amb el mateix nom curt que la remota.
3. Configurar un remot preferent:
A partir d'aquí, davant de l'ambigüitat Git triarà sempre origin sense preguntar:
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 -vvordre-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.
@{upstream} i @{push}: referir-se al seguiment
@{upstream} i @{push}: referir-se al seguimentGit 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.
@{upstream} (abreujat @{u}) resol a l'upstream de la branca actual:
I es pot aplicar a una altra branca:
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.
És la referència correcta per respondre a «quins commits sortirien si fes push ara?»:
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}..."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.
- 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:
* 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 ferpullalhora. Embruten el graf i no aporten informació. - Commits que no haurien d'existir tal com són.
apanyno descriu res. I a les branques de treball hi hawip,wip 2,ara síi algunconsole.logoblidat 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ó:
- En confirmar en local, només es mou
main. - En fer
push, es mouenmain,origin/maini la branca del servidor. - Quan l'altre clon envia alguna cosa, l'
origin/maindel primer no canvia fins que fafetch. git statuscalcula el desfasament sense consultar la xarxa: provoca una situació en què el missatge sigui objectivament fals respecte al servidor i explica per què.- Reprodueix el càlcul de
git statusa mà ambgit rev-list.
Exercici 2: gestionar el seguiment
En un repositori connectat a un bare:
- Crea una branca local sense upstream i demostra amb dues ordres diferents que no en té.
- Comprova què diu
git pushen aquella branca si desactivespush.autoSetupRemote. - Configura-li l'upstream de les tres maneres possibles (desfent-ho entre l'una i l'altra) i verifica el resultat a
.git/configcada vegada. - Treu-li l'upstream i observa què desapareix de
git status. - Crea una branca local que segueixi una de remota amb un altre nom i demostra que funciona.
Exercici 3: DWIM i ambigüitat
- Munta dos servidors bare i un repositori de treball amb tots dos registrats.
- Crea a tots dos una branca amb el mateix nom, amb contingut diferent.
- Intenta fer servir el DWIM i captura l'error.
- Resol-ho de les tres maneres explicades a la lliçó.
- Demostra amb
git branch -vvque en cada cas la branca local segueix el remot correcte. - Comprova amb
git logque 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 brunoecho "comptador" >> f.txt
git commit -am "Afegeix el comptador de tasques pendents"
git rev-parse main origin/main9e2f4a78c3b1d6f4a2e7c5b9d8f1a3c6e4b2d7f9 ← main ha avançat 5a1d8f39c2e7b4a6f1d8c3e9b2a5f7d4c6e8b1a3 ← origin/main NO
I al servidor tampoc no ha canviat res:
# 2. El push mou les tres
git push
git rev-parse main origin/main
git --git-dir=/tmp/ex-track/servidor.git rev-parse main9e2f4a78c3b1d6f4a2e7c5b9d8f1a3c6e4b2d7f9 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 main9e2f4a78c3b1d6f4a2e7c5b9d8f1a3c6e4b2d7f9 ← la foto de l'Ana, antiquada c3b8f5d2a9e4f7b1c6d3a8e5b2f9c4d7a1e6b3f8 ← la realitat del servidor
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.
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.
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:
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
Només apareix main. La branca nova no té cap entrada.
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-alfabeticGit 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'# 3b. Forma 2: branch --set-upstream-to
git branch --set-upstream-to=origin/funcionalitat/ordre-alfabeticgit 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 pushLes tres produeixen exactament la mateixa configuració. Canvia el moment i la comoditat, no el resultat.
On branch funcionalitat/ordre-alfabetic Your branch is up to date with 'origin/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-alfabeticbranch 'ordre-local' set up to track 'origin/funcionalitat/ordre-alfabetic'. Switched to a new branch 'ordre-local'
funcionalitat/ordre-alfabetic a7c2e94 Un altre commit main 5f8b2e1 [origin/main] Base * ordre-local a7c2e94 [origin/funcionalitat/ordre-alfabetic] Un altre commit
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 -rorigin/funcionalitat/ordre-alfabetic origin/main personal/funcionalitat/ordre-alfabetic personal/main
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-alfabeticbranch '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-alfabeticbranch '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-alfabeticbranch '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.
* 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-alfabeticdiff --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'ordenarDos 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 pushigit pullsense arguments, els missatges de desfasament degit statusi la drecera@{u}. - Viu en dues línies de
.git/config:branch.<branca>.remote(amb quin remot) ibranch.<branca>.merge(quina branca d'aquell remot). És configuració local: no viatja amb elpushi cada persona té la seva. main,origin/maini elmaindel 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 unfetchprevi, aquests números poden ser falsos. git branch -vvmostra l'upstream i el desfasament de totes les branques entre claudàtors. Sense claudàtors = sense upstream, la branca és purament local. Ambgone= 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 ambpush.autoSetupRemote true, a posteriori ambgit branch -u origin/<branca>, o en crear la branca amb--track. Es treu amb--unset-upstream, i llavorsgit statusdeixa 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 configurantcheckout.defaultRemote. @{upstream}(o@{u}) es refereix a l'upstream de la branca actual sense anomenar-lo:git log @{u}..(el que no has enviat) igit 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
- 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ó
