La lliçó anterior va resoldre una de les tres maneres en què un repositori es torna inmanejable: els fitxers molt grans. En queden dues, i són independents entre si. Un repositori pot tenir moltíssim historial encara que cada fitxer sigui diminut, i pot tenir moltíssims fitxers encara que l'historial sigui curt. Cada problema té el seu símptoma i el seu remei, i aplicar el remei equivocat no arregla res.

Aquesta lliçó tanca dues promeses del curs. La lliçó 06-05 va deixar oberta la comparativa entre submòduls, subtree, paquets i monorepo, ajornant el monorepo fins aquí. La 08-06 va ensenyar a mesurar i a optimitzar un repositori normal que s'ha tornat lent, i va remetre aquí els repositoris veritablement enormes. I el cas 3 de la 10-01 va descriure el monorepo empresarial explicant-ne el perquè i deixant el com per a aquesta lliçó.

Comencem per deixar clara una cosa, perquè és la més important de totes: gairebé res del que hi ha aquí no ho necessita gestor-tasques, ni probablement ho necessites tu. Això és material per al dia en què et toqui un repositori de cinquanta mil fitxers, i perquè sàpigues reconèixer quan ha arribat aquest dia. Aplicar aquestes tècniques a un repositori sa hi afegeix complexitat, raresa i una superfície nova d'errors, a canvi de res.

Contingut

  1. Les tres dimensions del creixement
  2. Mesurar abans d'optimitzar (i què mesurar en cada cas)
  3. Clons parcials: --filter=blob:none i --filter=tree:0
  4. Clon parcial enfront de clon superficial
  5. Sparse checkout i l'índex dispers
  6. Acceleradors del costat del client
  7. Monorepo enfront de multirepo
  8. Què cal perquè un monorepo funcioni
  9. Quan NO cal res d'això
  10. Una recepta per símptoma

  1. Les tres dimensions del creixement

L'error més habitual en enfrontar-se a un repositori lent és tractar-lo com un problema. No ho és. Són tres, es manifesten de manera diferent i s'arreglen amb eines diferents.

Dimensió Què significa Símptomes típics Què NO ho arregla Remei
Molt historial Centenars de milers o milions de commits; molts anys de vida git clone lentíssim; git log --graph lent; git blame lent; git branch --contains etern Esborrar fitxers; LFS commit-graph; clon parcial --filter=tree:0; clon superficial per a casos d'usar i llençar
Molts fitxers Desenes o centenars de milers de fitxers a la còpia de treball git status triga segons o minuts; git checkout lent; l'editor indexa eternament Reduir l'historial; gc sparse-checkout --cone + índex dispers; fsmonitor; untrackedCache
Fitxers molt grans Binaris de desenes o centenars de MB, amb moltes versions Repositori de gigabytes; clon lentíssim encara que hi hagi pocs commits; gc costós sparse-checkout; commit-graph Git LFS (10-03); treure el binari del repositori; clon parcial --filter=blob:none

Les tres són independents. Un repositori pot patir-ne una, dues o les tres alhora, i cal diagnosticar cadascuna per separat.

Un exemple que aclareix la independència: un repositori amb vint anys d'història i seixanta fitxers de text pateix la dimensió 1 i cap més. git clone triga deu minuts i git status és instantani. Aplicar-li sparse-checkout no serviria absolutament de res.

flowchart TD
    P["El meu repositori va lent"] --> Q1{"Quina ordre<br/>és lenta?"}
    Q1 -->|"git status<br/>git checkout"| D2["Dimensió 2:<br/>molts fitxers"]
    Q1 -->|"git log<br/>git blame<br/>git branch --contains"| D1["Dimensió 1:<br/>molt historial"]
    Q1 -->|"git clone<br/>git gc"| Q2{"El repositori<br/>pesa molt?"}
    Q2 -->|"sí, GB"| D3["Dimensió 3:<br/>fitxers grans"]
    Q2 -->|"no, però hi ha<br/>molts commits"| D1
    D1 --> R1["commit-graph<br/>--filter=tree:0"]
    D2 --> R2["sparse-checkout --cone<br/>fsmonitor"]
    D3 --> R3["Git LFS (10-03)<br/>--filter=blob:none"]

  1. Mesurar abans d'optimitzar (i què mesurar en cada cas)

La regla de la lliçó 08-06 continua vigent i aquí importa més: la intuïció sobre què és lent és gairebé sempre equivocada. Abans d'aplicar cap d'aquestes tècniques, mesura.

La foto general

# Mida i composició del repositori (lliçó 08-06)
git count-objects -vH
count: 0
size: 0 bytes
in-pack: 4128394
packs: 3
size-pack: 8.42 GiB
prune-packable: 0
garbage: 0
size-garbage: 0 bytes

Les tres mesures que separen les dimensions

# Dimensió 1: quant historial hi ha?
git rev-list --count --all
git rev-list --count HEAD

# Dimensió 2: quants fitxers hi ha a la còpia de treball?
git ls-files | wc -l

# Dimensió 3: quant ocupen els objectes més grans?
git lfs migrate info --everything --above=1Mb 2>/dev/null || \
git rev-list --objects --all \
  | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
  | awk '$1=="blob" {print $3, $4}' | sort -rn | head -10

Tres xifres que orienten la decisió, amb ordres de magnitud aproximats:

Mesura Sense problema Comença a fer mal Necessita tècniques d'aquesta lliçó
Commits (rev-list --count --all) < 20.000 50.000 – 300.000 > 500.000
Fitxers (ls-files | wc -l) < 10.000 20.000 – 80.000 > 100.000
Mida (size-pack) < 500 MB 1 – 3 GB > 5 GB

Són referències, no llindars exactes: depenen molt del maquinari, del sistema de fitxers i del sistema operatiu. Un repositori de 30.000 fitxers va fi en un portàtil modern amb Linux i pot ser penós a Windows amb un antivirus revisant cada accés.

On se'n va el temps exactament

GIT_TRACE2_PERF_BRIEF=1 GIT_TRACE2_PERF=/dev/stdout git status 2>&1 | head -20
d0 | main    | region_enter | r1  |  0.001 | index:do_read_index
d0 | main    | region_leave | r1  |  1.842 | index:do_read_index
d0 | main    | region_enter | r1  |  1.843 | dir:untracked
d0 | main    | region_leave | r1  |  9.104 | dir:untracked
d0 | main    | region_leave | r1  | 11.203 | status

Lectura del diagnòstic: 1,8 segons llegint l'índex i 9,1 segons recorrent directoris buscant fitxers sense seguiment. És un problema de dimensió 2 pur. Ni el commit-graph ni el clon parcial hi farien res; el que cal és untrackedCache, fsmonitor i, si escau, sparse-checkout.

I gestor-tasques, per tenir-ne la referència:

git rev-list --count --all     # 412
git ls-files | wc -l           # 9
git count-objects -vH          # size-pack: 2.14 MiB

