A la lliçó anterior vam fer servir git push diverses vegades sense explicar-lo. Tocava: calia posar el projecte al servidor per poder estudiar la recepció. Ara girem el canal i desgranem l'ordre que envia.

git push és l'ordre amb conseqüències per als altres. Tot el que has fet fins ara —confirmar, ramificar, fusionar, fins i tot equivocar-te— passava dins del teu .git/ i es podia desfer sense que ningú se n'assabentés. Un push canvia el repositori que tot l'equip fa servir com a referència. El que envies, altres ho descarreguen; i el que esborres del servidor, altres deixen de tenir-ho.

Per això aquesta lliçó dedica tant espai als rebuigs i a les opcions de forçat. Saber enviar és fàcil; saber per què el servidor et diu que no, i què és segur fer llavors, és el que distingeix algú que no destrueix la feina dels seus companys.

L'Ana envia per fi main i les seves branques al servidor de l'equip, i en Bruno i la Carla les reben.

Contingut

  1. Què envia exactament git push
  2. El primer enviament: git push -u origin main
  3. Què fa exactament el -u
  4. El refspec d'enviament
  5. Les formes abreujades i push.default
  6. Quan el servidor diu que no: non-fast-forward
  7. --force-with-lease davant de --force
  8. Esborrar una branca al servidor
  9. Enviar etiquetes
  10. L'Ana publica les seves branques; en Bruno i la Carla les reben

  1. Què envia exactament git push

La definició precisa, que és més estreta del que la gent imagina:

git push fa dues coses: puja al remot els objectes que li falten i li demana que actualitzi una referència perquè apunti a un commit concret.

Objectes i un punter. Res més.

El que NO envia:

  • El teu directori de treball. Els fitxers modificats sense confirmar es queden al teu disc. Si has editat app.js i no has fet commit, aquell canvi no surt de la teva màquina per molts push que facis.
  • El teu índex. L'àrea de preparació és teva i personal.
  • La teva configuració. .git/config no viatja: els remots, àlies i ajustos de cada persona són seus.
  • Les teves altres branques, llevat que les demanis explícitament.
  • El teu reflog. El registre dels teus moviments és local.
  • Els fitxers ignorats. El que no és dins d'un commit, no existeix per al push.

Aquesta primera és la més important i la font d'un desconcert clàssic: «he fet push i el meu company no veu el canvi». Gairebé sempre significa que el canvi no es va arribar a confirmar. Només viatja el que és dins d'un commit.

Els passos interns

sequenceDiagram
    participant L as Repositori local
    participant R as Servidor

    L->>R: Quines referències tens i on apunten?
    R-->>L: refs/heads/main → c2a8f1e
    Note over L: Calcula quins objectes té<br/>el servidor i quins no
    L->>R: Envia el packfile amb els objectes que falten
    Note over R: Desa els objectes<br/>i comprova que l'actualització<br/>és un fast-forward
    L->>R: Actualitza refs/heads/main → b4d7e93
    R-->>L: Acceptat

Aquest penúltim pas —la comprovació de fast-forward— és el cor de l'apartat 6 i la raó de gairebé tots els rebuigs.

I una conseqüència interessant: l'enviament és incremental. Git calcula quins objectes li falten al servidor i envia només aquests, comprimits en un packfile. Enviar el commit número mil d'un projecte no costa més que enviar el segon.

  1. El primer enviament: git push -u origin main

Tornem al moment en què l'Ana va publicar. El seu repositori tenia el remot registrat (lliçó 04-02) i l'autenticació resolta (lliçó 04-03), però mai no havia enviat res:

cd ~/projectes/gestor-tasques
git push -u origin main
Enumerating objects: 34, done.
Counting objects: 100% (34/34), done.
Delta compression using up to 8 threads
Compressing objects: 100% (22/22), done.
Writing objects: 100% (34/34), 4.87 KiB | 4.87 MiB/s, done.
Total 34 (delta 9), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (9/9), done.
To https://git.exemple.cat/equip/gestor-tasques.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.

Val la pena llegir aquesta sortida sencera, perquè explica tot el procés:

Línia Què significa
Enumerating objects: 34 Git ha comptat els objectes que cal enviar
Delta compression using up to 8 threads Està comprimint, desant diferències entre objectes semblants
Writing objects: 100% (34/34), 4.87 KiB Pujada real: 34 objectes en menys de 5 KB
remote: Resolving deltas El que diu el servidor: està reconstruint els objectes. Tot el que comença per remote: ve d'allà
* [new branch] main -> main S'ha creat la branca main al servidor
branch 'main' set up to track 'origin/main' Efecte del -u: s'ha configurat el seguiment

Fixa't en l'eficiència: tota la història del projecte —quatre commits base, dues fusions, un squash, un conflicte resolt— cap en 4,87 KB. Aquesta és la compressió delta del model de dades que vam estudiar a la lliçó 01-04.

I una comprovació des del servidor:

git ls-remote origin
c2a8f1e4b6d9f3a7e2c5b8d1f4a6c9e3b7d2f5a8	HEAD
c2a8f1e4b6d9f3a7e2c5b8d1f4a6c9e3b7d2f5a8	refs/heads/main

El repositori que estava buit ja té una branca i una història.

  1. Què fa exactament el -u

-u és l'abreviatura de --set-upstream, i apareix absolutament a tots els tutorials de Git sense que gairebé cap expliqui què fa.

El que fa és escriure dues línies a .git/config:

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

Aquesta secció [branch "main"] és la branca de seguiment: l'associació entre la teva branca local main i la branca main d'origin. I a partir d'aquell moment canvien diverses coses:

