Les tres lliçons anteriors van descriure tres fluxos de treball diferents, i totes tres van acabar assenyalant la mateixa dependència: comprovacions automàtiques en les quals es pugui confiar. Git Flow les necessita menys perquè té una fase de QA manual a la branca de versió. GitHub Flow les exigeix per poder afirmar que main és desplegable. Trunk Based Development les exigeix ràpides i perfectes, perquè el tronc rep canvis cada hora.
Aquesta lliçó construeix aquella peça. I tanca dues promeses pendents del curs: la de la lliçó 06-01, on vam veure que els hooks de client no són un control de seguretat perquè qualsevol els esquiva amb --no-verify, i vam deixar anunciada la família de hooks que s'executen al servidor; i la de la 07-04, on va aparèixer un protected branch hook declined sense explicar del tot què era.
També convé delimitar l'abast des del principi. Aquí parlem d'integració contínua: comprovar automàticament que allò que s'integra funciona. No d'entrega ni de desplegament continus —com aquell codi arriba a producció, amb quins entorns, quina estratègia de promoció i com es reverteix—, que són el contingut de la lliçó 10-05: Git a DevOps. La frontera és just on el codi es declara apte per integrar.
Contingut
- Què és la integració contínua i què no és
- Com s'enganxa a Git: els disparadors
- Quin commit es comprova realment en una pull request
- Una canonada d'exemple, explicada línia a línia
- Comprovacions d'estat i branques protegides
- Els hooks de servidor:
pre-receive,updateipost-receive - Client, servidor i CI: qui comprova què
- Mantenir la CI ràpida
- Merge queue: integrar sense curses
- Què queda fora: el desplegament
- Què és la integració contínua i què no és
Comencem pel malentès, perquè és gairebé universal.
«Nosaltres ja fem integració contínua: tenim un servidor que executa les proves.»
Això no és integració contínua. És automatització de proves, que està molt bé i és un requisit, però no és el mateix.
La definició original, de Martin Fowler i del moviment de la programació extrema, és aquesta:
Integració contínua és la pràctica per la qual els membres d'un equip integren la seva feina amb freqüència, almenys diàriament, i cada integració es verifica mitjançant una compilació i una bateria de proves automàtiques per detectar els errors d'integració com abans millor.
La paraula que carrega amb tot el significat és integren. Un equip on cada persona treballa tres setmanes a la seva branca, amb un servidor de CI que prova aquelles branques aïllades religiosament, no està fent integració contínua. Està provant de manera contínua tres versions divergents del projecte que encara no s'han trobat. El dia que es trobin, apareixeran tots els problemes de cop: és exactament el que la pràctica pretenia evitar.
Per això aquesta lliçó ve després de la 07-05 i no abans. La política de ramificació és la part difícil; el servidor de comprovacions és la part fàcil.
| És integració contínua | No ho és |
|---|---|
| Tot l'equip integra a la línia principal diàriament | Tenir un servidor de CI |
| Cada integració es verifica automàticament | Executar les proves abans d'una publicació mensual |
| La línia principal està sempre sana | Que les proves passin a la branca de cadascú |
| Una fallada s'arregla immediatament, amb prioritat | Acumular un tauler de comprovacions en vermell |
| La resposta arriba en minuts | Un cicle de comprovació de dues hores |
I les quatre regles pràctiques que la sostenen:
- Integrar sovint, almenys una vegada al dia per persona (lliçó 07-05).
- Cada integració dispara la verificació, sense excepcions ni exclusions manuals.
- Arreglar la línia principal és la màxima prioritat. Si
mainestà en vermell, es para tot. Si no s'arregla en minuts, es reverteix (lliçó 05-06). - La resposta ha de ser ràpida, o el bucle deixa de tancar-se (apartat 8).
- Com s'enganxa a Git: els disparadors
El sistema de CI no endevina quan ha de treballar: reacciona a esdeveniments de Git. Aquells esdeveniments són els disparadors (triggers), i són la superfície de contacte entre les dues coses.
Els tres fonamentals:
Disparador per push
El més bàsic. Cada vegada que algú actualitza una referència al servidor, es llancen les comprovacions sobre aquell commit.
I les variants habituals, que en un projecte real es combinen:
on:
push:
# Nomes a la linia principal i a les branques de treball
branches:
- main
- 'funcionalitat/**'
- 'correccio/**'
# Ignorar canvis que no poden trencar res
paths-ignore:
- '**.md'
- 'docs/**'El paths-ignore mereix un comentari: filtrar per rutes estalvia temps de màquina, però fes-lo servir amb compte. Si el filtre exclou un fitxer que sí que afecta el resultat (una configuració, un fitxer de dades), tindràs integracions sense comprovar i no ho sabràs. I si aquella comprovació és obligatòria a la branca protegida, la PR es pot quedar esperant eternament una comprovació que no es llançarà mai. És un problema clàssic i desconcertant.
Disparador per pull request
Es llança en obrir una PR i cada vegada que se li afegeixen commits. És el disparador que sosté la revisió: qui revisa vol veure el resultat abans d'aprovar.
Els tipus d'esdeveniment importen:
| Esdeveniment | Quan passa |
|---|---|
opened |
S'obre la PR |
synchronize |
Arriben commits nous a la branca: el cas més freqüent |
reopened |
Es reobre una PR tancada |
ready_for_review |
Deixa de ser esborrany (lliçó 07-01) |
Disparador per etiqueta
Les etiquetes de la lliçó 05-05 són el disparador natural de tot el que té a veure amb publicar una versió:
Recorda de la 05-05 que les etiquetes no viatgen soles: git push origin main no les envia. Si la teva publicació depèn d'una etiqueta, l'ordre és git push origin main --follow-tags o git push origin v2.4.0. És una causa molt comuna d'«he etiquetat i no ha passat res».
Altres disparadors
| Disparador | Ús típic |
|---|---|
| Programat (cron) | Bateria completa nocturna, auditoria de dependències |
| Manual | Rellançar una comprovació, executar alguna cosa sota demanda |
| Per una altra canonada | Encadenar fases |
| Webhook extern | Reaccionar a esdeveniments d'altres sistemes |
Un patró molt estès i molt recomanable: bateria ràpida a cada enviament i a cada PR, bateria completa a la nit. Ho veurem a l'apartat 8.
- Quin commit es comprova realment en una pull request
Aquest apartat explica una de les sorpreses més freqüents de tot el mòdul.
L'Ana obre una PR des de funcionalitat/filtre-per-etiqueta cap a main. La CI s'executa i surt en verd. L'Ana integra… i main es posa en vermell. Com pot ser, si acabava de passar?
I de vegades passa el contrari: l'Ana no ha tocat res, però en tornar de dinar la CI de la seva PR ha passat de verd a vermell sense que ella hagi fet cap commit.
L'explicació és la mateixa en tots dos casos, i és aquesta:
En una pull request, la CI normalment no comprova la teva branca. Comprova el resultat de fusionar la teva branca amb la branca de destinació.
És a dir: no es prova D2 (la punta de la teva branca), es prova un commit de fusió efímer entre D2 i la punta actual de main.
gitGraph commit id: "C1" commit id: "C3 (base de la branca)" branch funcionalitat/filtre checkout funcionalitat/filtre commit id: "D1" commit id: "D2 (la teva punta)" checkout main commit id: "C4" commit id: "C5 (punta actual)" merge funcionalitat/filtre id: "M (el que prova la CI)"
A GitHub, aquell commit és exactament la referència refs/pull/<n>/merge que vam veure a la lliçó 07-01: el servidor la calcula i la manté actualitzada cada vegada que canvia qualsevol dels dos costats.
# Endur-te exactament el que la CI esta provant
git fetch origin pull/42/merge:el-que-prova-la-ci
git switch el-que-prova-la-ciAquella ordre és el millor diagnòstic quan la CI falla i tu no reprodueixes la fallada en local: probablement estàs executant pull/42/head mentre la CI executa pull/42/merge.
Per què es fa així, i és el correcte: el que importa no és si la teva branca funciona aïllada, sinó si funcionarà un cop integrada. Comprovar la fusió detecta els conflictes semàntics dels quals vam parlar a la lliçó 07-05: la Carla va reanomenar una funció a main, tu vas afegir una crida amb el nom antic a la teva branca, no hi ha conflicte textual, i el resultat està trencat. Provar només la teva branca no ho veuria mai.
Les tres conseqüències pràctiques, que expliquen els dos misteris del principi:
- El resultat de la teva PR pot canviar sense que tu facis res, perquè
mainha avançat. No és una fallada del sistema: és informació valuosa que acabes de rebre de franc. - Verd a la PR no garanteix verd a
mainen integrar simainha avançat entre l'última comprovació i la fusió. És la cursa que resol la merge queue de l'apartat 9. - Si hi ha conflictes, el commit de fusió no es pot calcular i la CI no s'executa gens ni mica. Primer es resolen els conflictes (lliçó 03-05), després torna a haver-hi senyal.
I un matís important per a la lliçó 07-01: a GitLab això s'anomena merged results pipelines i és configurable; algunes plataformes proven per defecte només la punta de la branca. Esbrina què fa la teva, perquè canvia completament com interpretes un vermell.
Restricció de seguretat, reprenent la 07-01. Quan la PR ve d'un fork, el codi l'ha escrit algú de fora. Si aquella execució tingués accés a les credencials del projecte, qualsevol les podria robar obrint una PR que imprimís les variables d'entorn. Per això les plataformes executen les PRs de forks en mode restringit, sense secrets i amb permisos de només lectura, i sovint exigeixen que un membre de l'equip autoritzi cada execució. És incòmode i és imprescindible.
- Una canonada d'exemple, explicada línia a línia
Construirem la canonada de gestor-tasques. El fitxer fa servir la sintaxi i els noms d'esdeveniments més habituals, però els conceptes són idèntics a qualsevol plataforma: el que canvia són les paraules, no les idees.
# .ci/canonada.yml — Integracio continua de gestor-tasques
name: Integracio continua
# 1. DISPARADORS: quan s'executa aixo
on:
push:
branches: [main]
pull_request:
branches: [main]
# 2. CONCURRENCIA: cancellar execucions obsoletes de la mateixa branca
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
# 3. PRIMERA TASCA: comprovacions rapides
comprovacions-rapides:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
# 3.1 Portar el codi
- name: Obtenir el codi
uses: actions/checkout@v4
with:
fetch-depth: 0 # historial complet: cal per al pas 3.5
# 3.2 Preparar l'entorn
- name: Preparar Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm' # cachejar ~/.npm entre execucions
# 3.3 Installar dependencies de manera reproduible
- name: Installar dependencies
run: npm ci
# 3.4 Analisi estatica
- name: Executar el linter
run: npm run lint
# 3.5 Comprovar el format dels missatges de confirmacio
- name: Validar els missatges de confirmacio
if: github.event_name == 'pull_request'
run: |
BASE="${{ github.event.pull_request.base.sha }}"
git log --format=%s "$BASE..HEAD" | while read -r assumpte; do
if ! echo "$assumpte" | grep -qE '^(GT-[0-9]+|Fusiona) '; then
echo "Missatge sense referencia de tiquet: $assumpte"
exit 1
fi
done
# 4. SEGONA TASCA: proves en diversos sistemes, en parallel
proves:
runs-on: ${{ matrix.so }}
timeout-minutes: 20
strategy:
fail-fast: false # que un sistema no cancelli els altres
matrix:
so: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- run: npm ci
- name: Executar la bateria de proves
run: npm test -- --coverage
# 4.1 Desar resultats encara que les proves fallin
- name: Publicar l'informe de cobertura
if: always()
uses: actions/upload-artifact@v4
with:
name: cobertura-${{ matrix.so }}
path: cobertura/
# 5. TERCERA TASCA: comprovar els submoduls
submoduls:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive # portar components-ui
- name: Comprovar que el submodul apunta a un commit publicat
run: |
cd components-ui
git fetch origin main
git merge-base --is-ancestor HEAD origin/main \
|| { echo "components-ui apunta a un commit que no es a main"; exit 1; }
# 6. PORTA FINAL: una unica tasca que resumeix el resultat
tot-correcte:
runs-on: ubuntu-latest
needs: [comprovacions-rapides, proves, submoduls]
if: always()
steps:
- name: Verificar que no ha fallat res
run: |
[ "${{ contains(needs.*.result, 'failure') }}" = "false" ] || exit 1
echo "Totes les comprovacions han passat."Ara, per què cada peça és on és.
1. Disparadors. S'executa a cada enviament a main i a cada PR contra main. No a qualsevol branca: la PR ja cobreix les branques de treball, i executar les dues coses duplica la despesa sense aportar senyal.
2. Concurrència. Si l'Ana envia tres commits en cinc minuts, sense això es llancen tres execucions completes i només importa l'última. cancel-in-progress cancel·la les obsoletes. En un equip actiu això sol ja redueix la despesa de màquina a la meitat. Compte: no ho apliquis a main si la teva canonada té efectes (publicar un artefacte, per exemple); cancel·lar a mitges pot deixar coses a mig fer.
3.1 fetch-depth: 0. Per defecte, els sistemes de CI fan un clon superficial (git clone --depth 1, lliçó 02-02): porten només l'últim commit. És molt més ràpid, però trenca tot el que necessiti historial: git log entre dues referències, git describe --tags, git merge-base, git blame. Com que el pas 3.5 recorre els commits de la PR, aquí cal l'historial complet. Aquesta és probablement la fallada de configuració més comuna de tota la CI: una ordre de Git que funciona en local i a la CI diu fatal: bad revision.
3.3 npm ci i no npm install. ci instal·la exactament el que diu el fitxer de bloqueig i falla si no coincideix amb el manifest. install pot resoldre versions noves i fer que l'execució d'avui difereixi de la d'ahir sense que ningú hagi canviat res. Reproduïbilitat: la mateixa entrada ha de donar el mateix resultat.
3.5 Validació dels missatges de commit. Aquí hi ha la connexió amb la lliçó 06-01: el hook commit-msg ja valida el format GT-NNN al portàtil, però se salta amb --no-verify. Aquesta comprovació ho verifica al servidor, on ningú no la pot esquivar. Fixa't en el rang: $BASE..HEAD, és a dir, només els commits que la PR aporta (lliçó 07-02). Les regles concretes del format són el tema de la lliçó 08-01; aquí només muntem el mecanisme que les fa complir.
4. Matriu. L'Ana fa servir Ubuntu, el Bruno macOS i la Carla Windows 11. Comprovar els tres sistemes en paral·lel detecta el clàssic problema de rutes amb \ davant de / o de majúscules als noms de fitxer abans que arribi a main. fail-fast: false és important: sense ell, el primer sistema que falli cancel·la els altres i perds informació útil.
4.1 if: always(). Sense això, l'informe de cobertura no es desa quan les proves fallen, que és justament quan més el necessites per diagnosticar.
5. Submòduls. Reprèn la lliçó 06-05. gestor-tasques fa servir components-ui com a submòdul, i l'error clàssic és confirmar un punter a un commit que només existeix al portàtil de qui el va fer. Qualsevol altra persona clonarà i no podrà inicialitzar el submòdul. git merge-base --is-ancestor comprova que el commit apuntat sigui abastable des de la branca principal del submòdul. És una comprovació de tres línies que estalvia tardes senceres.
6. La porta final. Una única tasca que depèn de totes les altres. La seva utilitat és pràctica: a la branca protegida s'exigeix una sola comprovació obligatòria, tot-correcte, en lloc de mantenir una llista de cinc noms que cal actualitzar cada vegada que s'afegeix una tasca. Amb la matriu és encara més valuós, perquè els noms generats (proves (ubuntu-latest), etc.) canvien en tocar la matriu i les regles de protecció es queden esperant comprovacions que ja no existeixen.
- Comprovacions d'estat i branques protegides
Ja tenim les comprovacions. Ara cal convertir-les en un requisit, i aquesta és la part que respon definitivament la pregunta que va deixar oberta la lliçó 06-01.
La comprovació d'estat
Quan la canonada acaba, publica el seu resultat associat al commit, no a la branca ni a la PR. És una dada del tipus: «el commit a7c2e91 ha superat la comprovació tot-correcte». Això és una comprovació d'estat (status check).
Consultable des de la plataforma mateixa, i visible a la interfície com el tic verd o la creu vermella al costat del commit.
La branca protegida
Per si sola, una comprovació d'estat és informativa. El que la converteix en obligació és la configuració de la branca protegida, que ja vam presentar a la lliçó 07-04:
| Regla | Efecte sobre main |
|---|---|
| Prohibir l'enviament directe | Tot entra per PR |
| Exigir N aprovacions | Res no entra sense revisió |
| Exigir comprovacions en verd | Res no entra amb la CI en vermell |
Exigir la branca actualitzada amb main |
Es prova la combinació real |
| Descartar aprovacions en arribar commits | No s'aprova una versió i se n'integra una altra |
| Prohibir l'enviament forçat | Ningú no reescriu l'historial publicat (05-06) |
| Prohibir l'esborrat de la branca | main no desapareix |
| Exigir signatura dels commits | Només entra codi amb autoria verificada |
Quan algú intenta saltar-se això, el rebuig arriba del servidor:
remote: error: GH006: Protected branch update failed for refs/heads/main. remote: error: Required status check "tot-correcte" is expected. To [email protected]:equip/gestor-tasques.git ! [remote rejected] main -> main (protected branch hook declined) error: failed to push some refs to 'git.exemple.cat:equip/gestor-tasques.git'
Per què això sí que és un control efectiu
Aquí hi ha la resposta que la lliçó 06-01 va deixar pendent, i convé enunciar-la amb precisió:
Un hook de client viu al disc de qui l'executa, a
.git/hooks/, que no es versiona. Qualsevol el pot esborrar, modificar o esquivar amb--no-verify. És una ajuda contra el despistament.Una comprovació d'estat s'executa a la infraestructura del projecte i la decisió d'acceptar l'enviament la pren el servidor.
--no-verifyés una opció del client: li diu al teu Git que no executi els teus hooks. No viatja al protocol de xarxa i el servidor no s'assabenta que existeix. No hi ha res per saltar-se.
La mateixa diferència, en una frase: el hook de client t'avisa; el servidor decideix.
Un git push --force sobre una branca protegida es rebutja igual, i aquí hi ha la garantia tècnica de la regla d'or de la lliçó 05-06: no reescriure historial publicat deixa de ser una recomanació i passa a ser una impossibilitat.
Un avís sobre les excepcions
Gairebé totes les plataformes permeten que certs rols se saltin les proteccions. És temptador deixar aquella porta oberta «per si de cas». Però pensa quan es fa servir a la pràctica: durant un incident en producció, amb pressa, a les onze de la nit, per algú cansat. És a dir, exactament quan més falta fan les comprovacions. Si de debò cal una via d'emergència, que sigui explícita, que deixi registre i que es revisi després.
- Els hooks de servidor:
pre-receive, update i post-receive
pre-receive, update i post-receiveI ara, per fi, la família completa que la lliçó 06-01 va deixar anunciada.
Quan algú fa git push, el servidor rep els objectes i, abans d'actualitzar cap referència, executa els seus propis hooks. Viuen al directori hooks/ del repositori bare (lliçó 04-01), no en un .git/hooks/ de cap portàtil.
| Hook | Quan | Entrada | Efecte del codi de sortida |
|---|---|---|---|
pre-receive |
Una vegada, abans d'acceptar res | Per stdin: <sha-antic> <sha-nou> <ref>, una línia per referència |
Sortida diferent de 0 rebutja el push sencer |
update |
Una vegada per cada referència | Com a arguments: <ref> <sha-antic> <sha-nou> |
Rebutja només aquella referència; les altres poden passar |
post-receive |
Després d'acceptar, amb tot actualitzat | Igual que pre-receive, per stdin |
S'ignora: ja no es pot rebutjar res |
pre-receive: la porta
Exemple real per a gestor-tasques: rebutjar enviaments que introdueixin fitxers de més de cinc megues, que és la causa habitual de repositoris que es tornen inmanejables (i el motiu d'existir de Git LFS, lliçó 10-03).
#!/bin/bash
# hooks/pre-receive al repositori bare del servidor
LIMIT=$((5 * 1024 * 1024))
while read -r antic nou ref; do
# Branca nova: comparar contra l'arbre buit
if [ "$antic" = "0000000000000000000000000000000000000000" ]; then
rang="$nou"
else
rang="$antic..$nou"
fi
# Recorrer els objectes nous que arriben en aquest push
while read -r mode tipus sha mida ruta; do
[ "$tipus" = "blob" ] || continue
if [ "$mida" -gt "$LIMIT" ]; then
echo "REBUTJAT: '$ruta' ocupa $mida bytes (limit: $LIMIT)."
echo "Fes servir Git LFS per a fitxers grans."
exit 1
fi
done < <(git rev-list --objects "$rang" |
git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' |
awk '{print "-", $1, $2, $3, substr($0, index($0,$4))}')
done
exit 0Fixa't en dos detalls. El primer: la comprovació del hash de zeros, que és com Git indica «aquesta referència no existia abans» (una branca nova). El segon: exit 1 en qualsevol punt rebutja el push complet, incloses les altres branques que vinguessin al mateix enviament. Aquella atomicitat és deliberada: o entra tot o no entra res.
Altres usos típics de pre-receive:
- Rebutjar force pushes sobre branques concretes.
- Rebutjar commits el missatge dels quals no segueixi la convenció
GT-NNN. - Rebutjar commits l'autor dels quals no tingui un correu del domini de l'empresa.
- Detectar secrets (claus d'API, credencials) al contingut que arriba. Això és matèria de la lliçó 08-05, però convé saber que el lloc correcte per a aquella barrera és aquí.
update: control per referència
S'executa una vegada per cada referència i pot rebutjar només aquella. És el lloc natural per a regles específiques d'una branca o de les etiquetes:
#!/bin/bash
# hooks/update — nomes l'equip de publicacio pot crear etiquetes de versio
ref="$1"; antic="$2"; nou="$3"
case "$ref" in
refs/tags/v*)
if [ "$antic" != "0000000000000000000000000000000000000000" ]; then
echo "REBUTJAT: les etiquetes de versio no es modifiquen ni es mouen."
exit 1
fi
if ! id -nG "$USER" 2>/dev/null | grep -qw publicacio; then
echo "REBUTJAT: nomes el grup 'publicacio' pot crear etiquetes v*."
exit 1
fi
;;
esac
exit 0Aquest exemple protegeix una cosa important que vam veure a la lliçó 05-05: una etiqueta publicada no es mou. Si algú mou v2.0.0 a un altre commit, qui ja se l'havia baixat té una versió diferent de la de qui se la baixi demà, i això és una font de fallades impossibles de diagnosticar.
post-receive: notificar
S'executa amb tot ja acceptat, així que no pot rebutjar res. El seu paper és avisar la resta del món.
#!/bin/bash
# hooks/post-receive
while read -r antic nou ref; do
branca="${ref#refs/heads/}"
[ "$branca" = "$ref" ] && continue # ignorar etiquetes
autor=$(git log -1 --format='%an' "$nou")
assumpte=$(git log -1 --format='%s' "$nou")
# Avisar el xat de l'equip
curl -sS -X POST "https://xat.exemple.cat/hooks/gestor-tasques" \
-H 'Content-Type: application/json' \
-d "{\"text\": \"$autor ha enviat a $branca: $assumpte\"}" >/dev/null
# I disparar la canonada d'integracio continua
curl -sS -X POST "https://ci.exemple.cat/api/executar" \
-H "Authorization: Bearer $TOKEN_CI" \
-d "{\"branca\": \"$branca\", \"commit\": \"$nou\"}" >/dev/null
doneAquell segon curl és literalment el disparador de l'apartat 2. Quan fas servir una plataforma allotjada, això passa per sota: el post-receive del servidor emet un webhook que el sistema de CI rep. És també el hook que genera el missatge remote: Crea una pull request per a... que vam veure a la lliçó 07-01.
La limitació de sempre
Ja ho vam apuntar a la 06-01 i cal repetir-ho: en una plataforma allotjada no tens accés al directori hooks/. GitHub, GitLab o Bitbucket no et deixen posar un script al seu servidor. El que ofereixen són els seus equivalents gestionats:
| Concepte | Servidor propi | Plataforma allotjada |
|---|---|---|
| Rebutjar un push pel seu contingut | pre-receive |
Push rules (en plans de pagament) o comprovació a la CI |
| Controlar qui actualitza quina referència | update |
Branques protegides, regles d'etiqueta, CODEOWNERS |
| Notificar i disparar | post-receive |
Webhooks, integracions natives |
| Exigir que les proves passin | Difícil: el push és síncron | Comprovacions d'estat obligatòries |
L'última fila és interessant i explica el repartiment real. Un pre-receive s'executa durant el push, amb l'usuari esperant al terminal: no pot llançar una bateria de vint minuts. Per això les comprovacions lentes no viuen en un hook, sinó a la CI, i la seva obligatorietat s'imposa en el moment de fusionar la PR, no en el del push. Els hooks de servidor són per a regles ràpides i estructurals; la CI, per a verificacions lentes i substantives.
- Client, servidor i CI: qui comprova què
La foto completa de les tres barreres, que és el resum pràctic de tot el mòdul 6 i d'aquest:
| Hook de client | Hook de servidor | CI / comprovació d'estat | |
|---|---|---|---|
| On s'executa | Portàtil de cada persona | Servidor Git | Infraestructura de CI |
| Quan | Abans de commit / push | Durant el push | Després del push, en paral·lel |
| Es pot esquivar? | Sí: --no-verify, o esborrant el fitxer |
No | No |
| Es versiona? | No (llevat de core.hooksPath + Husky) |
No, és del servidor | Sí: la canonada és al repositori |
| Velocitat exigida | Segons | Segons | Minuts |
| Bloqueja l'usuari | Sí, mentre s'executa | Sí, mentre s'executa | No: és asíncron |
| Què posar-hi | Formatatge, lint del que s'ha modificat, format del missatge | Mida de fitxers, permisos sobre refs, secrets, force push | Proves, compilació, matriu de sistemes, cobertura, seguretat |
| Paper | Estalviar el viatge al servidor | Regles estructurals innegociables | La verificació real |
L'estratègia sana no és triar-ne una, sinó encadenar les tres, cada vegada més lentes i més completes:
flowchart LR
A["pre-commit<br/>2 segons<br/>format i lint"] --> B["pre-push<br/>30 segons<br/>proves ràpides"]
B --> C["pre-receive<br/>1 segon<br/>regles estructurals"]
C --> D["CI a la PR<br/>8 minuts<br/>bateria completa"]
D --> E["Branca protegida<br/>decideix si s'integra"]
Cada baula detecta el que li toca com abans millor. El hook local t'estalvia un viatge al servidor; el servidor rebutja el que és estructuralment inacceptable; la CI verifica de debò; i la branca protegida converteix el resultat de la CI en la decisió final.
I la regla que resumeix la relació entre les tres: allò obligatori es comprova on l'usuari no mana.
- Mantenir la CI ràpida
Una CI lenta no és un inconvenient menor: destrueix la pràctica que pretén sostenir.
La cadena causal és directa. Si la comprovació triga quaranta minuts, la gent deixa d'esperar el resultat i canvia de tasca. En tornar, cal reconstruir el context. Com que esperar és car, s'acumulen canvis per amortitzar l'espera: PRs més grans, branques més llargues, integracions menys freqüents. És a dir, exactament el contrari de la integració contínua. I pitjor: un vermell triga quaranta minuts a detectar-se, durant els quals altres cinc persones hi han construït a sobre.
Referències pràctiques àmpliament acceptades:
| Durada | Efecte sobre l'equip |
|---|---|
| < 5 min | S'espera el resultat sense canviar de tasca. Ideal |
| 5–10 min | Acceptable. El límit pràctic per a TBD |
| 10–20 min | Es canvia de context. Comença a fer mal |
| 20–60 min | S'acumulen canvis. La integració contínua es degrada |
| > 60 min | La gent ignora la CI. Ha deixat de servir |
Les cinc tècniques que més rendeixen:
- Memòria cau
El més rendible, de bon tros. Instal·lar dependències sol ser el pas més lent i el més repetitiu.
- name: Cachejar les dependencies
uses: actions/cache@v4
with:
path: ~/.npm
# La clau inclou el hash del fitxer de bloqueig:
# si no canvien les dependencies, es reutilitza la memoria cau
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
npm-${{ runner.os }}-La clau de la qüestió és la clau: ha de canviar exactament quan canviï allò que s'ha desat. Un restore-keys com a xarxa de seguretat permet reutilitzar una memòria cau parcial quan l'exacta no existeix.
- Paral·lelisme
Les tasques independents s'executen alhora. Amb la matriu de l'apartat 4, tres sistemes operatius costen el mateix en temps de rellotge que un. També es poden partir les proves en fragments:
Quatre màquines, un quart del temps. Ull: hi ha un cost fix per tasca (arrencar la màquina, portar el codi, instal·lar) que posa un terra. Partir en vint fragments de trenta segons cadascun no accelera res si arrencar costa un minut.
- Executar només allò afectat
Si la PR només toca README.md, no cal la bateria completa. Git dona la informació necessària:
- name: Detectar que ha canviat
id: canvis
run: |
BASE="${{ github.event.pull_request.base.sha }}"
FITXERS=$(git diff --name-only "$BASE...HEAD")
echo "$FITXERS"
if echo "$FITXERS" | grep -qvE '\.(md|txt)$'; then
echo "codi=true" >> "$GITHUB_OUTPUT"
else
echo "codi=false" >> "$GITHUB_OUTPUT"
fi
- name: Executar les proves
if: steps.canvis.outputs.codi == 'true'
run: npm testFixa't en el "$BASE...HEAD" amb tres punts: és el diff des de l'ancestre comú, exactament per la raó que vam explicar a la lliçó 07-02. Amb dos punts apareixerien com a canviats els fitxers que ha tocat main, i la detecció seria incorrecta.
Avís important: si aquesta comprovació és obligatòria a la branca protegida i decideixes saltar-te-la, la PR es quedarà esperant per sempre una comprovació que no arriba mai. El patró correcte és que la tasca s'executi sempre i acabi ràpid quan no hi ha res a fer, no que no s'executi.
- Esglaonar: ràpid sempre, complet a la nit
No tot s'ha d'executar a cada enviament.
| Bateria | Quan | Durada | Contingut |
|---|---|---|---|
| Ràpida | Cada enviament i cada PR | < 10 min | Lint, proves unitàries, compilació |
| Mitjana | En integrar a main |
< 30 min | Proves d'integració, matriu completa |
| Completa | Cada nit | Sense límit | Extrem a extrem, rendiment, seguretat, navegadors |
El compromís: una fallada detectada només per la nocturna triga fins a un dia a aparèixer. És acceptable si la bateria ràpida cobreix allò que es trenca amb freqüència.
- Clon superficial quan es pugui
El fetch-depth: 0 de l'apartat 4 és necessari per als passos que consulten l'historial, però és lent en repositoris grans. A les tasques que no el necessitin, deixa el clon superficial per defecte. I per a casos extrems existeixen els clons parcials (--filter=blob:none), que veurem a la lliçó 10-04.
I una última recomanació que no és tècnica: mesura. Gairebé totes les plataformes mostren la durada per tasca i per pas. Deu minuts mirant aquell desglossament solen revelar que el 70 % del temps se'n va en un pas concret que es pot cachejar. Optimitzar a cegues és llençar el temps.
- Merge queue: integrar sense curses
Recorda la conseqüència 2 de l'apartat 3: verd a la teva PR no garanteix verd a main, perquè entre l'última comprovació i la fusió main pot haver avançat.
Amb tres persones i quatre integracions al dia, la probabilitat és baixa i s'assumeix. Amb trenta persones i cinquanta integracions al dia, passa cada dia, i la solució òbvia —exigir que la branca estigui actualitzada amb main abans d'integrar— produeix un efecte pervers: cada vegada que algú integra, totes les altres PRs queden desactualitzades i cal reactualitzar-les i tornar-les a provar. Es converteix en una cursa on només guanya qui prem el botó més ràpid.
La cua d'integració (merge queue, merge train a GitLab) resol això. En lloc de fusionar en prémer el botó, la PR entra en una cua, i el sistema:
- Pren la primera PR de la cua.
- Construeix un commit especulatiu:
main+ aquella PR. - Executa les comprovacions sobre aquella combinació.
- Si passa, la fusiona a
mainde debò. - Si falla, l'expulsa de la cua i avisa el seu autor, sense bloquejar les altres.
I l'optimització que la fa viable: se'n processen diverses en paral·lel de manera especulativa. Amb tres PRs a la cua es proven simultàniament main+A, main+A+B i main+A+B+C. Si les tres passen, s'integren les tres de cop. Si B falla, es descarta B i es reaprofita el resultat de main+A, reprocessant només el que ve després.
flowchart TD
A["PR A aprovada"] --> Q["Cua d'integració"]
B["PR B aprovada"] --> Q
C["PR C aprovada"] --> Q
Q --> S1["Provar main+A"]
Q --> S2["Provar main+A+B"]
Q --> S3["Provar main+A+B+C"]
S1 --> R["Verd: integrar"]
S2 --> R
S3 --> X["Vermell: expulsar C<br/>i integrar només A i B"]
Avantatges: main mai no es trenca per una cursa, ningú no ha de reactualitzar la seva branca a mà, i el rendiment de l'equip deixa de degradar-se amb la seva mida.
Cost: consumeix bastant més temps de màquina (cada combinació especulativa és una execució completa) i afegeix latència entre aprovar i integrar.
Quan fa falta: quan l'equip integra tantes vegades al dia que les curses són habituals, normalment a partir de deu o quinze persones treballant sobre el mateix repositori. Per sota d'això és complexitat innecessària: amb exigir la branca actualitzada n'hi ha prou.
- Què queda fora: el desplegament
Hem arribat al límit d'aquesta lliçó, i convé marcar-lo bé.
El que hem construït respon a: «és correcte aquest canvi i es pot integrar?». Això és integració contínua.
El que ve després respon a: «com arriba aquest canvi a les mans dels usuaris?». Això és entrega i desplegament continus, i inclou entorns de preproducció, estratègies de promoció, desplegament progressiu, reversió, infraestructura com a codi, observabilitat i tot el que passa després que el commit entri a main.
Aquella meitat és el contingut de la lliçó 10-05: Git a DevOps. Des del punt de vista de Git, l'únic que cal retenir aquí és la frontera:
| Integració contínua (aquesta lliçó) | Entrega i desplegament (10-05) | |
|---|---|---|
| Pregunta | És correcte? Es pot integrar? | Com arriba a l'usuari? |
| Disparadors a Git | push, pull request |
Fusió a main, etiqueta |
| Resultat | Verd o vermell, integrar o no | Versió en funcionament |
| Si falla | No s'integra | Es reverteix el desplegament |
Fixa't en la fila dels disparadors: les etiquetes de la lliçó 05-05 són la frontissa entre les dues meitats. És el que fa que git tag -a v2.4.0 && git push --follow-tags sigui, en molts projectes, l'acte que inicia una publicació.
Errors Habituals i Consells
Error 1: creure que «tenir CI» és fer integració contínua. Si cada persona integra cada tres setmanes, un servidor de proves no arregla res. La pràctica és integrar sovint; el servidor només la verifica.
Error 2: no saber que la CI prova la fusió, no la teva branca. És la causa de l'«en local em funciona» més freqüent a les PRs. Porta't pull/<n>/merge i reprodueix-ho sobre això.
Error 3: oblidar fetch-depth: 0 quan la canonada fa servir l'historial. El clon superficial trenca git log entre referències, git describe, git merge-base i git blame amb errors confusos.
Error 4: npm install en lloc de npm ci. Les execucions deixen de ser reproduïbles i apareixen fallades que ningú no ha causat.
Error 5: exigir com a obligatòries comprovacions els noms de les quals canvien. Amb matrius, els noms es generen. Fes servir una única tasca resum (tot-correcte) com a comprovació obligatòria.
Error 6: filtrar per rutes una comprovació obligatòria. Si no es llança, la PR espera per sempre. La tasca s'ha d'executar sempre i acabar ràpid quan no hi ha res a fer.
Error 7: conviure amb proves inestables. Un vermell aleatori ensenya l'equip a rellançar sense mirar, i amb això mor tot el senyal. Arregla-la, aïlla-la o esborra-la, però no la ignoris.
Error 8: tolerar una CI de quaranta minuts. Degrada el flux sencer cap a PRs grans i branques llargues. És una inversió amb retorn immediat.
Error 9: confiar en els hooks de client per a allò obligatori. --no-verify i ja està. Allò innegociable va al servidor o a una comprovació d'estat.
Error 10: deixar oberta l'excepció de les branques protegides. Es farà servir durant un incident, amb pressa, que és quan és pitjor idea.
Error 11: no enviar les etiquetes i esperar que es dispari la publicació. git push --follow-tags.
Consell 1: concurrency amb cancel-in-progress des del primer dia. Estalvi immediat de temps de màquina, cost zero.
Consell 2: una tasca resum de la qual depenguin totes les altres. Simplifica moltíssim la configuració de la branca protegida.
Consell 3: mesura abans d'optimitzar. El desglossament per passos gairebé sempre revela un únic culpable cachejable.
Consell 4: esglaona les bateries. Ràpida a cada enviament, mitjana en integrar, completa a la nit.
Consell 5: valida a la CI el que el hook commit-msg valida en local. El hook avisa aviat; la CI obliga.
Consell 6: comprova els punters dels submòduls a la CI. Tres línies que eviten clons trencats per a tot l'equip (lliçó 06-05).
Consell 7: desa els artefactes amb if: always(). Els informes d'una fallada són justament els que més necessites.
Consell 8: --filter i clons parcials en repositoris grans. I si el problema són fitxers binaris pesats, la resposta és Git LFS (lliçó 10-03).
Exercicis
Exercici 1: comprovar el que comprova la CI
Sense plataforma, reproduint el mecanisme amb Git pur:
- Crea un repositori amb
app.jsimainen tres commits. - Crea
funcionalitat/filtresdes del segon commit i fes dos commits que reanomenin una funció. - Torna a
maini afegeix un commit que cridi aquella funció amb el nom antic. - Calcula a mà el commit que provaria la CI: crea una branca temporal des de
maini fusiona-hi la branca de la funcionalitat. - Comprova que
git diff main...funcionalitat/filtresno revela el problema, i que el resultat de la fusió sí que el té. - Escriu un script de tres línies que, donades dues branques, produeixi el commit de fusió sense deixar rastre (pista:
git merge-tree).
Exercici 2: un hook de servidor efectiu
- Crea
/tmp/servidor/gestor.gitcom a repositori bare i clona'l a/tmp/ana. - Escriu un
hooks/pre-receiveal bare que rebutgi qualsevol push que contingui un commit l'assumpte del qual no comenci perGT-NNNo perFusiona. - Comprova que un commit amb missatge correcte passa i un d'incorrecte es rebutja, i que en el segon cas cap referència del push no s'actualitza.
- Instal·la a
/tmp/anaun hook de clientcommit-msgamb la mateixa regla, i demostra quegit commit --no-verifyl'esquiva però que elpre-receivecontinua rebutjant el push. - Escriu un
hooks/updateque impedeixi modificar o esborrar qualsevol etiqueta ja existent, i comprova-ho.
Exercici 3: una canonada mínima i la seva optimització
- Escriu un fitxer
ci/canonada.ymlper agestor-tasquesamb: disparadors per enviament amaini per PR, una tasca de lint, una tasca de proves en matriu de tres sistemes i una tasca resum de la qual depenguin les dues. - Afegeix memòria cau de dependències amb una clau basada en el hash del fitxer de bloqueig.
- Afegeix un pas que validi els missatges de commit només de la PR, fent servir el rang correcte.
- Afegeix una detecció de canvis que eviti executar les proves si només s'han tocat fitxers
.md, sense deixar de publicar el resultat de la comprovació. - Explica, per a cada elecció, quin problema evita.
Solucions
Solució 1:
mkdir /tmp/ci-fusio && cd /tmp/ci-fusio
git init -qb main
printf 'function desaTasques(t) { return t; }\n' > app.js
git add . && git commit -q -m "Afegeix desaTasques"
echo 'const tasques = [];' >> app.js
git add . && git commit -q -m "Afegeix l'estat inicial"
BASE=$(git rev-parse HEAD)
echo 'desaTasques(tasques);' >> app.js
git commit -qam "Desa l'estat en arrencar"# 2. La branca reanomena
git switch -qc funcionalitat/filtres "$BASE"
sed -i 's/function desaTasques/function persisteixTasques/' app.js
git commit -qam "Reanomena desaTasques a persisteixTasques"
echo 'function filtra(f) { return tasques.filter(f); }' >> app.js
git commit -qam "Afegeix el filtratge"# 3. main ja tenia la crida antiga (commit del pas 1)
# 4. El commit que provaria la CI
git switch -q main
git switch -qc simulacio-merge-de-la-ci
git merge -q funcionalitat/filtres
cat app.jsfunction persisteixTasques(t) { return t; }
const tasques = [];
desaTasques(tasques);
function filtra(f) { return tasques.filter(f); }Fusiona sense conflicte i el resultat crida desaTasques, que ja no existeix: conflicte semàntic.
El diff mostra el reanomenament i el filtratge, i res més: la crida trencada és a main, no a la branca, així que no apareix. Només provant la fusió es detecta.
# 6. El commit de fusio sense tocar l'arbre de treball
git merge-tree --write-tree main funcionalitat/filtresgit merge-tree (Git 2.38 o superior) retorna l'arbre resultant sense modificar el directori de treball ni crear cap commit. És, essencialment, el que fan les plataformes per calcular pull/<n>/merge.
Solució 2:
mkdir -p /tmp/servidor && git init -q --bare /tmp/servidor/gestor.git
git clone -q /tmp/servidor/gestor.git /tmp/ana
cd /tmp/ana && git switch -qc main
echo "inicial" > app.js && git add . && git commit -q -m "GT-001 Estat inicial"
git push -qu origin main# 2. El hook de servidor
cat > /tmp/servidor/gestor.git/hooks/pre-receive <<'EOF'
#!/bin/bash
BUIT=0000000000000000000000000000000000000000
while read -r antic nou ref; do
[ "$nou" = "$BUIT" ] && continue # esborrat de branca
if [ "$antic" = "$BUIT" ]; then
rang="$nou"
else
rang="$antic..$nou"
fi
while read -r assumpte; do
if ! echo "$assumpte" | grep -qE '^(GT-[0-9]{1,5} |Fusiona )'; then
echo "REBUTJAT: '$assumpte' no segueix la convencio GT-NNN."
exit 1
fi
done < <(git log --format=%s "$rang")
done
exit 0
EOF
chmod +x /tmp/servidor/gestor.git/hooks/pre-receive# 3. Missatge correcte: passa
cd /tmp/ana
echo "ok" >> app.js && git commit -qam "GT-002 Afegeix el filtratge per etiqueta"
git push origin main# Missatge incorrecte: es rebutja
echo "malament" >> app.js && git commit -qam "arranjaments diversos"
git push origin mainremote: REBUTJAT: 'arranjaments diversos' no segueix la convencio GT-NNN. To /tmp/servidor/gestor.git ! [remote rejected] main -> main (pre-receive hook declined) error: failed to push some refs to '/tmp/servidor/gestor.git'
# 4. El hook de client i --no-verify
cat > /tmp/ana/.git/hooks/commit-msg <<'EOF'
#!/bin/bash
grep -qE '^(GT-[0-9]{1,5} |Fusiona )' "$1" || {
echo "El missatge ha de comencar per GT-NNN."; exit 1; }
EOF
chmod +x /tmp/ana/.git/hooks/commit-msg
cd /tmp/ana
git reset -q --hard HEAD~1
echo "malament" >> app.js
git commit -qam "una altra vegada arranjaments" # el hook el bloqueja
git commit -qam "una altra vegada arranjaments" --no-verify # l'esquiva
git push origin main # el servidor NO s'esquiva--no-verify desactiva el hook local perquè el fitxer és al disc de l'Ana. El pre-receive s'executa al servidor i no hi ha cap opció del client que l'abasti: això és el que el converteix en un control efectiu.
# 5. Protegir les etiquetes
git reset -q --hard HEAD~1
cat > /tmp/servidor/gestor.git/hooks/update <<'EOF'
#!/bin/bash
ref="$1"; antic="$2"
BUIT=0000000000000000000000000000000000000000
case "$ref" in
refs/tags/*)
if [ "$antic" != "$BUIT" ]; then
echo "REBUTJAT: les etiquetes no es modifiquen ni es mouen."
exit 1
fi ;;
esac
exit 0
EOF
chmod +x /tmp/servidor/gestor.git/hooks/update
cd /tmp/ana
git tag -a v1.0.0 -m "Versio 1.0.0" && git push -q origin v1.0.0
git tag -f -a v1.0.0 -m "Moguda" && git push --force origin v1.0.0remote: REBUTJAT: les etiquetes no es modifiquen ni es mouen. ! [remote rejected] v1.0.0 -> v1.0.0 (hook declined)
Solució 3:
# ci/canonada.yml
name: Integracio continua
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: '22'
# 2. Memoria cau amb clau basada en el fitxer de bloqueig
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: npm-${{ runner.os }}-
- run: npm ci
- run: npm run lint
# 3. Missatges de commit: nomes els que aporta la PR
- name: Validar els missatges de confirmacio
if: github.event_name == 'pull_request'
run: |
BASE="${{ github.event.pull_request.base.sha }}"
git log --format=%s "$BASE..HEAD" | while read -r a; do
echo "$a" | grep -qE '^(GT-[0-9]+ |Fusiona )' || {
echo "Missatge invalid: $a"; exit 1; }
done
proves:
runs-on: ${{ matrix.so }}
strategy:
fail-fast: false
matrix:
so: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
# 4. Deteccio de canvis: la tasca SEMPRE s'executa,
# pero se salta el pas car si nomes hi ha documentacio.
- name: Detectar canvis de codi
id: canvis
shell: bash
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
BASE="${{ github.event.pull_request.base.sha }}"
FITXERS=$(git diff --name-only "$BASE...HEAD")
else
FITXERS=$(git diff --name-only HEAD~1 HEAD)
fi
if echo "$FITXERS" | grep -qvE '\.(md|txt)$'; then
echo "codi=true" >> "$GITHUB_OUTPUT"
else
echo "codi=false" >> "$GITHUB_OUTPUT"
fi
- uses: actions/setup-node@v4
if: steps.canvis.outputs.codi == 'true'
with:
node-version: '22'
cache: 'npm'
- run: npm ci
if: steps.canvis.outputs.codi == 'true'
- run: npm test
if: steps.canvis.outputs.codi == 'true'
- name: Sense canvis de codi
if: steps.canvis.outputs.codi == 'false'
run: echo "Nomes documentacio: no s'executen les proves."
tot-correcte:
runs-on: ubuntu-latest
needs: [lint, proves]
if: always()
steps:
- run: |
[ "${{ contains(needs.*.result, 'failure') }}" = "false" ] || exit 1
echo "Totes les comprovacions han passat."5. Què evita cada elecció:
| Elecció | Problema que evita |
|---|---|
concurrency + cancel-in-progress |
Malgastar màquines en execucions que ja no importen |
fetch-depth: 0 |
fatal: bad revision en recórrer els commits de la PR |
| Memòria cau amb hash del bloqueig | Reinstal·lar dependències idèntiques a cada execució |
npm ci |
Execucions no reproduïbles per resolució de versions |
fail-fast: false |
Perdre la informació dels altres sistemes en fallar-ne un |
Rang $BASE..HEAD |
Validar commits de main que no són d'aquesta PR |
| Diff amb tres punts a la detecció | Comptar com a canviats fitxers que només va tocar main |
| La tasca s'executa sempre, se salta el pas car | Que una comprovació obligatòria no es llanci i la PR esperi per sempre |
Tasca tot-correcte |
Haver de llistar noms de matriu a la branca protegida |
Conclusió
Amb aquesta lliçó es tanca el mòdul 7. L'essencial:
- La integració contínua no és tenir un servidor de CI: és integrar de debò i sovint, almenys diàriament, amb cada integració verificada automàticament. Sense integració freqüent, un servidor de proves només verifica versions divergents que encara no s'han trobat.
- S'enganxa a Git mitjançant disparadors: per
push, per pull request (inclòssynchronize, quan arriben commits nous) i per etiqueta, que és la frontissa cap a la publicació. I recorda que les etiquetes no viatgen ambgit pusha seques. - En una pull request, la CI comprova el resultat de la fusió, no la teva branca. És la referència
pull/<n>/merge. Per això la teva PR es pot posar en vermell sense que hagis fet res, per això detecta conflictes semàntics, i per això verd a la PR no garanteix verd amain. - Una canonada ben construïda porta l'historial que necessita (
fetch-depth: 0), instal·la de manera reproduïble (npm ci), paral·lelitza en matriu de sistemes, comprova també els punters dels submòduls, i acaba en una tasca resum de la qual depenguin les altres. - Una comprovació d'estat s'associa a un commit; una branca protegida la converteix en requisit. I això és el que la 06-01 va deixar pendent:
--no-verifyés una opció del client, no viatja per la xarxa i el servidor no s'assabenta que existeix. El hook local t'avisa; el servidor decideix. - Els hooks de servidor tanquen el cercle:
pre-receive(una vegada, rebutja el push sencer),update(una vegada per referència, rebutja només aquella) ipost-receive(després, només notifica i dispara). Són efectius perquè s'executen on l'usuari no mana. En plataformes allotjades no tens accés ahooks/, i els seus equivalents són les regles de push, les branques protegides i els webhooks. - El repartiment sa encadena les tres barreres: hook de client per al que és ràpid i local, hook de servidor per al que és estructural i innegociable, CI per a la verificació lenta i real. Allò obligatori es comprova on l'usuari no mana.
- Una CI lenta destrueix el flux: empeny cap a PRs grans, branques llargues i integració tardana. Menys de deu minuts com a objectiu, amb memòria cau, paral·lelisme, execució de només allò afectat i bateries esglaonades.
- La merge queue elimina la cursa entre PRs aprovades provant combinacions especulatives abans de fusionar. Val la pena a partir de deu o quinze persones sobre el mateix repositori.
- I la frontera: aquí acaba la integració. Com aquell codi arriba als usuaris —entorns, promoció, desplegament progressiu, reversió— és la lliçó 10-05.
El mòdul, en una idea
L'equip de gestor-tasques va començar aquest mòdul amb una eina dominada i sense acords. Ara té un procés complet: el Diego, sense permís d'escriptura, proposa canvis des del seu fork mitjançant pull requests; l'Ana els revisa amb git diff main...branca i git range-diff; l'equip ha triat el seu flux de branques segons el producte —Git Flow per a la versió instal·lable, alguna cosa propera a GitHub Flow o TBD per al núvol—; i unes comprovacions automàtiques que ningú no pot esquivar decideixen què entra a la línia principal.
Tenen procés. El que els falta ara són hàbits.
Perquè un procés impecable conviu perfectament amb un historial il·legible. Es poden tenir branques protegides, revisió obligatòria i CI en verd, i tot i així acumular cent commits anomenats «canvis» que ningú no podrà interpretar d'aquí a un any; versionar la carpeta node_modules i un fitxer de configuració amb la contrasenya de la base de dades; patir conflictes absurds perquè un company de Windows desa els finals de línia d'una altra manera; i descobrir un dia que una clau d'API porta vuit mesos a l'historial públic.
Això és el mòdul 8. Com escriure missatges de confirmació que serveixin de documentació (08-01, on per fi desenvoluparem la convenció que aquí només hem validat); com mantenir un historial llegible i quina política d'integració triar (08-02, que tanca la decisió que vam deixar oberta a la 07-04); què no versionar mai (08-03 i 08-04); com no filtrar un secret i què fer si ja ha passat (08-05); i com mantenir el repositori ràpid quan creix (08-06).
Comencem pel més quotidià i el més desatès: la lliçó 08-01: Escrivint Bons Missatges de Confirmació.
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ó