Cap de les tres dimensions. Res d'aquesta lliçó no li aplica.

  1. Clons parcials: --filter=blob:none i --filter=tree:0

El clon parcial és, de llarg, la tècnica més útil i menys coneguda d'aquesta lliçó.

La idea

Un clon normal descarrega tots els objectes assolibles: tots els commits, tots els arbres i tots els blobs de tota la història. Un clon parcial descarrega el graf però omet certs objectes, i els demana al servidor sota demanda quan alguna ordre els necessita.

Això requereix que el servidor ho admeti —ho fan les plataformes principals i les versions modernes de Git— i funciona sobre una connexió persistent anomenada promisor remote: el remot "promet" tenir els objectes que falten.

flowchart LR
    subgraph normal["Clon complet"]
        A1["Tots els commits"]
        A2["Tots els arbres"]
        A3["Tots els blobs<br/>de tota la història"]
    end
    subgraph parcial["Clon amb --filter=blob:none"]
        B1["Tots els commits"]
        B2["Tots els arbres"]
        B3["Només els blobs de<br/>la revisió extreta"]
        B4["La resta: sota demanda"]
    end

--filter=blob:none: sense contingut històric

git clone --filter=blob:none https://git.exemple.cat/projecte-gran.git

Descarrega tots els commits i tots els arbres, però cap blob llevat dels necessaris per extreure la revisió de treball.

Què continua funcionant sense descarregar res:

git log --oneline --graph --all     # el graf és complet
git log --format=%s                 # els missatges són als commits
git branch -a --contains a1b2c3d    # topologia pura
git tag --contains a1b2c3d
git log --name-only                 # els noms són als arbres
git rev-list --count --all

Què provoca una descàrrega sota demanda:

git log -p                # necessita el contingut per al diff
git diff HEAD~50          # necessita blobs antics
git blame app.js          # necessita totes les versions del fitxer
git checkout v1.0.0       # necessita els blobs d'aquesta revisió

Veure-ho en marxa:

git blame app.js
remote: Enumerating objects: 47, done.
remote: Counting objects: 100% (47/47), done.
Receiving objects: 100% (47/47), 182.41 KiB | 2.11 MiB/s, done.
4f8a2e6c (Ana Ferrer 2026-07-31 13:12:04 +0200 1) function calculaPendents(tasques) {
...

Git ha anat a buscar les 47 versions del fitxer que necessitava per al blame, i després ha respost. És més lent la primera vegada i queda a la memòria cau per a les següents.

Els objectes que ja s'han portat s'hi queden:

git rev-list --objects --all --missing=print | grep '^?' | wc -l

Aquest --missing=print llista amb un ? al davant els objectes que falten localment. És la manera de saber quant queda per descarregar.

--filter=tree:0: només commits

git clone --filter=tree:0 https://git.exemple.cat/projecte-gran.git

Encara més agressiu: omet també els arbres. Només es descarreguen els objectes commit.

Amb això continua funcionant git log --oneline, git log --graph, git rev-list i qualsevol consulta purament topològica o de missatges. Però gairebé qualsevol operació sobre fitxers dispara descàrregues, inclòs git log --name-only, perquè els noms viuen als arbres.

És l'opció per a casos molt concrets: analitzar el graf d'un repositori enorme, comptar commits, extreure estadístiques d'autoria, generar un registre de canvis. Per treballar cada dia, --filter=blob:none és gairebé sempre l'elecció correcta.

Altres filtres

# Ometre blobs de més d'1 MB (útil si hi ha binaris solts)
git clone --filter=blob:limit=1m https://git.exemple.cat/projecte.git

# Aplicar el filtre a un repositori ja clonat
git config remote.origin.promisor true
git config remote.origin.partialclonefilter blob:none

blob:limit=1m és un punt intermedi interessant: porta tot el codi (que és petit) i deixa fora els binaris pesants. Sense necessitar LFS.

Comparació en xifres

Per a un repositori hipotètic de gran mida, amb ordres de magnitud aproximats:

Tipus de clon Es descarrega Temps relatiu Historial complet
Complet ~8 GB 100 %
--filter=blob:none ~500 MB ~8 % (graf complet)
--filter=tree:0 ~150 MB ~3 % Sí (només commits)
--depth=1 ~200 MB ~4 % No

La fila que importa és la segona: el 8 % de la descàrrega conservant l'historial complet. Això és el que fa del clon parcial l'opció per defecte raonable per a repositoris grans.

  1. Clon parcial enfront de clon superficial

A la lliçó 02-02 vam veure el clon superficial --depth=1. Convé contrastar-los, perquè resolen coses diferents i el superficial té paranys que el parcial no té.

--depth=N (superficial) --filter=blob:none (parcial)
Què omet Commits anteriors a la profunditat indicada Blobs no necessaris ara
El graf Truncat: els commits antics no existeixen Complet
git log Només els N últims commits Tot l'historial
git blame Només fins al tall Complet (descarregant)
git bisect Inutilitzable Funciona
git describe Falla o dona resultats estranys Funciona
git merge-base amb una branca antiga Falla: no hi ha avantpassat comú Funciona
git log v1.0.0..main Falla: l'etiqueta no existeix Funciona
Fusionar / rebasar sobre base antiga Problemàtic Normal
Recuperar el que falta git fetch --unshallow (descàrrega completa) Automàtic i transparent
Suport del servidor Universal Requereix servidor modern
Ús típic CI que només compila la revisió actual Treball diari en repositoris grans

Per què el parcial sol ser millor

1. No trenca la semàntica de Git. Un clon superficial és un repositori incomplet: hi ha commits que senzillament no existeixen, i qualsevol operació que necessiti assolir-los falla. Un clon parcial és un repositori complet amb contingut diferit: tot existeix, algunes coses es descarreguen en demanar-les.

Aquest és l'argument de fons. Amb --depth, Git et mentirà sobre la història i tu hauràs de recordar que està truncada. Amb --filter, Git et dirà la veritat i descarregarà allò que li falti.

2. La recuperació és transparent. Si un clon superficial necessita alguna cosa antiga, cal executar git fetch --unshallow, que descarrega el repositori sencer de cop. Un clon parcial porta només els objectes concrets que li calen, sense que tu hi intervinguis.

3. Els errors del superficial són confusos. Aquest és el clàssic en una canonada de CI:

git clone --depth=1 https://git.exemple.cat/projecte.git
cd projecte
git describe --tags
fatal: No names found, cannot describe anything.

O pitjor, en una comparació de petició d'extracció:

git diff origin/main...HEAD
fatal: no merge base

Tots dos fallen perquè el punt de divergència és fora de la profunditat descarregada. És el motiu que tantes configuracions de CI portin fetch-depth: 0 (descarregar-ho tot), que resol el problema per la via cara. L'alternativa bona:

- uses: actions/checkout@v4
  with:
    fetch-depth: 0
    filter: blob:none    # historial complet, sense contingut històric

Historial complet per a les comparacions i el describe, sense descarregar gigabytes de blobs antics. És la configuració recomanada per a CI en repositoris grans.

Quan continua sent millor el superficial

  • El servidor no admet clons parcials.
  • Una tasca de CI que només compila la revisió actual i no consulta res de l'historial: --depth=1 és mínim i suficient.
  • Un contenidor de desplegament on només vols els fitxers d'una versió concreta. Encara que per a això, git archive sol ser encara millor:
git archive --format=tar.gz --remote=https://git.exemple.cat/projecte.git v1.4.0 > projecte.tar.gz

Això no crea repositori: descarrega només els fitxers d'aquella etiqueta.

  1. Sparse checkout i l'índex dispers

El clon parcial ataca el que es descarrega. sparse-checkout ataca el que apareix al disc, que és un problema diferent: la dimensió 2.

El problema

Un monorepo amb 300.000 fitxers. L'Ana treballa només a serveis/tasques/, que en són 400. Però la seva còpia de treball té els 300.000, i en conseqüència:

  • git status recorre 300.000 entrades.
  • L'editor indexa 300.000 fitxers.
  • Cada git checkout entre branques comprova 300.000 rutes.
  • La cerca de text travessa codi que no li interessa.

La solució

# Activar el mode dispers, en mode con
git sparse-checkout init --cone

# Declarar quins directoris vull al disc
git sparse-checkout set serveis/tasques biblioteques/components-ui

# Veure què hi ha declarat
git sparse-checkout list

# Afegir-ne més sense reescriure l'anterior
git sparse-checkout add serveis/notificacions

# Tornar a l'estat normal
git sparse-checkout disable

Després d'això, la còpia de treball conté només aquests directoris (més els fitxers de l'arrel), i git status passa de recórrer 300.000 entrades a recórrer'n unes poques milers.