Sense seguiment Amb seguiment (-u)
git push origin main cada vegada git push a seques
git pull origin main cada vegada git pull a seques
git status no diu res del servidor git status diu «2 commits ahead of 'origin/main'»
git branch -vv no mostra l'aparellament Mostra [origin/main] i el desfasament

La comprovació immediata:

git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

Aquest «up to date with 'origin/main'» només apareix si hi ha seguiment configurat. Sense ell, git status no tindria amb què comparar.

El -u només cal la primera vegada que envies una branca. Després, la configuració ja està escrita i git push a seques funciona.

I recorda que a la lliçó 01-06 vam configurar això:

git config --global push.autoSetupRemote true

Amb aquest ajust (disponible des de Git 2.37), el -u és innecessari: en enviar una branca nova, Git configura el seguiment automàticament. L'Ana hauria pogut escriure simplement git push i el resultat hauria estat idèntic. L'hem fet servir explícitament perquè vegis el que passa per sota.

Tot el que fa referència a les branques de seguiment —com es consulten, com es canvien, què significa exactament aquell «2 commits ahead»— és el tema de la lliçó 04-06, la que tanca el mòdul.

  1. El refspec d'enviament

A la lliçó 04-02 vam desgranar el refspec de descàrrega. El d'enviament fa servir exactament la mateixa sintaxi, i veure'l tanca el cercle.

La forma completa d'un push és:

git push <remot> <refspec-local>:<refspec-remota>
git push origin main:main

Llegit: «envia la meva branca main i actualitza amb ella la branca main del servidor.»

main            :            main
└─┬─┘                        └─┬─┘
ORIGEN                      DESTÍ
(aquí, al meu               (allà, al
 repositori)                 servidor)

Comparat amb el refspec de descàrrega, la lògica és idèntica i només s'inverteix la direcció:

