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
- Què envia exactament
git push - El primer enviament:
git push -u origin main - Què fa exactament el
-u - El refspec d'enviament
- Les formes abreujades i
push.default - Quan el servidor diu que no: non-fast-forward
--force-with-leasedavant de--force- Esborrar una branca al servidor
- Enviar etiquetes
- L'Ana publica les seves branques; en Bruno i la Carla les reben
- Què envia exactament
git push
git pushLa definició precisa, que és més estreta del que la gent imagina:
git pushfa 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.jsi no has fetcommit, aquell canvi no surt de la teva màquina per moltspushque facis. - El teu índex. L'àrea de preparació és teva i personal.
- La teva configuració.
.git/configno 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.
- El primer enviament:
git push -u origin main
git push -u origin mainTornem 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:
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:
c2a8f1e4b6d9f3a7e2c5b8d1f4a6c9e3b7d2f5a8 HEAD c2a8f1e4b6d9f3a7e2c5b8d1f4a6c9e3b7d2f5a8 refs/heads/main
El repositori que estava buit ja té una branca i una història.
- Què fa exactament el
-u
-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:
[remote "origin"]
url = https://git.exemple.cat/equip/gestor-tasques.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
remote = origin
merge = refs/heads/mainAquesta 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:
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ò:
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.
- 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:
Llegit: «envia la meva branca main i actualitza amb ella la branca main del 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:produccioEl 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 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:
La solució és ser explícit:
Els dos punts buits: esborrar
Si deixes el costat esquerre buit, estàs enviant res a aquella referència, cosa que l'esborra:
És la sintaxi antiga d'esborrat, i la veiem a l'apartat 8.
- Les formes abreujades i
push.default
push.defaultEn 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 pushLa 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:
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:
Explícit i conscient, que és com ha de ser.
- 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:
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:
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 pushAmb 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:
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:
- El rebuig és una protecció, no una fallada.
- La causa és sempre la mateixa: el remot té commits que tu no tens.
- La sortida per defecte és integrar primer i enviar després.
--force-with-lease davant de --force
--force-with-lease davant de --forceHi 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 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 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 | Sí |
| Si un company ha enviat mentrestant | El destrueix en silenci | Rebutja l'enviament |
| Protegeix de reescriure feina aliena | No | Sí |
Necessita un fetch recent per ser útil |
— | Sí (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:
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-leaseSegona, i millor: dir explícitament quin valor esperes.
git push --force-with-lease=funcionalitat/ordre-alfabetic:9d1e4b7 origin funcionalitat/ordre-alfabeticAquí 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:
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
- No forcis mai
main,developni 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. - Força només branques teves, de treball, que ningú més estigui fent servir.
- Fes servir sempre
--force-with-lease. Converteix--forceen una paraula que no escrius. - Avisa l'equip si forces una branca que algú pugui tenir clonada.
- Si dubtes, no forcis. Un historial amb un commit de fusió de més no ha matat mai ningú; un
--forcesobremainun divendres, gairebé.
Un àlies que ajuda que el bon costum sigui el més còmode:
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.
- 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
Llegible i difícil de confondre. És la que has de fer servir.
La forma antiga
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:
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:
I si a més tenen la branca local, aquesta s'esborra a part:
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.
- Enviar etiquetes
Un detall que sorprèn gairebé tothom la primera vegada: git push no envia etiquetes.
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-tagsLa 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:
I per esborrar una etiqueta del servidor, la mateixa sintaxi de sempre:
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.
- 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.
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.
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.
* [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:
b4d7e935f1c8a2e6d4b7f9a3c1e5b8d2f4a6c9e7 HEAD 8a3f7c1e4b9d2f6a5c8e3b7d1f4a9c2e6b5d8f3a refs/heads/correccio/focus-despres-esborrar 7f3c9a28e4b1d6f9a2c5e8b3d7f1a4c6e9b2d5f8 refs/heads/documentacio/actualitzar-notes b4d7e935f1c8a2e6d4b7f9a3c1e5b8d2f4a6c9e7 refs/heads/main
En Bruno rep
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
* 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-esborrarLa Carla rep i treballa
La Carla fa el mateix i decideix continuar la correcció de l'Ana:
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 pushTo [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.
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.
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:
- Confirma tres commits i envia amb
-u. - Mostra el
.git/configabans i després, i assenyala quines línies hi ha afegit el-u. - Explica la sortida completa del
push, línia per línia. - Confirma un altre commit i envia sense
-u. Què canvia a la sortida i per què? - 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):
- Tots dos parteixen del mateix commit.
- L'Ana confirma i envia.
- En Bruno confirma (sense fer
fetch) i intenta enviar. - Captura el missatge de rebuig i explica exactament per què s'ha produït, dibuixant el graf.
- Resol-ho correctament sense forçar i demostra que els commits de tots dos continuen al servidor.
- Repeteix l'experiment resolent-ho amb
--forcei 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:
- Enviar la teva branca local
funcionalitat/provaa una branca del servidor anomenadaexperimental/prova. - Enviar l'estat d'un commit anterior a la punta de la teva branca a una branca del servidor anomenada
revisio. - Enviar
HEADamainestant en HEAD desacoblat. - Esborrar
experimental/provadel servidor de les dues maneres possibles. - Comprovar el resultat amb
git ls-remotedespré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/*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'.
[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/mainLes 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 -> main— s'ha creat la brancamainal 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 pushEnumerating 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 pusha seques: el seguiment ja estava configurat pel-uanterior, 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 alfetch.
A més, només s'han enviat 3 objectes nous en comptes dels 9 inicials: l'enviament és incremental.
4c8e3b6f9a2d5c8e1b7f4a3d6c9e2b5f8a1d4c7e HEAD 4c8e3b6f9a2d5c8e1b7f4a3d6c9e2b5f8a1d4c7e refs/heads/main
# Una altra via: consultar el bare directament
git --git-dir=/tmp/ex-push/servidor.git log --oneline main4c8e3b6 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# 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 pushTo /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:
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"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# 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 --forceAquest + i el (forced update) són la marca del desastre:
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:
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/provaEl refspec <origen>:<destí> desacobla els dos noms: a l'esquerra el que envio, a la dreta com es diu allà.
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.
# 3. HEAD desacoblat -> main
git switch --detach 8f1a3d5
git push origin HEAD:refs/heads/des-de-detachedHEAD 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.
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.
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.
Conclusió
Amb aquesta lliçó, la feina de l'equip circula en els dos sentits. L'essencial:
git pushenvia 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-uescriu la secció[branch "…"]a.git/configi estableix la branca de seguiment. A partir d'aquí,git pushigit pulla seques funcionen igit statuspot informar-te del desfasament. Ambpush.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 enviarHEAD. push.default simpleenvia només la branca actual. L'anticmatchingenviava 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-leasedavant 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 facisfetchjust 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 antigagit push origin :<branca>(refspec amb origen buit). Els altres hauran de fergit fetch --pruneperquè desaparegui de les seves referències. - Les etiquetes no viatgen soles:
--tagsles envia totes,--follow-tagsnomé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
- 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ó