I una manera més directa de clonar amb això ja actiu:

git clone --filter=blob:none --sparse https://git.exemple.cat/monorepo.git
cd monorepo
git sparse-checkout set serveis/tasques

Aquesta combinació —clon parcial + sparse checkout— és el muntatge estàndard per treballar en un monorepo gran: descarregues poc i materialitzes menys.

Per què el mode con és el que escala

sparse-checkout té dos modes, i la diferència és crucial.

Mode original (patrons) Mode con (--cone)
Què accepta Patrons tipus .gitignore, arbitràriament complexos Només rutes de directori
Com es decideix cada fitxer Avaluant la llista de patrons fitxer a fitxer Comprovant el prefix de directori
Cost O(fitxers × patrons) O(directoris), amb cerca per prefix
Permet índex dispers No
Expressivitat Alta Limitada a directoris sencers

El mode con renuncia a l'expressivitat per guanyar una propietat estructural: com que els patrons són sempre directoris sencers, Git pot raonar sobre subarbres complets en lloc de sobre fitxers solts. I això habilita el que és veritablement important.

L'índex dispers

Recorda de la lliçó 01-04 i de la 08-06 que l'índex (.git/index) conté una entrada per fitxer seguit. En un monorepo de 300.000 fitxers, l'índex té 300.000 entrades i ocupa desenes de megabytes. Llegir-lo i escriure'l és la primera causa de lentitud de git status, i passa encara que els fitxers no siguin al disc: l'índex els continua llistant.

L'índex dispers (sparse index) ho resol. En lloc d'una entrada per fitxer, en guarda una de sola per cada directori que és fora del con, apuntant directament al seu objecte arbre.

Índex normal (300.000 entrades):
  serveis/tasques/app.js
  serveis/tasques/index.html
  ...
  serveis/facturacio/main.js            <- fora del con
  serveis/facturacio/model.js           <- fora del con
  ... (280.000 més, totes fora del con)

Índex dispers (~4.000 entrades):
  serveis/tasques/app.js
  serveis/tasques/index.html
  ...
  serveis/facturacio/      (tree 8a1f6c3d)   <- UNA entrada
  serveis/pagaments/       (tree 2e9f4c7b)   <- UNA entrada

S'activa així:

git config index.sparse true
git sparse-checkout init --cone --sparse-index

I es comprova:

git ls-files --sparse | grep '/$' | head
test-tool read-cache --table | wc -l   # si tens les eines de prova

L'efecte sobre git status en un monorepo gran és d'un ordre de magnitud. I el motiu és purament estructural: una entrada d'arbre representa un subarbre sencer, gràcies al fet que el hash d'un arbre resumeix tot el seu contingut (lliçó 01-04). Si el hash de l'arbre de serveis/facturacio/ no ha canviat, Git sap amb certesa matemàtica que res d'aquest subdirectori no ha canviat, sense mirar ni un sol fitxer.

Aquest és un exemple preciós de per què entendre el model de dades importa: l'índex dispers no és un truc, és una conseqüència directa de l'adreçament per contingut.

L'efecte sobre git status i altres ordres

Amb sparse-checkout actiu, git status inclou un avís:

git status
On branch main
You are in a sparse checkout with 3% of tracked files present.

nothing to commit, working tree clean

Aquest avís és important i cal saber llegir-lo. No estàs veient el repositori sencer. Si busques un fitxer i no apareix, potser no és que no existeixi: és que és fora del teu con.

Ordres que es comporten de manera diferent:

# Llista només el que hi ha al con
git ls-files

# Llista TOT el que està seguit, dins i fora del con
git ls-files --sparse

# Un fitxer fora del con continua existint a l'historial
git show HEAD:serveis/facturacio/main.js   # funciona perfectament

# git grep busca només al que està materialitzat per defecte
git grep "calculaTotal"

I la raresa que més desconcerta: si fas git checkout d'una branca que esborra un fitxer fora del teu con, no veuràs res, perquè aquest fitxer mai no va ser al teu disc. Tot és coherent, però requereix tenir el model clar.

  1. Acceleradors del costat del client

Aquests ja van aparèixer a la lliçó 08-06; aquí van amb el matís de l'escala.

commit-graph: accelerar el recorregut del graf

És un fitxer d'índex auxiliar que guarda, precalculada, la topologia de l'historial: per a cada commit, els seus pares, la seva data i el seu número de generació (la distància al commit arrel).

# Generar-lo
git commit-graph write --reachable --changed-paths

# Que es mantingui sol
git config fetch.writeCommitGraph true
git config core.commitGraph true

Sense ell, respondre "és A avantpassat de B?" obliga a llegir objectes commit del disc i a recórrer el graf. Amb ell, el número de generació permet descartar branques senceres del recorregut sense llegir res.

El --changed-paths mereix atenció especial en repositoris enormes: guarda un filtre de Bloom amb les rutes modificades a cada commit. Serveix per accelerar dràsticament:

git log -- serveis/tasques/app.js
git log --follow ruta/al/fitxer

En un monorepo, git log sobre un fitxer concret passa d'examinar milions de commits a descartar-los gairebé tots amb una comprovació de bits. És la diferència entre trenta segons i mig segon.