Refspec Origen (esquerra) Destí (dreta)
fetch +refs/heads/*:refs/remotes/origin/* El servidor El teu disc
push main:main El teu disc El servidor

En tots dos casos, esquerra = d'on surt, dreta = on va. Un cop ho veus així, deixa de ser sintaxi arbitrària.

Casos que només es poden expressar amb la forma completa

# Enviar la meva branca local a una branca del servidor amb UN ALTRE nom
git push origin funcionalitat/ordre-alfabetic:experimental/ordre

# Enviar un commit concret (no la punta de la branca) a una branca remota
git push origin 9d1e4b7:refs/heads/revisio-parcial

# Enviar HEAD a una branca amb nom diferent
git push origin HEAD:main

# Actualitzar una branca del servidor amb el contingut d'UNA ALTRA branca meva
git push origin main:produccio

El tercer, HEAD:main, és útil quan estàs en HEAD desacoblat o en una branca amb nom local diferent i vols enviar el que tens a main.

Noms curts i noms complets

Aquestes dues línies són equivalents:

git push origin main:main
git push origin refs/heads/main:refs/heads/main

Git expandeix els noms curts. Però hi ha un cas en què la forma completa és obligatòria: quan la branca de destí encara no existeix i el nom és ambigu. Si al servidor hi hagués una etiqueta anomenada revisio i volguessis crear una branca amb aquest nom, Git no sabria què vols:

git push origin main:revisio
error: dst refspec revisio matches more than one

La solució és ser explícit:

git push origin main:refs/heads/revisio

Els dos punts buits: esborrar

Si deixes el costat esquerre buit, estàs enviant res a aquella referència, cosa que l'esborra:

git push origin :funcionalitat/antiga

És la sintaxi antiga d'esborrat, i la veiem a l'apartat 8.

  1. Les formes abreujades i push.default

En el dia a dia gairebé ningú escriu el refspec complet. Aquestes són les formes curtes i què significa cadascuna:

# 1. Tot explícit
git push origin main:main

# 2. Un sol nom: s'envia a la branca del mateix nom
git push origin main

# 3. Només el remot: depèn de push.default
git push origin

# 4. Res: fa servir el remot de seguiment i push.default
git push

La forma 4 és la que faràs servir el 95 % de les vegades, i el seu comportament depèn d'un ajust que ja vam configurar a la lliçó 01-06:

git config --global push.default simple

Els valors possibles i què fan:

Valor Comportament
simple (Per defecte des de Git 2.0) Envia només la branca actual, i només si la remota es diu igual. Si no hi ha seguiment, s'hi nega i et diu què has d'escriure
current Envia la branca actual a una branca del mateix nom, creant-la si cal. No exigeix seguiment
upstream Envia la branca actual a la seva branca de seguiment, encara que es digui diferent
matching (El comportament vell, perillós) Envia totes les branques locals que tinguin una homònima al servidor
nothing Es nega a fer res sense un refspec explícit

matching mereix un advertiment històric. Era el valor per defecte fins a Git 2.0, i provocava una sorpresa desagradable: escrivies git push pensant a enviar la branca on eres, i Git enviava de cop les vuit branques locals que tenien homònima al servidor, inclosos experiments a mitges que no volies publicar. Canviar-lo a simple va ser una de les millors decisions de Git. No el posis a matching.

I una opció que sí que és útil quan de debò vols enviar-ne diverses:

# Enviar TOTES les branques locals, explícitament
git push --all origin

Explícit i conscient, que és com ha de ser.

  1. Quan el servidor diu que no: non-fast-forward

Aquest és el rebuig més freqüent de Git i el que cal entendre de debò.

En Bruno es posa a treballar al matí, fa dos commits i envia:

git push
To https://git.exemple.cat/equip/gestor-tasques.git
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to 'https://git.exemple.cat/equip/gestor-tasques.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.

Per què passa

El servidor té commits que tu no tens. Mentre en Bruno treballava, l'Ana va enviar els seus. El graf està així:

flowchart LR
    C1["c2a8f1e"] --> C2["9e2f4a7"]
    C1 --> C3["7d1f4b8"]
    C2 --> C4["b4d7e93<br/><b>main del servidor</b>"]
    C3 --> C5["3e9c2a5<br/><b>main d'en Bruno</b>"]

    style C4 fill:#e8f0ff
    style C5 fill:#fff0e8

Tots dos van partir de c2a8f1e i cadascun va avançar pel seu costat. Han divergit, exactament el concepte de la lliçó 03-01.

Si Git acceptés l'enviament d'en Bruno, la referència main del servidor passaria de b4d7e93 a 3e9c2a5. I com que b4d7e93 no és un avantpassat de 3e9c2a5, els dos commits de l'Ana deixarien de ser assolibles des de cap branca: desapareixerien del projecte sense que ningú se n'assabentés, i qualsevol que clonés després no en veuria ni rastre.

Per això Git rebutja l'enviament: per no destruir feina aliena. La regla és que una referència del servidor només pot avançar de manera fast-forward, és a dir, cap a un descendent d'on era. És la mateixa noció de fast-forward del mòdul 3, aplicada ara com a mesura de protecció.

I aquí convé relacionar dues coses. Recorda de la lliçó 04-02 que el refspec de descàrrega porta un + al davant:

fetch = +refs/heads/*:refs/remotes/origin/*

Aquest + permet actualitzacions no fast-forward a les teves referències remotes, perquè la seva feina és reflectir el servidor sigui quin sigui el seu estat. A l'enviament no hi ha cap + per defecte, i és deliberat: cap endins, permissiu; cap enfora, protector.

Els missatges que veuràs

Missatge Situació
! [rejected] main -> main (fetch first) El servidor té commits que tu no tens
! [rejected] main -> main (non-fast-forward) El mateix, amb la referència ja descarregada
! [rejected] main -> main (stale info) La teva informació del servidor està desactualitzada (típic amb --force-with-lease)

La sortida bàsica

El procediment és sempre el mateix, i és exactament el que vas aprendre a la lliçó anterior:

# 1. Portar el que hi ha al servidor
git fetch origin

# 2. Veure què és
git log --oneline main..origin/main
git log --oneline origin/main..main

# 3. Integrar
git merge origin/main

# 4. Tornar a enviar
git push

Amb pull.ff only configurat, el pas 3 en forma de git pull s'aturarà dient Not possible to fast-forward, perquè hi ha divergència real i Git no decideix per tu. En aquest punt has d'integrar explícitament.

El que no has de fer mai és el que suggereix l'instint i algun que altre consell malintencionat d'internet:

# NO. Això destrueix els commits de l'Ana.
git push --force

Això sobreescriu la referència del servidor amb la teva versió, i els commits de l'Ana queden orfes. Ella els recuperarà del seu propi disc, sí, però qualsevol que cloni en aquest interval no els veurà, i el desconcert està garantit.

Aquest apartat cobreix la causa i la sortida bàsica. El tractament complet de la divergència —com decidir entre fusionar, reaplicar o replantejar; què fer si a més hi ha conflictes; com sortir dels casos embolicats— és el tema de la lliçó 09-03: Resolent Divergències amb el Remot. Aquí queda't amb tres idees:

  1. El rebuig és una protecció, no una fallada.
  2. La causa és sempre la mateixa: el remot té commits que tu no tens.
  3. La sortida per defecte és integrar primer i enviar després.

  1. --force-with-lease davant de --force

Hi ha situacions legítimes en què necessites sobreescriure una branca del servidor: has reescrit la teva pròpia branca de treball (amb les tècniques del mòdul 5) i el resultat ja no és descendent del que hi ha publicat. En aquests casos, l'enviament normal serà rebutjat amb tota la raó, i cal forçar.

Però hi ha dues maneres de forçar, i la diferència entre elles pot ser la feina d'una tarda d'un company.

--force: «escriu això, passi el que passi»

git push --force origin funcionalitat/ordre-alfabetic

Git sobreescriu la referència del servidor sense comprovar res. Si algú havia enviat alguna cosa mentrestant, els seus commits queden orfes.

--force-with-lease: «escriu això, si el servidor continua on jo crec»

git push --force-with-lease origin funcionalitat/ordre-alfabetic

Git compara el valor actual de la referència al servidor amb el teu origin/funcionalitat/ordre-alfabetic, és a dir, amb l'última foto que tens. Si coincideixen, sobreescriu. Si no coincideixen —perquè algú ha enviat alguna cosa des del teu darrer fetch—, rebutja l'enviament:

 ! [rejected]        funcionalitat/ordre-alfabetic -> funcionalitat/ordre-alfabetic (stale info)
error: failed to push some refs

La paraula lease és «arrendament»: tu tenies reservada la referència en un estat concret, i si ha canviat, el contracte es trenca i no s'escriu res.

flowchart TB
    P["git push --force-with-lease"] --> Q{"El servidor és on<br/>diu el meu origin/branca?"}
    Q -->|Sí| A["Sobreescriu:<br/>ningú no ha tocat res<br/>des del meu darrer fetch"]
    Q -->|No| R["REBUTJA:<br/>algú ha enviat alguna cosa<br/>que jo no he vist"]

    style A fill:#e8f4e8
    style R fill:#f9e8e8

La comparativa

--force (-f) --force-with-lease
Comprova l'estat del servidor No
Si un company ha enviat mentrestant El destrueix en silenci Rebutja l'enviament
Protegeix de reescriure feina aliena No
Necessita un fetch recent per ser útil (vegeu l'avís de sota)
Quan fer-lo servir Pràcticament mai Quan de debò cal forçar

L'avís important sobre --force-with-lease

La protecció es basa en la teva referència remota local. I això obre un forat:

# PERILL: això anul·la la protecció
git fetch
git push --force-with-lease

Si fas fetch just abans, el teu origin/branca s'actualitza amb el que el company acaba d'enviar, la comparació coincideix i --force-with-lease accepta encantat… sobreescrivint precisament allò de què l'opció t'havia de protegir.

Hi ha dues maneres de fer-ho bé:

Primera: mirar abans de forçar. Un fetch seguit d'una inspecció deliberada:

git fetch origin
git log --oneline origin/funcionalitat/ordre-alfabetic
# És tot meu? Reconec tots els commits?
git push --force-with-lease

Segona, i millor: dir explícitament quin valor esperes.

git push --force-with-lease=funcionalitat/ordre-alfabetic:9d1e4b7 origin funcionalitat/ordre-alfabetic

Aquí no depens de cap foto: li dius a Git «només escriu si el servidor és exactament a 9d1e4b7». És la forma més segura, i la que convé fer servir en scripts.

Des de Git 2.30 existeix a més una opció complementària:

git push --force-if-includes --force-with-lease

Comprova, a més, que els commits que hi ha al servidor estan inclosos en el que enviaràs, és a dir, que realment els has integrat i no simplement descarregat.

Les regles d'or del forçat

  1. No forcis mai main, develop ni cap branca compartida. Si l'equip hi treballa a sobre, reescriure-la obliga tothom a resoldre un embolic. I és exactament el que impedeixen les branques protegides de les plataformes.
  2. Força només branques teves, de treball, que ningú més estigui fent servir.
  3. Fes servir sempre --force-with-lease. Converteix --force en una paraula que no escrius.
  4. Avisa l'equip si forces una branca que algú pugui tenir clonada.
  5. Si dubtes, no forcis. Un historial amb un commit de fusió de més no ha matat mai ningú; un --force sobre main un divendres, gairebé.

Un àlies que ajuda que el bon costum sigui el més còmode:

git config --global alias.pushf "push --force-with-lease"

I una nota tranquil·litzadora, perquè convé no viure amb por: si algú força i destrueix commits, gairebé sempre es poden recuperar. Qui els tenia al seu disc els conserva; i al servidor, el reflog o les eines de la plataforma solen permetre rescatar-los. És un embolic, no una catàstrofe. La recuperació s'estudia a la lliçó 09-04.

  1. Esborrar una branca al servidor

Quan una branca s'integra, cal esborrar-la als dos llocs: al teu repositori (amb git branch -d, lliçó 03-06) i al servidor.

La forma moderna

git push origin --delete funcionalitat/comptador-tasques
To https://git.exemple.cat/equip/gestor-tasques.git
 - [deleted]         funcionalitat/comptador-tasques

Llegible i difícil de confondre. És la que has de fer servir.

La forma antiga

git push origin :funcionalitat/comptador-tasques

Fa exactament el mateix, i ara entens per què: és el refspec <origen>:<destí> amb l'origen buit. Estàs enviant «res» a aquella referència del servidor, cosa que l'elimina.

La veuràs en documentació i en scripts antics, així que convé reconèixer-la. Però també convé saber-ne el risc: un espai de més i estàs esborrant una cosa que no volies. Compara:

git push origin :main          # ESBORRA main del servidor
git push origin main           # Envia main al servidor

Un caràcter de diferència entre publicar i destruir. Per això --delete és millor.

Què cal fer després

Esborrar una branca al servidor no esborra les referències que tenen els teus companys. Cadascun les haurà de netejar, com vam veure a la lliçó anterior:

# Al repositori de cada persona
git fetch --prune

I si a més tenen la branca local, aquesta s'esborra a part:

git branch -d funcionalitat/comptador-tasques

Tres coses diferents un altre cop: la branca del servidor, la referència remota de cadascú i la branca local de cadascú. La lliçó següent insisteix en aquesta distinció.

Un avís: esborrar una branca al servidor pot deixar els seus commits sense cap referència que els assoleixi. Si aquella branca no estava fusionada, aquests commits queden a mercè del recol·lector d'escombraries del servidor. Comprova abans amb git log --oneline main..origin/la-branca que no hi ha res a perdre.

  1. Enviar etiquetes

Un detall que sorprèn gairebé tothom la primera vegada: git push no envia etiquetes.

git tag v1.0.0
git push
Everything up-to-date

L'etiqueta s'ha creat al teu repositori i allà es queda. El refspec per defecte només tracta branques (refs/heads/*), no etiquetes (refs/tags/*).

Les maneres d'enviar-les:

# Una etiqueta concreta
git push origin v1.0.0

# TOTES les etiquetes locals
git push origin --tags

# Només les etiquetes ANOTADES que apunten a commits que s'estan enviant
git push origin --follow-tags

La diferència entre les dues últimes importa:

Opció Què envia
--tags Totes les teves etiquetes locals, incloses les de proves i les que apunten a commits sense enviar
--follow-tags Només les etiquetes anotades que apunten a commits assolibles des del que estàs enviant

--follow-tags és gairebé sempre l'opció correcta en l'ús diari, i es pot deixar configurada:

git config --global push.followTags true

I per esborrar una etiqueta del servidor, la mateixa sintaxi de sempre:

git push origin --delete v1.0.0
git push origin :refs/tags/v1.0.0    # forma antiga equivalent

Què són les etiquetes, la diferència entre lleugeres i anotades, com es fan servir per marcar versions i com encaixen en el flux de publicació és el tema de la lliçó 05-05: Etiquetant Confirmacions. Aquí només ens interessa el transport: que no viatgen soles i amb quina opció s'envien.

  1. L'Ana publica les seves branques; en Bruno i la Carla les reben

Tanquem amb l'escenari complet de l'equip.

L'Ana té main ja publicada i dues branques locals pendents de compartir: documentacio/actualitzar-notes i correccio/focus-despres-esborrar.

git branch -vv
  correccio/focus-despres-esborrar  8a3f7c1 Corregeix el focus després d'esborrar una tasca
  documentacio/actualitzar-notes    7f3c9a2 Actualitza les notes internes amb el nou flux
* main                              b4d7e93 [origin/main] Ajusta l'estil del peu de pàgina

Només main té branca de seguiment (els claudàtors). Les altres dues encara no existeixen al servidor.

git push origin documentacio/actualitzar-notes
Enumerating objects: 5, done.
Writing objects: 100% (3/3), 412 bytes | 412.00 KiB/s, done.
To https://git.exemple.cat/equip/gestor-tasques.git
 * [new branch]      documentacio/actualitzar-notes -> documentacio/actualitzar-notes
branch 'documentacio/actualitzar-notes' set up to track 'origin/documentacio/actualitzar-notes'.

Fixa't en l'última línia: el seguiment s'ha configurat sol, sense -u. És push.autoSetupRemote true de la lliçó 01-06 funcionant.

git switch correccio/focus-despres-esborrar
git push
 * [new branch]      correccio/focus-despres-esborrar -> correccio/focus-despres-esborrar
branch 'correccio/focus-despres-esborrar' set up to track 'origin/correccio/focus-despres-esborrar'.

Ni tan sols ha calgut anomenar el remot ni la branca. Estat final:

git ls-remote origin
b4d7e935f1c8a2e6d4b7f9a3c1e5b8d2f4a6c9e7	HEAD
8a3f7c1e4b9d2f6a5c8e3b7d1f4a9c2e6b5d8f3a	refs/heads/correccio/focus-despres-esborrar
7f3c9a28e4b1d6f9a2c5e8b3d7f1a4c6e9b2d5f8	refs/heads/documentacio/actualitzar-notes
b4d7e935f1c8a2e6d4b7f9a3c1e5b8d2f4a6c9e7	refs/heads/main

En Bruno rep

cd ~/projectes/gestor-tasques
git fetch --prune
From https://git.exemple.cat/equip/gestor-tasques
 * [new branch]      correccio/focus-despres-esborrar -> origin/correccio/focus-despres-esborrar
 * [new branch]      documentacio/actualitzar-notes   -> origin/documentacio/actualitzar-notes
git branch -a
* main
  remotes/origin/HEAD -> origin/main
  remotes/origin/correccio/focus-despres-esborrar
  remotes/origin/documentacio/actualitzar-notes
  remotes/origin/main

Les branques de l'Ana són al seu disc com a referències remotes, no com a branques locals. Les pot examinar sense crear res:

git log --oneline origin/correccio/focus-despres-esborrar -3
git diff main origin/correccio/focus-despres-esborrar

La Carla rep i treballa

La Carla fa el mateix i decideix continuar la correcció de l'Ana:

git fetch --prune
git switch correccio/focus-despres-esborrar
branch 'correccio/focus-despres-esborrar' set up to track 'origin/correccio/focus-despres-esborrar'.
Switched to a new branch 'correccio/focus-despres-esborrar'

Treballa, confirma i envia:

echo "// Retorna el focus al camp després d'esborrar" >> app.js
git commit -am "Retorna el focus al camp després d'esborrar una tasca"
git push
To [email protected]:equip/gestor-tasques.git
   8a3f7c1..d5e9b2f  correccio/focus-despres-esborrar -> correccio/focus-despres-esborrar

Aquest és el cicle complet tancat. Fixa't en la forma d'aquella última línia, diferent de la del primer enviament:

  • * [new branch] X -> X → s'ha creat una branca al servidor.
  • 8a3f7c1..d5e9b2f X -> X → una branca existent ha avançat d'un commit a un altre, en fast-forward.

El projecte que feia tres mòduls que estava tancat en un portàtil ja circula entre tres màquines i tres sistemes operatius diferents.

Errors Habituals i Consells

Error 1: «he fet push i no veuen el meu canvi». El 90 % de les vegades, el canvi no es va arribar a confirmar. git push envia commits, no fitxers modificats. Comprova-ho amb git status i git log --oneline -3.

Error 2: respondre a un rebuig amb --force. El rebuig non-fast-forward significa que el servidor té feina que tu no tens. Forçar la destrueix. La resposta correcta és fetch + integrar + enviar. Vegeu 09-03.

Error 3: fer servir --force en comptes de --force-with-lease. El primer no comprova res; el segon s'hi nega si algú ha enviat alguna cosa des del teu darrer fetch. Costa el mateix escriure l'un que l'altre; fes un àlies.

Error 4: fer fetch just abans de --force-with-lease. Anul·la la protecció, perquè actualitza la referència amb la qual Git compara. O mires deliberadament què ha arribat, o fas servir la forma --force-with-lease=branca:hash.

Error 5: forçar una branca compartida. Reescriure main obliga tot l'equip a resoldre un embolic que no ha causat. Força només branques teves de treball.

Error 6: creure que git push envia les etiquetes. No ho fa: el refspec per defecte només cobreix refs/heads/*. Fes servir --follow-tags o configura push.followTags true.

Error 7: fer servir git push origin :branca sense fixar-s'hi. Un espai de més entre : i el nom canvia el significat del tot. Fes servir --delete, que diu el que fa.

Error 8: esborrar una branca del servidor i esperar que desaparegui del repositori dels altres. Cada persona ha de fer git fetch --prune. Configura fetch.prune true a l'equip.

Consell 1: mira abans d'enviar. git log --oneline origin/main..main t'ensenya exactament quins commits sortiran. Deu segons que eviten publicar un wip o un console.log oblidat.

Consell 2: fes git fetch abans de començar, no abans d'enviar. Així detectes la divergència quan encara és barata de resoldre, en comptes de descobrir-la amb la feina acabada.

Consell 3: àlies per al forçat segur.

git config --global alias.pushf "push --force-with-lease"

Consell 4: revisa què configura push.default. El valor simple és el correcte. Si treballes en un equip amb configuracions antigues, comprova que ningú no té matching.

Consell 5: --dry-run per assajar.

git push --dry-run origin main

Mostra què faria l'enviament sense arribar a fer-lo. Útil amb refspecs complicats o abans d'un esborrat.

Exercicis

Exercici 1: anatomia d'un enviament

Munta un servidor bare i un repositori de treball. Després:

  1. Confirma tres commits i envia amb -u.
  2. Mostra el .git/config abans i després, i assenyala quines línies hi ha afegit el -u.
  3. Explica la sortida completa del push, línia per línia.
  4. Confirma un altre commit i envia sense -u. Què canvia a la sortida i per què?
  5. Comprova des del servidor que les referències són on han de ser, sense clonar.

Exercici 2: provocar i resoldre un rebuig

Amb un servidor i dos clons (l'Ana i en Bruno):

  1. Tots dos parteixen del mateix commit.
  2. L'Ana confirma i envia.
  3. En Bruno confirma (sense fer fetch) i intenta enviar.
  4. Captura el missatge de rebuig i explica exactament per què s'ha produït, dibuixant el graf.
  5. Resol-ho correctament sense forçar i demostra que els commits de tots dos continuen al servidor.
  6. Repeteix l'experiment resolent-ho amb --force i demostra que el commit de l'Ana ha desaparegut de la branca.

Exercici 3: refspecs d'enviament

Amb un repositori connectat a un bare, aconsegueix el següent i explica cada ordre:

  1. Enviar la teva branca local funcionalitat/prova a una branca del servidor anomenada experimental/prova.
  2. Enviar l'estat d'un commit anterior a la punta de la teva branca a una branca del servidor anomenada revisio.
  3. Enviar HEAD a main estant en HEAD desacoblat.
  4. Esborrar experimental/prova del servidor de les dues maneres possibles.
  5. Comprovar el resultat amb git ls-remote després de cada pas.

Solucions

Solució 1:

mkdir -p /tmp/ex-push && cd /tmp/ex-push
git init --bare servidor.git
git init -b main feina
cd feina
# 1. Tres commits
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"

# 2a. Configuració ABANS
git remote add origin /tmp/ex-push/servidor.git
cat .git/config
[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true
[remote "origin"]
	url = /tmp/ex-push/servidor.git
	fetch = +refs/heads/*:refs/remotes/origin/*
git push -u origin main
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 8 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (9/9), 782 bytes | 782.00 KiB/s, done.
Total 9 (delta 1), reused 0 (delta 0), pack-reused 0
To /tmp/ex-push/servidor.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.
# 2b. Configuració DESPRÉS
cat .git/config
[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true
[remote "origin"]
	url = /tmp/ex-push/servidor.git
	fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
	remote = origin
	merge = refs/heads/main

Les línies noves són la secció [branch "main"] amb remote i merge. Això és tot el que fa -u: establir la branca de seguiment.

3. La sortida, línia per línia:

  • Enumerating objects: 9 — Git ha identificat 9 objectes per enviar: 3 commits + 3 arbres + 3 blobs.
  • Delta compression using up to 8 threads — comprimeix en paral·lel, desant diferències entre objectes semblants.
  • Writing objects: 100% (9/9), 782 bytes — la pujada real: 782 bytes.
  • Total 9 (delta 1), reused 0 — dels 9 objectes, 1 s'ha desat com a diferència respecte d'un altre; cap no s'ha reutilitzat perquè el servidor estava buit.
  • To /tmp/ex-push/servidor.git — el destí.
  • * [new branch] main -> mains'ha creat la branca main al servidor. L'asterisc i [new branch] marquen la creació.
  • branch 'main' set up to track 'origin/main' — l'efecte del -u.
# 4. Un altre commit, enviament sense -u
echo "# Gestor de tasques" > README.md
git add . && git commit -m "Documenta la instal·lació al README"
git push
Enumerating objects: 4, done.
Writing objects: 100% (3/3), 298 bytes | 298.00 KiB/s, done.
To /tmp/ex-push/servidor.git
   7d2f9a1..4c8e3b6  main -> main

Dues diferències, i totes dues tenen explicació:

  • N'hi ha hagut prou amb git push a seques: el seguiment ja estava configurat pel -u anterior, així que Git sap a quin remot i a quina branca ha d'enviar.
  • La línia de resultat ja no diu [new branch] sinó 7d2f9a1..4c8e3b6: la branca ja existia i ha avançat d'un commit a un altre. Aquest rang amb dos punts indica un fast-forward, exactament la notació que vam veure al fetch.

A més, només s'han enviat 3 objectes nous en comptes dels 9 inicials: l'enviament és incremental.

# 5. Comprovar el servidor sense clonar
git ls-remote origin
4c8e3b6f9a2d5c8e1b7f4a3d6c9e2b5f8a1d4c7e	HEAD
4c8e3b6f9a2d5c8e1b7f4a3d6c9e2b5f8a1d4c7e	refs/heads/main
# Una altra via: consultar el bare directament
git --git-dir=/tmp/ex-push/servidor.git log --oneline main
4c8e3b6 Documenta la instal·lació al README
7d2f9a1 Afegeix l'esborrat de tasques al llistat
9f4c2a8 Afegeix els estils base del llistat
5a1d8f3 Estructura inicial del gestor de tasques

Solució 2:

mkdir -p /tmp/ex-rebuig && cd /tmp/ex-rebuig
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
# 2. L'Ana confirma i envia
cd /tmp/ex-rebuig/ana
echo "comptador" >> f.txt
git commit -am "Afegeix el comptador de tasques pendents"
git push
git log --oneline -2
9e2f4a7 Afegeix el comptador de tasques pendents
5a1d8f3 Estructura inicial del gestor de tasques
# 3. En Bruno confirma sense fetch i intenta enviar
cd /tmp/ex-rebuig/bruno
echo "filtre" >> f.txt
git commit -am "Afegeix el filtre de tasques pendents"
git push
To /tmp/ex-rebuig/servidor.git
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to '/tmp/ex-rebuig/servidor.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally.

4. Per què: tots dos van partir de 5a1d8f3. L'Ana va crear 9e2f4a7 a sobre i el va publicar; en Bruno va crear 7d1f4b8 a sobre del mateix 5a1d8f3. El graf:

                  9e2f4a7  ← main del servidor (Ana)
                 /
    5a1d8f3 ────
                 \
                  7d1f4b8  ← main d'en Bruno

Perquè l'enviament d'en Bruno fos vàlid, 9e2f4a7 hauria de ser avantpassat de 7d1f4b8. No ho és: són branques germanes. Si el servidor acceptés l'actualització, refs/heads/main passaria a 7d1f4b8 i 9e2f4a7 deixaria de ser assolible: el commit de l'Ana desapareixeria del projecte. Git ho impedeix.

Es pot comprovar formalment:

git fetch origin
git merge-base --is-ancestor origin/main main && echo "Seria fast-forward" || echo "NO és fast-forward: per això rebutja"
NO és fast-forward: per això rebutja
# 5. Resolució correcta
git log --oneline main..origin/main
9e2f4a7 Afegeix el comptador de tasques pendents
git merge origin/main

Si el fitxer és el mateix, hi haurà conflicte (tots dos van afegir una línia al final de f.txt). Es resol amb el que vam aprendre a 03-05:

# Resoldre deixant les dues aportacions
printf "base\ncomptador\nfiltre\n" > f.txt
git add f.txt
git commit -m "Fusiona el comptador i el filtre de tasques"
git push
To /tmp/ex-rebuig/servidor.git
   9e2f4a7..c3b8f5d  main -> main
# Els commits dels DOS són al servidor
git --git-dir=/tmp/ex-rebuig/servidor.git log --oneline --graph main
*   c3b8f5d Fusiona el comptador i el filtre de tasques
|\
| * 9e2f4a7 Afegeix el comptador de tasques pendents
* | 7d1f4b8 Afegeix el filtre de tasques pendents
|/
* 5a1d8f3 Estructura inicial del gestor de tasques

No s'ha perdut res. Les dues feines conviuen, unides per un commit de fusió amb dos pares, exactament com al mòdul 3.

# 6. La versió destructiva, per veure-ho amb els teus propis ulls
cd /tmp/ex-rebuig
rm -rf servidor.git ana bruno
git init --bare servidor.git
git clone servidor.git ana
cd ana && echo "base" > f.txt && git add . && git commit -m "Base" && git push -u origin main && cd ..
git clone servidor.git bruno

cd ana
echo "comptador" >> f.txt && git commit -am "Afegeix el comptador de tasques pendents" && git push
cd ../bruno
echo "filtre" >> f.txt && git commit -am "Afegeix el filtre de tasques pendents"
git push --force
To /tmp/ex-rebuig/servidor.git
 + 9e2f4a7...7d1f4b8 main -> main (forced update)

Aquest + i el (forced update) són la marca del desastre:

git --git-dir=/tmp/ex-rebuig/servidor.git log --oneline main
7d1f4b8 Afegeix el filtre de tasques pendents
5a1d8f3 Base

El commit de l'Ana ja no és a la branca. Qualsevol que cloni ara no el veurà. Continua existint com a objecte solt al servidor —i evidentment al repositori de l'Ana—, però cap referència no l'assoleix. I compara-ho amb el que hauria passat fent servir l'opció segura:

git push --force-with-lease
 ! [rejected]        main -> main (stale info)

Rebutjat. El servidor no era on la foto d'en Bruno deia, així que la protecció ha actuat. Aquesta és, en una línia, la raó per no escriure --force mai més.

Solució 3:

mkdir -p /tmp/ex-refspec && cd /tmp/ex-refspec
git init --bare servidor.git
git init -b main feina
cd feina
echo "base" > f.txt && git add . && git commit -m "Base"
git remote add origin /tmp/ex-refspec/servidor.git
git push -u origin main

git switch -c funcionalitat/prova
echo "un" >> f.txt && git commit -am "Primer pas"
echo "dos" >> f.txt && git commit -am "Segon pas"
echo "tres" >> f.txt && git commit -am "Tercer pas"
# 1. Branca local -> branca remota amb UN ALTRE nom
git push origin funcionalitat/prova:experimental/prova
 * [new branch]      funcionalitat/prova -> experimental/prova

El refspec <origen>:<destí> desacobla els dos noms: a l'esquerra el que envio, a la dreta com es diu allà.

git ls-remote origin
5f8b2e1...	HEAD
9c4e2b7...	refs/heads/experimental/prova
5f8b2e1...	refs/heads/main
# 2. Un commit anterior a la punta, a una branca nova
git log --oneline -3
9c4e2b7 Tercer pas
2c9d4e6 Segon pas
8f1a3d5 Primer pas
git push origin 2c9d4e6:refs/heads/revisio
 * [new branch]      2c9d4e6 -> revisio

Aquí la forma completa refs/heads/revisio és necessària: com que la branca de destí encara no existeix, Git no pot deduir si vols crear una branca o una etiqueta. Amb el nom complet no hi ha ambigüitat. I fixa't que el costat esquerre és un hash, no un nom de branca: qualsevol expressió que resolgui a un commit val.

git ls-remote origin | grep revisio
2c9d4e6f8a3b1d5c9e2f7a4b8d6c1e3f5a9b2d7c	refs/heads/revisio
# 3. HEAD desacoblat -> main
git switch --detach 8f1a3d5
git push origin HEAD:refs/heads/des-de-detached
 * [new branch]      HEAD -> des-de-detached

HEAD resol al commit on ets, encara que no hi hagi cap branca apuntant-lo. És la manera de publicar feina des d'un HEAD desacoblat sense haver de crear una branca local abans.

git switch funcionalitat/prova
# 4a. Esborrat, forma moderna
git push origin --delete experimental/prova
 - [deleted]         experimental/prova
# 4b. Esborrat, forma antiga
git push origin :revisio
 - [deleted]         revisio

Totes dues fan el mateix. La segona és el refspec amb l'origen buit: enviar «res» a aquella referència l'elimina. És exactament la mateixa sintaxi, portada a l'extrem.

# 5. Comprovació final
git ls-remote origin
5f8b2e1...	HEAD
8f1a3d5...	refs/heads/des-de-detached
5f8b2e1...	refs/heads/main

Queden main i des-de-detached; experimental/prova i revisio han desaparegut. I un detall important: les branques locals d'aquest repositori no s'han vist afectades per cap dels esborrats.

git branch
* funcionalitat/prova
  main

Conclusió

Amb aquesta lliçó, la feina de l'equip circula en els dos sentits. L'essencial:

  • git push envia objectes i demana actualitzar una referència. No envia el teu directori de treball, ni el teu índex, ni la teva configuració, ni les teves altres branques. Només viatja el que és dins d'un commit.
  • git push -u origin <branca>: el -u escriu la secció [branch "…"] a .git/config i estableix la branca de seguiment. A partir d'aquí, git push i git pull a seques funcionen i git status pot informar-te del desfasament. Amb push.autoSetupRemote true (lliçó 01-06) ni tan sols cal.
  • El refspec d'enviament fa servir la mateixa sintaxi que el de descàrrega: origen:destí, esquerra d'on surt i dreta on va. Permet enviar a una branca amb un altre nom, enviar un commit concret o enviar HEAD.
  • push.default simple envia només la branca actual. L'antic matching enviava totes les homònimes i era una font de sorpreses: no el facis servir.
  • El rebuig non-fast-forward passa perquè el remot té commits que tu no tens. Si Git acceptés l'enviament, aquests commits deixarien de ser assolibles i desapareixerien del projecte. És una protecció, no una fallada. La sortida bàsica és fetch → integrar → enviar; el tractament complet, a la lliçó 09-03.
  • --force-with-lease davant de --force: el primer comprova que el servidor continua on diu la teva referència remota i rebutja si algú ha enviat alguna cosa; el segon sobreescriu sense mirar i pot deixar òrfena la feina d'una altra persona. Fes servir sempre el primer, i no facis fetch just abans (o fes servir la forma --force-with-lease=branca:hash). No forcis mai branques compartides.
  • Esborrar una branca del servidor: git push origin --delete <branca>, o la forma antiga git push origin :<branca> (refspec amb origen buit). Els altres hauran de fer git fetch --prune perquè desaparegui de les seves referències.
  • Les etiquetes no viatgen soles: --tags les envia totes, --follow-tags només les anotades assolibles des del que envies, que sol ser el que vols. Les etiquetes, a la lliçó 05-05.

El que ve

Ja saps enviar i rebre. Però al llarg d'aquestes lliçons han anat apareixent missatges que encara no hem explicat del tot: «Your branch is ahead of 'origin/main' by 2 commits», «branch 'main' set up to track 'origin/main'», aquells claudàtors de git branch -vv, i aquell git switch que crea una branca local a partir d'una de remota sense que li ho demanis.

Tot això apunta al mateix concepte: les branques de seguiment. A la lliçó 04-06: Rastrejant Branques, que tanca el mòdul, veurem què és exactament l'upstream d'una branca i on viu a .git/config, aclarirem d'una vegada la diferència entre main, origin/main i el main del servidor, i desmuntarem com calcula Git aquell «2 commits per davant». És la lliçó que lliga tots els caps del mòdul.

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