És l'optimització de dimensió 1 amb millor relació cost/benefici, i no té cap inconvenient: és un fitxer de memòria cau regenerable que no altera res del repositori.

fsmonitor: no recórrer el disc

git config core.fsmonitor true
git config core.untrackedCache true

fsmonitor engega un dimoni que escolta les notificacions del sistema de fitxers i manté la llista del que ha canviat. Així git status no ha de recórrer els directoris: pregunta al dimoni.

untrackedCache guarda a l'índex l'última exploració de cada directori juntament amb la seva marca de temps, per no tornar a explorar els que no s'han tocat.

Efecte conjunt en un repositori de 100.000 fitxers, com a ordre de magnitud: git status pot passar de diversos segons a fraccions de segon. La combinació amb l'índex dispers és multiplicativa, perquè ataquen parts diferents del cost: fsmonitor redueix el que cal mirar al disc, l'índex dispers redueix el que cal llegir de l'índex.

Nota de plataforma: fsmonitor integrat funciona a macOS i Windows de sèrie; a Linux depèn de la versió de Git i pot requerir configuració addicional. Mesura-ho abans i després al teu entorn concret.

git maintenance: que es mantingui sol

git maintenance start

Programa tasques periòdiques al planificador del sistema: actualitzar el commit-graph, empaquetar objectes solts de manera incremental, fer prefetch dels remots i netejar referències.

Enfront del gc automàtic de sempre, té dos avantatges: s'executa quan no estàs treballant en lloc d'interrompre't, i inclou tasques que gc no fa, com el prefetch (que descarrega en segon pla el que altres publiquen, de manera que el teu git fetch sigui instantani).

Configuració recomanada per a un repositori gran:

git maintenance register
git config maintenance.commit-graph.enabled true
git config maintenance.prefetch.enabled true
git config maintenance.incremental-repack.enabled true
git config maintenance.loose-objects.enabled true
git config maintenance.gc.enabled false     # el substitueixen les anteriors
git maintenance start

Ajustos agrupats

Git ofereix dos "paquets" de configuració que activen diverses coses alhora:

# Repositoris amb molts fitxers
git config feature.manyFiles true

# Els ajustos experimentals recomanats de la versió
git config feature.experimental true

feature.manyFiles activa index.version=4 (format d'índex més compacte), core.untrackedCache=true i index.skipHash=true. És la drecera raonable per a la dimensió 2. El que fa exactament pot canviar entre versions de Git, així que convé comprovar-ho amb git help config.

  1. Monorepo enfront de multirepo

Aquí es tanca la comparativa que la lliçó 06-05 va deixar oberta i que el cas 3 de la 10-01 va deixar pendent.

Les dues postures

Multirepo: cada projecte o biblioteca té el seu repositori. La relació entre ells s'estableix amb versions publicades (paquets), amb submòduls o amb subtree.

Monorepo: un únic repositori conté molts projectes, que es relacionen per rutes de directori i es compilen junts.

flowchart TB
    subgraph multi["Multirepo"]
        M1["repo: app-web"] -->|"depèn de v2.1.0"| M4["repo: components-ui"]
        M2["repo: servei-dades"] -->|"depèn de v2.0.3"| M4
        M3["repo: app-mobil"] -->|"depèn de v1.9.0"| M4
    end
    subgraph mono["Monorepo"]
        R["repo únic"] --- A["/app-web"]
        R --- B["/servei-dades"]
        R --- C["/app-mobil"]
        R --- D["/biblioteques/components-ui"]
    end

Fixa't en les versions del diagrama de l'esquerra: tres consumidors fent servir tres versions diferents de la mateixa biblioteca. Això és el normal en multirepo, i és alhora el seu avantatge més gran (autonomia) i el seu problema més gran (deriva).

La comparativa completa

Criteri Monorepo Multirepo
Canvi atòmic entre projectes : un commit canvia la biblioteca i els seus 30 consumidors. Mai no hi ha estat incoherent No: 31 peticions d'extracció coordinades, versions de transició, setmanes
Refactorització global Cerca i substitució + un commit Un projecte en si mateix
Versionatge de dependències internes No existeix: tothom fa servir main Cada consumidor fixa una versió; apareix deriva
Autonomia dels equips Menor: comparteixen main, CI i eines Major: cada equip decideix el seu ritme i la seva publicació
Propietat del codi Necessita CODEOWNERS per directori (10-01) Natural: per repositori
Control d'accés Difícil de granular: qui clona ho veu tot Natural: permisos per repositori
Integració contínua Ha de ser selectiva, calculant el que està afectat Simple: cada repositori, el seu
Mida del repositori Creix sense límit; exigeix les tècniques d'aquesta lliçó Cadascun es manté petit
Eines necessàries Sistema de compilació amb graf de dependències, CI selectiva, escalat de Git Gestor de paquets, registre d'artefactes
Descobriment de codi Excel·lent: tot és cercable Costa saber què existeix i on
Incorporació de gent nova Un clon (gran) i ja ho tens tot Cal esbrinar quins repositoris clonar
Publicació Contínua; no hi ha versió global Versions independents per projecte
git bisect entre projectes Funciona: un sol historial Impossible sense coordinació manual
Historial Enorme i barrejat; cal filtrar per ruta Net i específic per projecte
Cost de començar Alt: no funciona sense invertir en eines Baix: és el que surt per defecte

Quan triar cadascun

El monorepo té sentit quan:

  • Els projectes canvien junts sovint: el símptoma inequívoc és que una petició d'extracció necessita canvis coordinats en diversos repositoris.
  • Hi ha biblioteques internes compartides i la deriva de versions ja fa mal.
  • Tothom pot veure tot el codi.
  • Hi ha capacitat d'invertir en eines i de mantenir-les.
  • Es vol que la refactorització global sigui possible.

El multirepo té sentit quan:

  • Els projectes són realment independents i evolucionen a ritmes diferents.
  • Hi ha requisits d'accés que obliguen a separar.
  • Els equips són autònoms i publiquen pel seu compte.
  • Alguns components es publiquen fora de l'organització.
  • No hi ha capacitat de mantenir eines pròpies.
  • Es prefereix la solució que funciona sola.

La pregunta que decideix

De tots els criteris, un pesa més que els altres:

Amb quina freqüència un canvi necessita tocar diversos repositoris alhora?

Si la resposta és "gairebé mai", multirepo, sense dubtar-ho. Si és "constantment, i és el nostre coll d'ampolla més gran", el monorepo resol exactament aquest problema i mereix la inversió.

I recorda la conclusió de la lliçó 06-05: els submòduls no resolen això. Lliguen una versió concreta d'una biblioteca a un consumidor —donen traçabilitat— però cada actualització continua sent un commit a cada consumidor. Són un multirepo amb traçabilitat, no un monorepo barat.

El camí intermedi

No és una decisió binària ni irreversible. Els patrons intermedis més habituals:

  1. Monorepo per domini. Un repositori per àrea gran (plataforma, producte, dades), no un per servei ni un per a tot. Captura la major part del benefici d'atomicitat sense arribar a escales que exigeixin eines exòtiques. És el punt òptim per a la majoria d'organitzacions mitjanes.

  2. Monorepo per a les biblioteques compartides. S'agrupen només les biblioteques internes, que són les que pateixen la deriva de versions, i els productes continuen separats. És un pas reversible i amb benefici immediat.

  3. Migració gradual. Es pot fusionar un repositori dins d'un altre conservant el seu historial:

cd ~/monorepo
git remote add components-ui https://git.exemple.cat/components-ui.git
git fetch components-ui

# Portar el seu historial reubicat sota un subdirectori
git merge -s ours --no-commit --allow-unrelated-histories components-ui/main
git read-tree --prefix=biblioteques/components-ui/ -u components-ui/main
git commit -m "chore: GT-270 incorpora components-ui com a biblioteques/components-ui

Es conserva l'historial complet del repositori original."

La combinació merge -s ours + read-tree --prefix és el mecanisme clàssic: el merge connecta els dos historials sense portar contingut, i el read-tree col·loca l'arbre de l'altre repositori sota el prefix indicat. El resultat és que git log --follow biblioteques/components-ui/boto.js continua funcionant cap enrere. És també, essencialment, el que fa git subtree add per sota (lliçó 06-05).

  1. Què cal perquè un monorepo funcioni

Si la decisió és monorepo, aquestes peces no són opcionals. Un monorepo sense elles és simplement un repositori lent i caòtic.

  1. Escalat de Git (tot l'anterior)

# Clon estàndard per a l'equip
git clone --filter=blob:none --sparse https://git.exemple.cat/monorepo.git
cd monorepo
git sparse-checkout set serveis/tasques biblioteques/components-ui

# Acceleradors
git config core.fsmonitor true
git config core.untrackedCache true
git config index.sparse true
git config feature.manyFiles true
git maintenance start

Això convé posar-ho en un guió d'incorporació al mateix repositori, perquè ningú no recordarà sis ordres.

  1. Propietat del codi

El fitxer CODEOWNERS que vam veure a la lliçó 10-01. Sense ell, el monorepo perd la propietat clara que el multirepo dona de franc.

  1. Integració contínua selectiva

Ja en vam veure l'esquelet a la 10-01. La versió que importa aquí és la basada en el graf de dependències, no en directoris:

name: CI selectiva del monorepo

on: [pull_request]

jobs:
  afectats:
    runs-on: ubuntu-latest
    outputs:
      projectes: ${{ steps.calc.outputs.llista }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          filter: blob:none

      - id: calc
        run: |
          # Rutes canviades des del punt de divergència
          CANVIS=$(git diff --name-only origin/main...HEAD)

          # El sistema de compilació tradueix rutes -> projectes afectats,
          # incloent-hi els que depenen del que ha canviat
          AFECTATS=$(./eines/afectats.sh $CANVIS)

          echo "llista=$AFECTATS" >> "$GITHUB_OUTPUT"
          echo "Projectes afectats: $AFECTATS"

  provar:
    needs: afectats
    if: needs.afectats.outputs.projectes != ''
    runs-on: ubuntu-latest
    strategy:
      matrix:
        projecte: ${{ fromJson(needs.afectats.outputs.projectes) }}
    steps:
      - uses: actions/checkout@v4
        with:
          sparse-checkout: |
            ${{ matrix.projecte }}
            biblioteques
      - run: ./eines/provar.sh ${{ matrix.projecte }}

Fixa't en el sparse-checkout dins de la tasca de CI: cada execució materialitza només el projecte que provarà. És la mateixa tècnica de l'apartat 5, aplicada a la canonada.

El que fa afectats.sh és el que distingeix una CI selectiva bona d'una d'ingènua: si canvia biblioteques/components-ui, no n'hi ha prou de provar aquesta biblioteca; cal provar tots els projectes que en depenen. Això exigeix un graf de dependències explícit, que el proporciona el sistema de compilació.

  1. Sistema de compilació amb memòria cau

En un monorepo, compilar-ho tot a cada canvi és inviable. Cal un sistema que entengui el graf de dependències i que guardi els resultats a la memòria cau indexats pel hash de les entrades: si res del que entra en un objectiu no ha canviat, es reutilitza el resultat anterior.

És, curiosament, el mateix principi que sosté Git: identificar pel hash del contingut i no recalcular el que no ha canviat.

  1. Convencions i automatització de migracions

Amb molts projectes en un repositori, calen convencions fortes: estructura de directoris homogènia, noms predictibles, i eines per aplicar canvis massius ("reanomenar aquesta funció a 200 llocs") de manera automàtica i revisable.

  1. Quan NO cal res d'això

Aquest apartat és tan important com els anteriors.

gestor-tasques no necessita res d'aquesta lliçó. Ni clons parcials, ni sparse-checkout, ni fsmonitor, ni monorepo. Té nou fitxers, 412 commits i pesa 2 MB. git status respon en mil·lisegons.

I no és un cas excepcional: la immensa majoria dels repositoris del món estan en aquesta situació. Un projecte amb 5.000 fitxers, 30.000 commits i 200 MB va perfectament amb Git de sèrie en qualsevol ordinador modern.

El cost d'optimitzar sense necessitat

Tècnica Cost si no la necessites
Clon parcial Latència inesperada en operacions normals; comportament estrany sense connexió; requereix servidor compatible
sparse-checkout Fitxers que "no existeixen" i confonen tothom; eines que fallen sense explicació; grep que no troba coses que sí que hi són
--depth Historial truncat, bisect inutilitzable, comparacions de petició d'extracció que fallen
Monorepo Repositori gran sense cap dels avantatges, perquè els projectes no canviaven junts
fsmonitor Un dimoni més consumint recursos, sense benefici mesurable

La tècnica més perillosa de la llista és sparse-checkout. Un company nou que hereta una configuració dispersa sense saber-ho pot passar-se hores sense entendre per què un fitxer que veu al web no és al seu disc.

La regla

Si git status respon en menys d'un segon i git clone triga menys d'un minut, no tens cap problema que aquestes tècniques resolguin.

Mesura primer (apartat 2). Aplica el remei de la dimensió que faci mal. Mesura després. I si no fa mal res, no facis res.

  1. Una recepta per símptoma

Taula de consulta ràpida.

Símptoma Dimensió Diagnòstic Remei
git clone triga moltíssim i el repositori pesa GB 3 (i/o 1) git count-objects -vH; buscar blobs grans LFS (10-03); --filter=blob:none; treure binaris
git status triga segons 2 GIT_TRACE2_PERF mostra dir:untracked alt fsmonitor, untrackedCache, feature.manyFiles; si continua, sparse-checkout --cone --sparse-index
git log -- ruta/fitxer triga moltíssim 1 Molts commits commit-graph write --reachable --changed-paths
git blame triga moltíssim 1 Moltes revisions del fitxer commit-graph; blame -w -M no ajuda al rendiment
git checkout entre branques és lent 2 Molts fitxers per actualitzar sparse-checkout; fsmonitor
La CI clona 8 GB cinquanta vegades al dia 1 i 3 Configuració de la canonada filter: blob:none + fetch-depth: 0; memòria cau de repositori; lfs: false (10-03)
git gc triga una hora 3 Objectes grans incompressibles LFS; git maintenance incremental en comptes de gc complet
Un canvi necessita 12 peticions d'extracció coordinades Organitzativa Multirepo amb acoblament fort Considerar monorepo o monorepo per domini
El repositori va bé Cap No facis res

Errors Habituals i Consells

Error 1: optimitzar sense mesurar. L'error central de la lliçó. Aplicar sparse-checkout a un problema d'historial no fa res. Diagnostica la dimensió primer.

Error 2: fer servir --depth=1 a la CI i després necessitar l'historial. Produeix fatal: no merge base i fatal: No names found. Fes servir fetch-depth: 0 amb filter: blob:none.

Error 3: sparse-checkout sense --cone. El mode de patrons és més lent, no permet índex dispers i és molt més fàcil de configurar malament. Fes servir --cone llevat que tinguis una raó molt concreta.

Error 4: oblidar que sparse-checkout està actiu. Documenta la configuració dispersa al README.md i recorda que git status avisa amb "You are in a sparse checkout with N% of tracked files present".

Error 5: creure que el clon parcial funciona sense connexió. Si treballes en un avió amb un clon parcial i executes git log -p sobre història antiga, fallarà: necessita anar al servidor. Abans de desconnectar-te, porta el que hagis de necessitar:

git rev-list --objects --all --missing=print | grep -c '^?'

Error 6: triar monorepo per moda. Sense CI selectiva, sense CODEOWNERS i sense sistema de compilació amb memòria cau, un monorepo és pitjor que el que tenies. La pregunta no és "què fan les empreses grans?", sinó "amb quina freqüència un canvi nostre toca diversos repositoris?".

Error 7: creure que els submòduls són un monorepo barat. No ho són: no donen atomicitat. Cada actualització de la biblioteca continua exigint un commit a cada consumidor (lliçó 06-05).

Consell 1: commit-graph per a tothom. És l'única optimització de la lliçó sense cap inconvenient. Activa-la encara que el teu repositori sigui mitjà:

git config --global fetch.writeCommitGraph true
git config --global core.commitGraph true

Consell 2: git maintenance start als teus repositoris grans. Millor que esperar el gc automàtic, i sense interrompre't.

Consell 3: mesura amb hyperfine o amb un bucle senzill. Abans i després, diverses vegades:

for i in 1 2 3 4 5; do /usr/bin/time -f "%e s" git status >/dev/null; done

Consell 4: posa un guió d'incorporació al repositori. Si el teu monorepo necessita sis ordres de configuració, posa-les a eines/configurar.sh i menciona-ho al README.md.

Consell 5: la CI també pot fer servir sparse checkout. Els fluxos moderns permeten declarar quins directoris materialitzar. En un monorepo, això redueix cada tasca de minuts a segons.

Exercicis

Exercici 1: diagnòstic per dimensions

Per a cada repositori, digues quina dimensió pateix, què NO ho arreglaria i què sí:

A.

$ git rev-list --count --all
1847293
$ git ls-files | wc -l
1204
$ git count-objects -vH
size-pack: 1.84 GiB
$ time git status
real 0m0.089s
$ time git log --oneline -- src/nucli.c
real 0m47.203s

B.

$ git rev-list --count --all
8420
$ git ls-files | wc -l
284917
$ git count-objects -vH
size-pack: 3.21 GiB
$ time git status
real 0m41.887s

C.

$ git rev-list --count --all
1204
$ git ls-files | wc -l
89
$ git count-objects -vH
size-pack: 6.72 GiB
$ time git clone .
real 8m12.443s

Exercici 2: configurar el lloc de treball en un monorepo

L'Ana s'incorpora a un equip amb un monorepo: 340.000 fitxers, 2,1 milions de commits, 12 GB. Ella treballarà només a serveis/tasques/ i farà servir biblioteques/components-ui/.

Escriu la seqüència completa d'ordres, des del clon fins a un lloc operatiu, explicant què ataca cadascuna i a quina dimensió. Inclou també què li diries sobre les rareses amb què es trobarà.

Exercici 3: decidir monorepo o multirepo

Una empresa de 120 persones té 23 repositoris: 8 serveis, 6 aplicacions client, 9 biblioteques internes.

Símptomes que reporten:

  • Canviar la interfície de la biblioteca autenticacio triga sis setmanes a arribar a tots els consumidors.
  • Hi ha quatre versions diferents de components-ui en producció alhora.
  • Ningú no sap quins serveis fan servir quines biblioteques.
  • Un col·laborador nou triga tres dies a clonar i configurar el que necessita.
  • Dos dels serveis els manté una empresa externa que no ha de veure la resta del codi.
  • Les biblioteques grafics i utilitats es publiquen públicament com a codi obert.

Decideix què recomanaries, amb quins passos i en quin ordre. Assenyala explícitament què no ficaries al monorepo i per què.

Solucions

Solució 1

A. Dimensió 1 pura: molt historial.

Indicis: 1,8 milions de commits però només 1.204 fitxers. git status respon en 89 ms (la dimensió 2 no existeix). El repositori pesa 1,84 GB, cosa coherent amb 1,8 milions de commits de codi de text, no amb binaris.

El símptoma decisiu: git log sobre un fitxer triga 47 segons. Git ha de recórrer milions de commits comprovant si cadascun va tocar src/nucli.c.

Què NO ho arreglaria: sparse-checkout (només hi ha 1.204 fitxers i status ja és instantani), LFS (no hi ha binaris), fsmonitor (res per recórrer al disc).

Què sí:

# El remei principal: filtres de Bloom de rutes modificades
git commit-graph write --reachable --changed-paths
git config core.commitGraph true
git config fetch.writeCommitGraph true

# Que es mantingui sol
git maintenance start

# I per a clons nous, sobretot a la CI
git clone --filter=blob:none https://git.exemple.cat/projecte.git

Amb --changed-paths, aquest git log -- src/nucli.c de 47 segons hauria de baixar a menys d'un segon: el filtre de Bloom descarta la immensa majoria dels commits sense obrir-ne els arbres.

B. Dimensió 2 dominant: molts fitxers.

Indicis: 285.000 fitxers, només 8.420 commits (l'historial és curt), i git status triga 42 segons. Els 3,21 GB són consistents amb molts fitxers, no necessàriament amb binaris grans.

Què NO ho arreglaria: commit-graph (amb 8.420 commits, l'historial no és el problema), clon superficial (tampoc).

Què sí, en ordre de menor a major intrusió:

# 1. Primer el que és barat i sense efectes secundaris
git config core.untrackedCache true
git config core.fsmonitor true
git config feature.manyFiles true

# 2. Mesurar de nou
for i in 1 2 3; do /usr/bin/time -f "%e s" git status >/dev/null; done

# 3. Si continua fent mal, materialitzar només el necessari
git sparse-checkout init --cone --sparse-index
git config index.sparse true
git sparse-checkout set la/meva/area/de/treball

L'ordre importa: els passos 1 i 2 no canvien el que veus al disc i poden resoldre-ho. El pas 3 sí que ho canvia i té cost de comprensió per a tot l'equip.

Convé a més comprovar si aquests 3,21 GB amaguen un problema de dimensió 3:

git lfs migrate info --everything --above=5Mb

C. Dimensió 3 pura: fitxers molt grans.

Indicis inequívocs: 89 fitxers, 1.204 commits, 6,72 GB. L'aritmètica no deixa lloc a dubtes: 6,72 GB entre 89 fitxers són uns 75 MB per fitxer de mitjana. Són binaris.

Què NO ho arreglaria: absolutament res de sparse-checkout ni de commit-graph. Ni tan sols un clon superficial ajudaria gaire, perquè els blobs grans de l'última revisió ja pesen.

Què sí:

# 1. Confirmar el diagnòstic
git lfs migrate info --everything --above=1Mb

# 2. Decidir per fitxer (lliçó 10-03):
#    generat? -> .gitignore
#    no cal el seu historial? -> fora del repositori
#    binari de producte amb historial? -> LFS

# 3. Migrar, amb tota la coordinació de la lliçó 10-03
git lfs migrate import --everything --include="*.psd,*.mp4,*.zip"

# 4. Pal·liatiu immediat mentre es decideix la migració
git clone --filter=blob:none https://git.exemple.cat/projecte.git

El pas 4 és un alleujament útil: el clon parcial evita descarregar totes les versions històriques dels binaris, encara que no arregli la mida del repositori del costat del servidor.

Solució 2

# ============================================================
# 1. CLON: parcial + dispers des del principi
#    Dimensions 1 i 3 (el que es descarrega)
# ============================================================
git clone --filter=blob:none --sparse \
  https://git.exemple.cat/monorepo.git
cd monorepo

--filter=blob:none evita descarregar el contingut històric de 2,1 milions de commits: de 12 GB passem a una fracció petita, conservant el graf complet. --sparse fa que el checkout inicial materialitzi només l'arrel, en lloc d'escriure 340.000 fitxers al disc.

# ============================================================
# 2. CON: què es materialitza al disc
#    Dimensió 2 (el que hi ha a la còpia de treball)
# ============================================================
git sparse-checkout init --cone --sparse-index
git config index.sparse true
git sparse-checkout set serveis/tasques biblioteques/components-ui
git sparse-checkout list

--cone permet l'índex dispers; --sparse-index i index.sparse fan que l'índex guardi una entrada per directori fora del con en lloc d'una per fitxer. De ~340.000 entrades a unes poques milers.

# ============================================================
# 3. ACCELERADORS DEL CLIENT
# ============================================================
git config core.fsmonitor true        # dimensió 2: no recórrer el disc
git config core.untrackedCache true   # dimensió 2: memòria cau de l'exploració
git config feature.manyFiles true     # dimensió 2: índex v4, skipHash
git config core.commitGraph true      # dimensió 1: recorregut del graf
git config fetch.writeCommitGraph true

# ============================================================
# 4. MANTENIMENT AUTOMÀTIC
# ============================================================
git maintenance register
git config maintenance.prefetch.enabled true
git config maintenance.commit-graph.enabled true
git config maintenance.incremental-repack.enabled true
git maintenance start

# ============================================================
# 5. VERIFICAR
# ============================================================
git ls-files | wc -l            # només el que hi ha al con
git ls-files --sparse | wc -l   # entrades reals de l'índex
for i in 1 2 3; do /usr/bin/time -f "%e s" git status >/dev/null; done

Què li diria a l'Ana sobre les rareses:

  1. "No veuràs la majoria dels fitxers." git status t'avisarà: "You are in a sparse checkout with 2% of tracked files present". No estan esborrats: no estan materialitzats. git show HEAD:serveis/facturacio/main.js funciona perfectament.

  2. "Si necessites una altra àrea, afegeix-la." git sparse-checkout add serveis/notificacions. No cal clonar de nou.

  3. "Algunes operacions aniran a la xarxa." git log -p sobre història antiga, git blame d'un fitxer amb moltes versions, o git checkout d'una etiqueta vella descarregaran blobs. La primera vegada és lenta; després queda a la memòria cau.

  4. "Abans d'anar-te'n sense connexió, porta el que hagis de necessitar." Un clon parcial necessita el servidor per al que no té.

  5. "El teu editor i les teves cerques només veuen el teu con." Això és bo per al rendiment, però si busques una funció i no apareix, potser és fora de la teva àrea. git grep --no-index o buscar al web del repositori són la sortida.

  6. "Si alguna cosa es comporta de manera estranya, mira primer git sparse-checkout list." És el primer sospitós de gairebé qualsevol raresa.

I li passaria un guió eines/configurar-lloc.sh amb tot l'anterior, perquè ningú no recorda catorze ordres.

Solució 3

Recomanació: monorepo parcial, en tres fases, començant per les biblioteques.

El símptoma dominant és inequívoc: sis setmanes per propagar un canvi de biblioteca i quatre versions de components-ui en producció. Això és exactament el problema que el monorepo elimina d'arrel, i confirma que hi ha acoblament fort entre les biblioteques i els seus consumidors.

Però hi ha dues restriccions que impedeixen un monorepo total, i cal respectar-les.

Què NO entra al monorepo, i per què:

Què Per què es queda fora
Els 2 serveis de l'empresa externa Control d'accés. Un monorepo dona accés a tot a qui el clona. És la restricció més dura i no es negocia: són requisits contractuals o de confidencialitat, no preferències tècniques.
Les biblioteques grafics i utilitats Es publiquen públicament. Un projecte de codi obert necessita el seu propi repositori: historial net i específic, incidències i peticions d'extracció pròpies, i cap risc d'exposar codi intern. Entren al monorepo com a dependència externa versionada, igual que qualsevol paquet de tercers.

Fase 1: monorepo de biblioteques internes (7 biblioteques).

És el pas amb millor relació benefici/risc, i és reversible.

# Crear el repositori i absorbir cada biblioteca conservant-ne l'historial
mkdir plataforma && cd plataforma && git init

for BIB in autenticacio components-ui dades registre cues config validacio; do
  git remote add "$BIB" "https://git.exemple.cat/$BIB.git"
  git fetch "$BIB"
  git merge -s ours --no-commit --allow-unrelated-histories "$BIB/main"
  git read-tree --prefix="biblioteques/$BIB/" -u "$BIB/main"
  git commit -m "chore: incorpora $BIB conservant-ne l'historial"
done

Benefici immediat: un canvi que toca autenticacio i dades alhora passa a ser un commit, i git bisect funciona entre biblioteques.

Aquestes biblioteques continuen publicant-se com a paquets versionats per als consumidors, de manera que res no canvia per als 12 productes. Per això és reversible.

Fase 2: absorbir els consumidors més acoblats.

Mesurar primer quins són:

# Quines peticions d'extracció dels últims sis mesos van tocar diversos
# repositoris el mateix dia i pel mateix tiquet?
for REPO in servei-a servei-b app-web app-mobil; do
  echo "== $REPO"
  git -C "$REPO" log --since=6.months --format='%ad %s' --date=short \
    | grep -oE '^[0-9-]+ .*(TICKET-[0-9]+)' | head
done

Els que apareguin repetidament al costat de canvis de biblioteca són els candidats. S'absorbeixen aquests, no tots.

Fase 3: consolidar la resta, si l'experiència ha estat bona.

Inversió obligatòria abans de la fase 2 (sense això, el monorepo empitjora les coses):

  1. CODEOWNERS per directori — recupera la propietat que el multirepo donava de franc.
  2. CI selectiva amb graf de dependències — sense ella, cada petició d'extracció executa la suite sencera.
  3. Sistema de compilació amb memòria cau pel hash de les entrades — o els temps de compilació es disparen.
  4. Guió de configuració del lloc — amb --filter=blob:none --sparse, com a la solució 2.

Què resol cada símptoma reportat:

Símptoma Com queda
Sis setmanes per propagar un canvi de biblioteca Resolt: un commit atòmic actualitza biblioteca i consumidors
Quatre versions de components-ui en producció Resolt: no hi ha versions internes, tothom fa servir main
Ningú no sap què fa servir què Resolt: el graf de dependències és explícit i cercable
Tres dies de configuració inicial Resolt: un clon parcial i dispers, amb guió
L'empresa externa no ha de veure la resta Respectat: els seus 2 serveis queden fora
grafics i utilitats són públiques Respectat: queden fora, com a dependències versionades

Resultat final: 1 monorepo (7 biblioteques + els productes interns que s'absorbeixin), 2 repositoris privats per a l'empresa externa, 2 repositoris públics. De 23 repositoris a 5, sense violar cap restricció.

El que NO faria: ficar-ho tot de cop. La fase 1 és reversible i dona benefici en setmanes; una migració total de 23 repositoris sense eines és una manera habitual de tenir un repositori de 40 GB, una CI de dues hores i un equip furiós.

Conclusió

Escalar Git comença per deixar de tractar-lo com un sol problema.

  • Són tres dimensions independents: molt historial (git log i blame lents), molts fitxers (git status lent) i fitxers molt grans (repositori de gigabytes). Cadascuna té el seu símptoma, la seva mesura i el seu remei, i aplicar el remei equivocat no arregla res. Es diagnostiquen amb git rev-list --count --all, git ls-files | wc -l i git count-objects -vH, i s'afina amb GIT_TRACE2_PERF.
  • Els clons parcials són la tècnica més útil i menys coneguda: --filter=blob:none descarrega el graf complet i omet el contingut històric, demanant-lo sota demanda; --filter=tree:0 omet també els arbres. Conserven log, bisect, describe i merge-base.
  • Enfront del clon superficial --depth, el parcial és gairebé sempre millor: --depth produeix un repositori incomplet on falten commits i les operacions fallen (fatal: no merge base), mentre que el parcial produeix un repositori complet amb contingut diferit. Per a la CI: fetch-depth: 0 + filter: blob:none.
  • sparse-checkout --cone limita el que es materialitza al disc, i l'índex dispers guarda una entrada per directori fora del con en lloc d'una per fitxer. El mode con renuncia a expressivitat per poder raonar sobre subarbres complets, cosa que només és possible perquè el hash d'un arbre resumeix tot el seu contingut (lliçó 01-04).
  • Els acceleradors del clientcommit-graph --changed-paths, fsmonitor, untrackedCache, feature.manyFiles i git maintenance— ataquen parts diferents del cost i es combinen bé. El commit-graph és l'únic sense inconvenients: activa'l sempre.
  • Monorepo enfront de multirepo es decideix amb una sola pregunta: amb quina freqüència un canvi necessita tocar diversos repositoris alhora? El monorepo compra atomicitat i refactorització global; ho paga amb eines pròpies, CODEOWNERS, CI selectiva i escalat de Git. I hi ha camins intermedis: monorepo per domini, monorepo només de biblioteques, migració gradual amb merge -s ours + read-tree --prefix.
  • I el més important: la majoria dels repositoris no necessiten res d'això. gestor-tasques té nou fitxers, 412 commits i 2 MB. Aplicar-li sparse-checkout seria afegir confusió a canvi de res.

La regla que resumeix la lliçó, i que és la mateixa de la 08-06 portada a una altra escala:

Mesura, diagnostica la dimensió, aplica el remei d'aquesta dimensió, i torna a mesurar. Si git status respon en menys d'un segon, no tens cap problema que aquestes tècniques resolguin.

El que ve

Ja sabem operar Git en repositoris de qualsevol mida. Queda l'última peça del món real, i és la que converteix Git en alguna cosa més que una eina de desenvolupadors.

A la lliçó 07-06 vam traçar una frontera: allà parlàvem d'integració contínua —comprovar automàticament que el que s'integra funciona— i vam deixar per a més endavant com aquest codi arriba als usuaris. També a la 07-04 vam ajornar la mecànica dels entorns i la promoció, i a la 05-05, en etiquetar v1.4.0, vam dir que les etiquetes anotades serien la peça que dispara un desplegament.

Tot això convergeix aquí. En moltes organitzacions, Git ja no el fa servir només una persona en un terminal: el fan servir cent canonades que clonen, etiqueten, construeixen i publiquen sense intervenció humana. El repositori deixa de contenir només codi i passa a contenir la definició de l'entorn, la configuració i la infraestructura. I un git push a la branca correcta deixa de ser "he desat la meva feina" per convertir-se en "això és a producció d'aquí a quatre minuts".

La lliçó 10-05: Git a DevOps tanca aquesta promesa: Git com a font única de veritat, els tres esglaons de CI, lliurament i desplegament, què dispara cadascun, per què l'artefacte s'ha d'identificar pel hash del commit, què és GitOps, on viuen els secrets en un món automatitzat, i per què revertir en producció gairebé mai no és un git revert.

Dominant Git: De Principiant a Avançat

Mòdul 1: Introducció a Git

Mòdul 2: Operacions Bàsiques de Git

Mòdul 3: Branques i Fusió

Mòdul 4: Treballant amb Repositoris Remots

Mòdul 5: Operacions Avançades de Git

Mòdul 6: Eines i Tècniques de Git

Mòdul 7: Estratègies de Col·laboració i Flux de Treball

Mòdul 8: Bones Pràctiques i Consells de Git

Mòdul 9: Resolució de Problemes i Depuració

Mòdul 10: Git al Món Real

© Copyright 2026. Tots els drets reservats