Aquesta lliçó salda tres deutes del curs. A la lliçó 06-05 vam dir que si el problema no és codi compartit sinó binaris pesants, la resposta no són els submòduls sinó Git LFS. A la 08-04 va aparèixer a .gitattributes una línia críptica —*.psd filter=lfs diff=lfs merge=lfs -text— i vam prometre explicar-la. I a la 08-06, en analitzar per què els binaris grans enverinen un repositori, vam dir que la solució correcta era aquesta.
Aquí la tens, i arriba en bon moment: l'equip de gestor-tasques incorporarà recursos gràfics, un vídeo de demostració i els fitxers de disseny originals de components-ui. La Carla ha afegit un mockup-panel.psd de 84 MB i ja l'ha modificat tres vegades. El repositori, que pesava 4 MB, ara pesa 250 MB, i el Bruno triga quatre minuts a clonar el que abans trigava dos segons.
Entendrem per què passa això —no per què «els binaris són dolents», sinó què fa exactament Git amb ells—, com ho resol LFS, i sobretot quin preu té, perquè LFS no és gratis ni indolor i cal adoptar-lo sabent on fa mal.
Contingut
- Per què Git pateix amb els binaris grans
- L'aritmètica del problema a
gestor-tasques - Què és Git LFS: la idea del punter
- Instal·lació i primer seguiment
- Com es reflecteix a
.gitattributes - El punter per dins
- Clonar, portar i no portar objectes
- Ordres d'inspecció i manteniment
- Migrar un repositori que ja té binaris a l'historial
- Limitacions i avisos que cal conèixer abans
- Alternatives a LFS
- Quan fer servir LFS i quan no
- Per què Git pateix amb els binaris grans
Cal tornar al model de dades de la lliçó 01-04, perquè el problema no és una limitació arbitrària: és una conseqüència directa de com funciona Git.
Cada versió és un blob complet
Git no desa diferències. Desa el contingut complet de cada versió de cada fitxer, com un objecte blob identificat pel hash SHA-1 del seu contingut. Quan modifiques un fitxer i confirmes, no es desa «el que ha canviat»: es desa un blob nou amb el fitxer sencer.
flowchart TD
C1["commit 1"] --> T1["tree"] --> B1["blob<br/>mockup.psd v1<br/>84 MB"]
C2["commit 2"] --> T2["tree"] --> B2["blob<br/>mockup.psd v2<br/>84 MB"]
C3["commit 3"] --> T3["tree"] --> B3["blob<br/>mockup.psd v3<br/>84 MB"]
B1 -.->|"tots continuen<br/>existint"| B2
B2 -.-> B3
Amb fitxers de text això no importa, i aquí hi ha la clau: en empaquetar el repositori, Git calcula deltes entre objectes semblants. Un fitxer app.js de 40 KB amb cent versions es comprimeix a una fracció minúscula, perquè cada versió es desa com «l'anterior més aquests canvis».
Per què la delta no funciona amb binaris
Tres raons que s'acumulen:
| Raó | Què implica |
|---|---|
| Ja estan comprimits | Un .psd, un .png, un .mp4 o un .zip porten compressió pròpia. Tornar a comprimir no guanya gairebé res, i zlib gasta temps per no res. |
| Un canvi petit altera tot el fitxer | Canviar un píxel en un format comprimit reescriu el flux complet. Els dos fitxers no s'assemblen byte a byte encara que s'assemblin visualment. |
| Git compara bytes, no semàntica | Git no sap que dos .psd són la mateixa imatge amb una capa diferent. Només veu dues seqüències de bytes sense res en comú. |
El resultat: la delta entre dues versions d'un binari és tan gran com el mateix binari. Es pot comprovar (lliçó 08-06):
9c4e2f1a8b3d blob 88080384 88052193 12 7a3f9d2e1c5b blob 88080384 88048871 88052205 2e8b1f4a7c9d blob 88080384 88051002 176101076 4f1a9c3e7b2d blob 41273 8104 264152078
Les columnes són: hash, tipus, mida real, mida dins del paquet i desplaçament. Els tres blobs del .psd ocupen gairebé 84 MB cadascun dins del paquet: la compressió no ha aconseguit res. Compara-ho amb el blob de text de l'última fila, que passa de 41 KB a 8 KB.
I l'historial és per sempre
Això és el que converteix un problema molest en un de greu. Com vam veure a la lliçó 08-05 amb els secrets i a la 08-06 amb la mida: esborrar el fitxer no elimina els objectes. Un git rm mockup-panel.psd crea un commit nou on el fitxer no hi és, però els tres blobs de 84 MB continuen a la base de dades, abastables des dels commits antics, i tothom qui cloni se'ls baixarà.
Treure'ls de debò exigeix reescriure l'historial amb git filter-repo, amb totes les conseqüències que vam estudiar: canvien tots els hashos des del punt reescrit, i tothom ha de recuperar el repositori.
El cost se'l menja tothom
I aquest és l'argument decisiu. El repositori de Git és distribuït: cada clon té l'historial complet. Que la Carla afegeixi 250 MB d'historial significa que:
- El Bruno, l'Ana i el Diego es baixen 250 MB en clonar.
- Cada treball de CI que cloni es baixa 250 MB (i n'hi ha molts al dia).
- Cada
worktreei cada clon nou ocupa aquest espai al disc. git gctriga més,git clonetriga més, i el servidor serveix més trànsit.
Un fitxer gran no és un problema de qui l'afegeix. És un impost sobre tot l'equip, cobrat per sempre.
- L'aritmètica del problema a
gestor-tasques
gestor-tasquesPosem números concrets al cas de la Carla, per tenir la intuïció de l'ordre de magnitud:
| Element | Mida | Versions | Ocupació a l'historial |
|---|---|---|---|
app.js, index.html, estils.css, README.md |
~60 KB | ~400 commits | ~2 MB (amb deltes) |
mockup-panel.psd |
84 MB | 3 | ~250 MB |
demo.mp4 (previst) |
120 MB | 2 | ~240 MB |
Recursos gràfics de components-ui |
~15 MB | ~20 | ~300 MB |
El codi —el que de debò és el projecte— ocupa el 0,25 % del repositori. La resta són binaris que gairebé ningú no necessita a la seva còpia de treball la majoria dels dies.
Aquí hi ha l'observació que justifica LFS: la majoria de la gent, la majoria del temps, només necessita l'última versió d'aquests fitxers, o cap. L'Ana, que treballa en la lògica d'app.js, no necessita les tres versions del .psd; ni tan sols necessita l'última. El servidor de CI que executa les proves, tampoc.
Git, per disseny, ho dona tot a tothom. LFS trenca aquest «tot a tothom» només per als fitxers que decideixis.
- Què és Git LFS: la idea del punter
Git LFS (Large File Storage) és una extensió de Git —no forma part de Git, s'instal·la a part— construïda sobre un mecanisme que Git sí que porta de sèrie: els filtres de .gitattributes que vam veure a la lliçó 08-04.
La idea completa cap en una frase:
En lloc del fitxer gran, al repositori es versiona un petit fitxer de text que diu on és el contingut. El contingut real viu en un magatzem a part i es baixa només quan cal.
flowchart TD
subgraph disc["La teva còpia de treball"]
A["mockup-panel.psd<br/>84 MB, fitxer real"]
end
subgraph repo["Repositori Git"]
B["mockup-panel.psd<br/>130 bytes: punter de text<br/>oid sha256:4d7a...<br/>size 88080384"]
end
subgraph magatzem["Magatzem LFS del servidor"]
C["4d7a...<br/>contingut real, 84 MB"]
end
A -->|"git add: filtre clean"| B
B -->|"git checkout: filtre smudge"| A
A -->|"git push: puja el contingut"| C
C -->|"git checkout / lfs pull"| A
Dos mecanismes independents treballant alhora:
El filtre clean s'executa quan el contingut entra a Git (git add). Rep el fitxer de 84 MB, en calcula el hash SHA-256, desa el contingut a la memòria cau local d'LFS i retorna a Git un text de tres línies. Git versiona aquest text, que és el que acaba al blob.
El filtre smudge s'executa quan el contingut surt de Git (git checkout). Rep el punter, busca el contingut pel seu hash (a la memòria cau local o baixant-lo del servidor) i escriu el fitxer real al disc.
El resultat és que treballes exactament igual que sempre. Obres el .psd, l'edites, git add, git commit, git push. Els filtres són invisibles. El que canvia és el que es desa a l'historial: 130 bytes de text en lloc de 84 MB de binari.
Per què això ho arregla
| Efecte | Motiu |
|---|---|
| L'historial de Git es manté petit | Cada versió són 130 bytes, i a sobre són text que comprimeix bé |
| El clon és ràpid | Es baixen els punters; el contingut, només el de la revisió que extreus |
| No es baixen les versions antigues | El servidor LFS només envia els objectes que demanis |
| Es pot evitar baixar fins i tot els actuals | La CI que no necessita el .psd es pot saltar el smudge |
| L'emmagatzematge es pot gestionar a part | Amb les seves pròpies polítiques de retenció i de cost |
- Instal·lació i primer seguiment
Instal·lar
LFS és un binari a part, i cada persona de l'equip l'ha d'instal·lar:
# Ana, Ubuntu
sudo apt install git-lfs
# Bruno, macOS
brew install git-lfs
# Carla, Windows 11: ve inclòs a l'instal·lador oficial de Git,
# o bé:
winget install GitHub.GitLFSI després, una vegada per màquina i per usuari:
Aquesta ordre fa dues coses concretes, i convé saber-ho perquè explica molts comportaments:
1. Registra els filtres a la configuració global:
filter.lfs.clean git-lfs clean -- %f filter.lfs.smudge git-lfs smudge -- %f filter.lfs.process git-lfs filter-process filter.lfs.required true
És exactament el mecanisme de filtres de la lliçó 08-04. filter.lfs.required true significa que si LFS no està instal·lat, Git falla en lloc de deixar punters escampats pel disc. És el que vols.
2. Instal·la hooks al repositori (pre-push, post-checkout, post-commit, post-merge) que s'encarreguen de pujar i baixar els objectes en el moment adequat. Són hooks (lliçó 06-01), amb les mateixes propietats de sempre: viuen a .git/hooks/ i no es distribueixen. Per això qui cloni el repositori necessita haver executat git lfs install a la seva màquina.
Declarar quins fitxers se segueixen
cd ~/gestor-tasques
git lfs track "*.psd"
git lfs track "*.mp4"
git lfs track "*.sketch"
git lfs track "recursos/video/**"Les cometes són importants: sense elles, l'intèrpret d'ordres expandiria *.psd als fitxers existents i es registrarien rutes concretes en lloc del patró.
Veure el que hi ha declarat:
Listing tracked patterns
*.psd (.gitattributes)
*.mp4 (.gitattributes)
*.sketch (.gitattributes)
recursos/video/** (.gitattributes)I deixar de seguir un patró:
Compte: untrack treu la regla per al futur. Els fitxers ja convertits en punters a l'historial continuen sent punters; per revertir-ho cal reescriure l'historial (apartat 9).
- Com es reflecteix a
.gitattributes
.gitattributesAquí es tanca la promesa de la lliçó 08-04. git lfs track no desa res en un fitxer propi: escriu a .gitattributes, el mateix fitxer que ja coneixes.
# ============================================================
# Finals de línia (resol GT-190)
# ============================================================
* text=auto
*.sh text eol=lf
*.bat text eol=crlf
# ============================================================
# Git LFS (GT-251)
# ============================================================
*.psd filter=lfs diff=lfs merge=lfs -text
*.mp4 filter=lfs diff=lfs merge=lfs -text
*.sketch filter=lfs diff=lfs merge=lfs -text
recursos/video/** filter=lfs diff=lfs merge=lfs -textDesglossem els quatre atributs, que és exactament el que va quedar pendent:
| Atribut | Què fa |
|---|---|
filter=lfs |
Aplica els filtres clean i smudge registrats com a filter.lfs.*. És el nucli del mecanisme: converteix contingut en punter en entrar, i punter en contingut en sortir. |
diff=lfs |
Fa servir el controlador de diferències d'LFS en lloc del genèric. Així git diff mostra alguna cosa útil sobre el fitxer (mida, identificador de l'objecte) en comptes del punter en cru. |
merge=lfs |
Fa servir el controlador de fusió d'LFS. Com veurem, no fusiona res: obliga a triar un costat. |
-text |
Desactiva l'atribut text: diu a Git que no normalitzi els finals de línia (lliçó 08-04). Sense això, un * text=auto com el de dalt podria corrompre el contingut abans que el filtre el vegi. |
El -text és el més fàcil de passar per alt i el que causa el mal més difícil de diagnosticar. Per això git lfs track el posa sempre.
Conseqüència pràctica molt important: .gitattributes és un fitxer versionat. Quan la Carla el confirma i l'envia, tot l'equip hereta la configuració d'LFS automàticament. No cal dir a ningú quins fitxers són d'LFS: és al repositori. Això és el que fa que LFS sigui utilitzable en equip, i és un bon exemple del principi de la lliçó anterior: la configuració compartida viu al repositori.
El que no viatja al repositori és la instal·lació d'LFS ni els seus hooks. Per això el CONTRIBUTING.md (lliçó 10-01) ho ha de dir a la primera línia.
L'ordre importa
Un detall subtil: .gitattributes s'aplica en el moment del git add. Si afegeixes el .psd abans de declarar el patró, es versiona com a binari normal i ja és a l'historial. Per això la seqüència correcta és sempre:
# 1. Declarar el patró
git lfs track "*.psd"
# 2. Confirmar .gitattributes ABANS d'afegir els binaris
git add .gitattributes
git commit -m "chore: GT-251 configura Git LFS per als fitxers de disseny"
# 3. Ara sí, afegir els fitxers
git add recursos/mockup-panel.psd
git commit -m "feat: GT-251 afegeix el mockup del panell de tasques"Confirmar .gitattributes en un commit separat i anterior no és cosmètica: garanteix que qui faci checkout de qualsevol punt de l'historial tingui la configuració correcta abans de trobar-se els fitxers.
- El punter per dins
Mirem exactament què es desa. Al disc, el fitxer és normal:
Però el que Git té versionat és una altra cosa:
version https://git-lfs.github.com/spec/v1 oid sha256:4d7a9f2e1c8b3a6d5e0f7c2b9a4d1e8f3c6b0a5d2e9f4c7b1a8d3e6f0c5b2a9d size 88080384
Cent trenta bytes. Això és tot el que hi ha a l'historial de Git per cada versió del .psd. Tres línies:
| Línia | Significat |
|---|---|
version |
Versió del format de punter d'LFS |
oid sha256:... |
L'identificador del contingut: el hash SHA-256 del fitxer real |
size |
La mida en bytes del contingut real |
Fixa't en l'elegància conceptual: LFS aplica exactament el mateix principi que Git —adreçament per contingut (lliçó 01-04)— però un nivell més avall. El contingut s'identifica pel seu hash, és immutable, i el mateix contingut no es desa mai dues vegades. LFS fa servir SHA-256 mentre que Git (encara) fa servir SHA-1, una cosa sobre la qual tornarem a la lliçó 10-06.
Per veure-ho des de més avall, amb lampisteria (lliçó 09-06):
Cent trenta bytes a la base de dades d'objectes. Davant de 88.080.384. Aquesta és tota la lliçó.
Què mostra git diff
Gràcies a l'atribut diff=lfs:
diff --git a/recursos/mockup-panel.psd b/recursos/mockup-panel.psd index 3f8a1c9..7b2e4d6 100644 --- a/recursos/mockup-panel.psd +++ b/recursos/mockup-panel.psd @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4d7a9f2e1c8b3a6d5e0f7c2b9a4d1e8f3c6b0a5d2e9f4c7b1a8d3e6f0c5b2a9d -size 88080384 +oid sha256:9e2f7a4c1b8d3e6f0a5c2b9d4e7f1a8c3b6d0e5f2a9c4b7d1e8f3a6c0b5d2e9f +size 88080384
Un diff útil dins del que és possible: diu que el contingut ha canviat i que la mida és la mateixa. No diu què ha canviat a la imatge, perquè això Git no ho pot saber.
I aquí convé recordar el truc de la lliçó 08-04: es pot definir un controlador de diferències textualitzador per a certs binaris, de manera que git diff mostri metadades llegibles:
No és una diferència visual, però saber que la imatge ha passat de 1200×800 a 2400×1600 ja és informació. Els dos mecanismes són compatibles: LFS gestiona l'emmagatzematge, textconv millora la visualització.
- Clonar, portar i no portar objectes
Aquí hi ha la major part del valor pràctic d'LFS, i també les seves raresses.
Un clon normal
Cloning into 'gestor-tasques'... remote: Enumerating objects: 1284, done. Receiving objects: 100% (1284/1284), 2.14 MiB | 8.42 MiB/s, done. Resolving deltas: 100% (612/612), done. Filtering content: 100% (2/2), 196 MiB | 12.3 MiB/s, done.
Dues fases clarament separades:
Receiving objects: l'historial de Git. 2,14 MB. Inclou els quatre-cents commits, tot el codi i tots els punters de totes les versions.Filtering content: LFS baixant el contingut real dels fitxers de la revisió extreta. 196 MB, dos fitxers.
La diferència amb l'escenari sense LFS: sense ell haurien estat 250 MB a la primera fase (totes les versions de tot) més el temps de resoldre deltes inútils. Amb LFS són 2 MB d'historial més només el contingut actual.
Clonar sense baixar el contingut
Per a qui no necessita els binaris —la CI que executa les proves, algú que només tocarà app.js— hi ha una variable d'entorn:
Això desactiva el filtre smudge, així que els fitxers LFS queden al disc com a punters de text:
version https://git-lfs.github.com/spec/v1 oid sha256:4d7a9f2e1c8b3a6d5e0f7c2b9a4d1e8f3c6b0a5d2e9f4c7b1a8d3e6f0c5b2a9d size 88080384
És una situació perfectament vàlida i molt útil, però cal saber-la reconèixer, perquè és l'origen de la incidència de suport més habitual amb LFS: algú obre el .psd amb el seu editor d'imatges i rep un error incomprensible, sense entendre que el que té és un fitxer de text de 130 bytes.
Per deixar-ho permanent en una màquina o repositori concret:
git config --global filter.lfs.smudge "git-lfs smudge --skip -- %f"
git config --global filter.lfs.process "git-lfs filter-process --skip"O de manera més neta i moderna, per clonar sense contingut:
git clone --no-checkout https://git.exemple.cat/gestor-tasques.git
cd gestor-tasques
git lfs install --local --skip-smudge
git checkout mainPortar el contingut quan el necessitis
# Baixar el contingut dels fitxers LFS de la revisió actual
git lfs pull
# Només el que coincideixi amb un patró
git lfs pull --include="recursos/mockup-*.psd"
# Excloure vídeos, que són el més pesant
git lfs pull --exclude="*.mp4"I la distinció entre fetch i pull, que és la mateixa que a Git (lliçó 04-04):
# fetch: baixa a la memòria cau local d'LFS, sense escriure a la còpia de treball
git lfs fetch
# fetch --all: TOTES les versions de TOTS els fitxers LFS de l'historial
git lfs fetch --all
# fetch d'una referència concreta
git lfs fetch origin v1.3.0
# checkout: escriu a la còpia de treball el que ja és a la memòria cau
git lfs checkout
# pull = fetch + checkout
git lfs pullCompte amb git lfs fetch --all: baixa el contingut de totes les versions de tots els fitxers LFS de tota la història. És exactament el que evitaves en adoptar LFS. Només té sentit en dos casos: fer una còpia de seguretat completa, o preparar una migració a un altre servidor.
Filtres permanents de baixada
Si a la teva màquina no vols mai els vídeos, es configura una vegada:
git config lfs.fetchexclude "*.mp4,recursos/video/**"
git config lfs.fetchinclude "recursos/mockup-*.psd"I per a la CI, on això importa molt més perquè s'executa desenes de vegades al dia:
name: Proves
on: [push, pull_request]
jobs:
provar:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
lfs: false # no baixar contingut LFS
- name: Instal·lar dependències
run: npm ci
- name: Proves
run: npm test
# Només el treball que genera la documentació necessita els recursos
documentacio:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
lfs: true
- name: Generar la documentació amb imatges
run: npm run docsAquest lfs: false és l'optimització amb millor retorn de tot l'apartat. Si tens cinquanta execucions de CI al dia i cadascuna s'estalvia 196 MB de baixada, són gairebé 10 GB diaris de trànsit i uns quants minuts d'espera acumulats.
- Ordres d'inspecció i manteniment
git lfs ls-files: què és a LFS
4d7a9f2e1c * recursos/mockup-panel.psd 9e2f7a4c1b * recursos/mockup-llistat.psd 1c8b3a6d5e - recursos/video/demo.mp4
La columna del mig és la més útil:
| Marca | Significat |
|---|---|
* |
El contingut real és al disc (smudge aplicat) |
- |
Només hi ha un punter; el contingut no està baixat |
Variants útils:
# Amb mida
git lfs ls-files --size
# En una revisió concreta
git lfs ls-files v1.3.0
# Només els noms, per encadenar amb altres ordres
git lfs ls-files --name-only
# Tots els objectes LFS de tota la història
git lfs ls-files --allgit lfs status: el git status d'LFS
On branch GT-251-recursos-disseny
Objects to be pushed to origin/GT-251-recursos-disseny:
recursos/mockup-panel.psd (84 MB)
Objects to be committed:
recursos/mockup-llistat.psd (LFS: 9e2f7a4)
Objects not staged for commit:
recursos/video/demo.mp4 (File: 1c8b3a6)Aquesta última línia revela un problema real: (File: ...) en lloc de (LFS: ...) significa que aquest fitxer no està sent gestionat per LFS, probablement perquè es va afegir abans de declarar el patró. És el primer lloc on mirar quan el repositori creix sense explicació.
git lfs env: diagnòstic
Mostra la versió, l'URL del punt final d'LFS, la ruta de la memòria cau local i la configuració de filtres. És l'equivalent per a LFS del git config --list --show-origin de la lliçó 09-06, i la primera ordre que cal executar quan alguna cosa no funciona.
git lfs prune: alliberar espai local
La memòria cau local d'LFS (.git/lfs/objects/) va acumulant contingut de revisions antigues:
# Veure què esborraria, sense esborrar res
git lfs prune --dry-run --verbose
# Esborrar els objectes locals que ja no calen
git lfs pruneprune és conservador per disseny: només esborra objectes que estiguin confirmats i enviats al servidor, i conserva els de les revisions recents. Es controla amb:
# Quants dies de referències recents es conserven (per defecte 7)
git config lfs.pruneoffsetdays 14
# Verificar contra el servidor que l'objecte hi és abans d'esborrar-lo en local
git config lfs.pruneverifyremotealways trueAquest pruneverifyremotealways mereix activar-se: comprova contra el servidor que cada objecte està fora de perill abans d'eliminar-lo del teu disc. Costa una mica de temps i evita l'únic escenari en què prune podria fer mal.
git lfs migrate info: auditar abans d'actuar
migrate: Fetching remote refs: ..., done. migrate: Sorting commits: ..., done. migrate: Examining commits: 100% (412/412), done. *.psd 250 MB 3/3 files *.mp4 240 MB 2/2 files *.zip 38 MB 4/4 files
Aquesta ordre no modifica res: analitza l'historial i diu quines extensions ocupen quant. És el punt de partida obligatori de qualsevol migració, i també una bona revisió periòdica.
- Migrar un repositori que ja té binaris a l'historial
Aquest és el cas de gestor-tasques: els .psd ja hi són a dins. Adoptar LFS ara fa que els fitxers futurs siguin punters, però els 250 MB de l'historial continuen allà i tothom se'ls continua baixant.
Per treure'ls cal reescriure l'historial. LFS porta la seva pròpia eina.
L'advertència, primer
git lfs migrate importreescriu l'historial. Canvien els hashos de tots els commits afectats i de tots els seus descendents. És la regla d'or de la lliçó 05-01 en la seva versió més contundent, i exigeix exactament la mateixa coordinació que la neteja de secrets de la lliçó 08-05.
Conseqüències concretes:
- Totes les branques i etiquetes que continguin commits reescrits apuntaran a objectes nous.
- Qualsevol persona amb un clon tindrà una divergència completa (lliçó 09-03).
- Les propostes obertes quedaran basades en commits que ja no són a
main. - Les signatures de commits (08-05) s'invaliden, perquè l'objecte signat ja no és el mateix.
El procediment complet
Pas 0: acordar-ho amb l'equip i triar el moment.
No és un pas tècnic i és el més important. Cal una finestra en què ningú no tingui feina sense enviar. L'Ana avisa per escrit, es fixa l'hora i es demana a tothom que enviï i tanqui les seves propostes.
# Cada persona comprova que no té res pendent
git status
git log --branches --not --remotes --oneline # commits locals sense enviar
git stash listAquest git log --branches --not --remotes és la comprovació exacta: llista els commits que existeixen en alguna branca local i en cap remot. Si surt buit, no hi ha res a perdre.
Pas 1: còpia de seguretat.
# Un clon mirall complet, desat a part
git clone --mirror https://git.exemple.cat/gestor-tasques.git ~/copies/gestor-tasques-abans.git
# I un paquet autocontingut, per si de cas (lliçó 09-05)
git bundle create ~/copies/gestor-tasques-$(date +%F).bundle --allPas 2: auditar.
Serveix per decidir quins patrons migrar. Migra per extensió, no per fitxer concret: és més robust i captura també els fitxers que es van reanomenar pel camí.
Pas 3: assajar en una còpia.
git clone https://git.exemple.cat/gestor-tasques.git /tmp/assaig-migracio
cd /tmp/assaig-migracio
git lfs migrate import --everything --include="*.psd,*.mp4,*.zip"migrate: Fetching remote refs: ..., done. migrate: Sorting commits: ..., done. migrate: Rewriting commits: 100% (412/412), done. main 8a1f6c3d4e5b -> 2c9d4f7a1b8e v1.0.0 3f2a1b9c7d4e -> 6b1e8a3c5d0f v1.1.0 7d4e9c2f1a8b -> 9f3c6b0a5d2e migrate: Updating refs: ..., done. migrate: checkout: ..., done.
Fixa't que les etiquetes també es reescriuen. v1.0.0 ja no apunta al mateix commit. És la conseqüència de la immutabilitat del model de dades: canviar qualsevol cosa a l'historial canvia tot el que en penja.
Pas 4: verificar el resultat de l'assaig.
# Quant s'ha reduït?
git count-objects -vH
# Què hi ha ara a LFS?
git lfs ls-files --all | head -20
# El contingut de l'última revisió és correcte?
git lfs pull
ls -lh recursos/
# El codi continua sent idèntic? Comparar l'arbre amb l'original
git rev-parse HEAD^{tree}
git -C ~/gestor-tasques rev-parse HEAD^{tree}Aquesta última comprovació és la més tranquil·litzadora i gairebé ningú no la fa. El hash de l'arbre de HEAD resumeix tot el contingut de la revisió. Si coincideix amb el del repositori original... no coincidirà, precisament perquè els .psd ara són punters. Però sí que ha de coincidir el dels subdirectoris que no contenen fitxers migrats:
Si aquests dos hashos són iguals, la migració no ha tocat el codi. És una verificació criptogràfica, no una impressió.
Pas 5: executar-ho de debò i publicar.
cd ~/gestor-tasques
git lfs migrate import --everything --include="*.psd,*.mp4,*.zip"
# Pujar el contingut LFS al magatzem del servidor ABANS que les referències
git lfs push --all origin
# I ara sí, les referències reescrites
git push --force-with-lease --all origin
git push --force-with-lease --tags originL'ordre importa: primer el contingut LFS, després les referències. Si publiques les referències abans, hi haurà un interval en què algú pot clonar un repositori els punters del qual apunten a objectes que encara no existeixen al magatzem.
--force-with-lease en lloc de --force, sempre (lliçó 09-03). I si el servidor té main com a branca protegida (07-06), caldrà desprotegir-la temporalment i tornar-la a protegir just després. Apunta-ho al guió, perquè s'oblida.
Pas 6: que tothom recuperi.
La instrucció per a l'equip, idèntica a la de la neteja de secrets:
# El més simple, segur i recomanable: clonar de nou
cd ~
mv gestor-tasques gestor-tasques-antic
git clone https://git.exemple.cat/gestor-tasques.git
cd gestor-tasques
git lfs install
git lfs pullI per a qui tingués feina sense enviar en una branca local:
# Al clon nou, portar la branca del clon antic
git remote add antic ~/gestor-tasques-antic
git fetch antic GT-260-la-meva-branca
# Reubicar només els meus commits sobre el nou main (lliçó 09-03)
git switch -c GT-260-la-meva-branca
git rebase --onto main antic/main antic/GT-260-la-meva-brancaAquest rebase --onto és exactament la tècnica de la lliçó 09-03: agafa els commits que hi ha entre el main antic i la branca antiga, i els replanta sobre el main nou.
Pas 7: netejar el servidor.
Reescriure l'historial no allibera espai al servidor per si sol: els objectes antics continuen allà mentre alguna referència els abasti. Cal demanar al servidor que executi la seva recollida d'escombraries, i en moltes plataformes gestionades això és una petició al suport. Esbrina-ho abans de començar, perquè si no, el repositori continuarà pesant el mateix del costat del servidor i la migració semblarà haver fallat.
L'operació inversa
Converteix punters de tornada en fitxers normals. Útil si decideixes abandonar LFS. Requereix tenir baixats els objectes, i reescriu l'historial exactament igual.
- Limitacions i avisos que cal conèixer abans
LFS resol un problema real, però n'introdueix d'altres. Aquests són els que cal conèixer abans d'adoptar-lo, no després.
- Cal un servidor LFS
LFS no funciona sense servidor. El magatzem és un servei a part que parla un protocol propi sobre HTTPS. La majoria de plataformes l'ofereixen; algunes instal·lacions autoallotjades requereixen configurar-lo explícitament.
Conseqüència immediata: un repositori amb LFS deixa de ser autocontingut. Si clones un repositori amb LFS i el servidor d'LFS no està disponible, tens els punters però no el contingut. Això xoca de front amb la naturalesa distribuïda de Git (lliçó 01-01), i és l'argument filosòfic més seriós contra LFS: has introduït un punt centralitzat en un sistema distribuït.
Per a una còpia de seguretat realment completa calen dues coses:
git bundle create copia.bundle --all # l'historial de Git
git lfs fetch --all # tot el contingut LFS
# i copiar també .git/lfs/objects/
- Les quotes costen diners
L'emmagatzematge LFS i la seva amplada de banda solen tenir quotes de pagament a les plataformes. I hi ha un parany que sorprèn molta gent: l'amplada de banda es consumeix també en baixar. Una CI que clona amb lfs: true cinquanta vegades al dia pot esgotar una quota mensual en pocs dies. D'aquí la importància del lfs: false de l'apartat 7.
A més, els objectes LFS de la història no s'esborren sols. Si migres un fitxer de 84 MB i després l'elimines, l'objecte continua ocupant quota. Purgar el magatzem és una operació específica de cada plataforma, no una ordre de Git.
- Les plataformes no es comporten igual
Hi ha diferències reals entre implementacions: com es gestionen els objectes als forks, si el bloqueig de fitxers està disponible, com s'apliquen les quotes, com es purguen els objectes orfes, i què passa en transferir un repositori entre comptes. No donis per fet que el que funciona en una plataforma funciona igual en una altra, sobretot en migrar de proveïdor: moure un repositori amb LFS d'un servidor a un altre requereix moure els objectes explícitament.
- Els fitxers LFS no es fusionen
Aquest és el més important en el dia a dia. Quan dues branques modifiquen el mateix fitxer LFS, hi ha conflicte sempre, i no es resol fusionant: cal triar una versió guanyadora.
Auto-merging recursos/mockup-panel.psd CONFLICT (content): Merge conflict in recursos/mockup-panel.psd Automatic merge failed; fix conflicts and then commit the result.
Objects to be committed:
recursos/mockup-panel.psd (LFS: 4d7a9f2 -> 9e2f7a4)
Unmerged paths:
recursos/mockup-panel.psdI la resolució és la mateixa que per a qualsevol binari (lliçó 03-05):
# Quedar-se amb la versió de la meva branca
git checkout --ours recursos/mockup-panel.psd
git add recursos/mockup-panel.psd
# O amb la de l'altra branca
git checkout --theirs recursos/mockup-panel.psd
git add recursos/mockup-panel.psd
# O, si de debò cal combinar el contingut:
# obrir les dues versions a l'aplicació de disseny i refer la feina a mà
git show :2:recursos/mockup-panel.psd > /tmp/meva.psd
git show :3:recursos/mockup-panel.psd > /tmp/seva.psdAquests :2: i :3: són les etapes de l'índex que vam veure a la lliçó 03-05 i a la 09-06: 1 és la base comuna, 2 «el nostre», 3 «el seu». Extreure-les permet obrir-les a l'aplicació corresponent i decidir amb la informació al davant.
La conseqüència organitzativa és que amb binaris no es pot treballar en paral·lel. La solució real no és tècnica sinó de coordinació: que només una persona toqui cada fitxer de disseny alhora. LFS ofereix un mecanisme per formalitzar-ho, el bloqueig de fitxers:
# Declarar que un patró és de bloqueig obligatori (a .gitattributes)
git lfs track "*.psd" --lockable
# Bloquejar abans d'editar
git lfs lock recursos/mockup-panel.psd
# Veure què hi ha bloquejat i per qui
git lfs locks
# Alliberar en acabar
git lfs unlock recursos/mockup-panel.psdAmb --lockable, LFS marca aquests fitxers com a només lectura al disc fins que els bloquegis. És un mecanisme d'exclusió mútua, com als sistemes centralitzats (lliçó 01-01), i és la prova que per a certs fluxos aquell model tenia sentit. Requereix suport del servidor.
- Tot l'equip l'ha d'instal·lar
Si algú clona sense LFS instal·lat i filter.lfs.required no està actiu, obtindrà punters de text en lloc de fitxers, i —el pitjor cas— podria confirmar un fitxer real a sobre d'un punter, trencant la coherència. Amb git lfs install ben fet això no passa, però és la raó per la qual la instal·lació ha de ser el primer del CONTRIBUTING.md.
- Algunes operacions es tornen més lentes o més estranyes
git checkoutentre branques amb fitxers LFS diferents implica baixades.git bisect(06-02) sobre un historial amb LFS pot baixar contingut a cada pas.- Els
worktree(06-06) comparteixen la memòria cau d'LFS, cosa que és bona, però cadascun necessita el seucheckout. - El
smudgefitxer a fitxer pot ser lent;git lfs pullés més eficient perquè baixa en lot.
- Alternatives a LFS
LFS no és l'única resposta, i de vegades no és la millor.
| Opció | Com funciona | A favor | En contra | Quan triar-la |
|---|---|---|---|---|
| Git LFS | Punter versionat + magatzem a part, via filtres de .gitattributes |
Integrat al flux normal; suportat per gairebé totes les plataformes; versiona el binari de debò | Requereix servidor; quotes de pagament; no fusiona; trenca l'autonomia del clon | Binaris que canvien i que formen part del producte: dissenys, recursos, models |
git annex |
Substitueix fitxers per enllaços simbòlics a un magatzem de contingut; admet molts backends (discos, S3, un altre ordinador) | Molt flexible; funciona sense servidor dedicat; pot repartir còpies entre diversos magatzems; excel·lent per a arxius enormes | Corba d'aprenentatge molt més gran; menys suport de plataformes; model mental propi | Arxius científics, col·leccions grans, entorns amb emmagatzematge heterogeni o sense connexió |
| No versionar el binari | El fitxer viu fora (emmagatzematge d'objectes, unitat compartida, gestor de recursos) i el repositori desa només una referència (URL + hash) | Sense cost ni complexitat a Git; sense quotes d'LFS; cada sistema fa el que li toca | Cal construir la coordinació; el binari no queda versionat amb el codi; risc que la referència es trenqui | Recursos grans que canvien poc: vídeos de màrqueting, conjunts de dades, dependències precompilades |
| Repensar el flux | Versionar la font i generar el binari; o desar l'original allà on es va crear i versionar només l'exportació lleugera | Elimina el problema d'arrel; sol millorar el procés | No sempre és possible | Quan el binari és derivat d'una cosa més petita |
La quarta mereix èmfasi
Abans d'instal·lar res, fes-te tres preguntes:
-
Aquest fitxer és generat? Un
.zipde distribució, un.pngexportat des d'un.svg, un executable compilat. Si es pot regenerar, no ha d'estar al repositori (lliçó 08-03): va al.gitignorei el produeix la CI com a artefacte. -
Necessito el seu historial? Un vídeo de demostració que se substitueix sencer cada sis mesos no guanya res versionat. Un
.psdsobre el qual s'itera cada dia i del qual de vegades cal tornar a la versió de la setmana passada, sí. -
Puc versionar la font en comptes del resultat? Un
.svgés text: es versiona, es diferencia i es fusiona perfectament. Un.pngde 4 MB exportat d'aquest.svgno aporta res. Canviar el format de treball resol el problema millor que qualsevol eina.
Aplicat a gestor-tasques:
| Fitxer | Decisió | Motiu |
|---|---|---|
mockup-panel.psd (84 MB, canvia sovint) |
LFS | S'hi itera, cal l'historial i és font, no derivat |
demo.mp4 (120 MB, es refà sencer) |
Fora del repositori | No cal el seu historial; un URL i un hash són prou |
logo.svg |
Git normal | És text: es diferencia i es fusiona |
logo-512.png (generat del .svg) |
.gitignore + generar a la CI |
És derivat |
Recursos de components-ui |
LFS al submòdul | Cada repositori decideix el que és seu (lliçó 06-05) |
- Quan fer servir LFS i quan no
Fes-lo servir quan es compleixin totes
- Tens fitxers binaris de més d'uns quants MB.
- Canvien amb certa freqüència, generant moltes versions.
- Necessites el seu historial: cal poder tornar a una versió anterior.
- No són derivats de res més petit que puguis versionar al seu lloc.
- Tens un servidor LFS disponible i pressupost per a la seva quota.
- Tot l'equip el pot instal·lar.
No el facis servir quan
- Els fitxers són petits (menys d'1 MB): el punter i la complexitat no compensen.
- Són generats: van al
.gitignore. - No necessites l'historial: desa'ls fora amb una referència.
- Són text, encara que siguin grans: Git els gestiona bé amb deltes i els fusiona.
- No tens servidor LFS i no en tindràs.
- El repositori ha de funcionar completament sense connexió des d'un sol clon.
La regla de decisió
flowchart TD
A["Tinc un fitxer gran"] --> B{"És text?"}
B -->|sí| C["Git normal.<br/>Les deltes funcionen bé"]
B -->|no| D{"És generat?"}
D -->|sí| E[".gitignore + artefacte de CI"]
D -->|no| F{"Necessito<br/>el seu historial?"}
F -->|no| G["Fora del repositori:<br/>URL + hash de verificació"]
F -->|sí| H{"Puc versionar<br/>la font al seu lloc?"}
H -->|sí| I["Versiona la font.<br/>Genera la resta"]
H -->|no| J{"Tinc servidor<br/>LFS i quota?"}
J -->|sí| K["Git LFS"]
J -->|no| L["git annex o<br/>magatzem extern"]
La resposta més freqüent en un projecte com gestor-tasques és «no ho necessites». Un repositori de codi pur amb un grapat d'icones i un logotip no té cap problema que LFS resolgui. La necessitat apareix quan hi entren fitxers de disseny, vídeo, àudio, models 3D, conjunts de dades o recursos de videojoc. En un repositori de 50.000 fitxers amb recursos gràfics, LFS és imprescindible; en un de quatre fitxers, és complexitat sense benefici.
Errors Habituals i Consells
Error 1: adoptar LFS després d'haver ficat els binaris. Declarar el patró només afecta el futur. Els objectes que ja són a l'historial hi continuen i es continuen baixant. Cal git lfs migrate import i una reescriptura coordinada. Configura LFS el dia que creïs el repositori, no quan pesi 2 GB.
Error 2: oblidar el -text. Sense ell, un * text=auto pot normalitzar els finals de línia en un binari i corrompre'l. git lfs track el posa; si edites .gitattributes a mà, no el treguis.
Error 3: confirmar els binaris abans que .gitattributes. Els filtres s'apliquen en el moment de l'add. .gitattributes primer, en el seu propi commit, i els binaris després.
Error 4: no adonar-se que es tenen punters. Si un fitxer d'imatge pesa 130 bytes i comença per version https://git-lfs..., és un punter. Executa git lfs pull. Comprovació ràpida:
Error 5: deixar la CI baixant LFS sense necessitar-ho. És la via més ràpida d'esgotar la quota d'amplada de banda. lfs: false tret dels treballs que realment necessiten els fitxers.
Error 6: git lfs fetch --all per costum. Baixa tot l'historial de contingut, que és justament el que LFS existeix per evitar. Només per a còpies de seguretat o migracions.
Error 7: creure que un clon amb LFS és una còpia de seguretat completa. No ho és: et falten els objectes LFS que no hagis baixat. Una còpia real necessita el bundle i el contingut LFS.
Error 8: dues persones editant el mateix .psd alhora. Acabarà en conflicte irresoluble i algú perdrà feina. Fes servir --lockable i git lfs lock, o simplement acordeu qui toca què.
Consell 1: audita abans de decidir. git lfs migrate info --everything --above=1Mb et diu exactament què està inflant el repositori, sense tocar res.
Consell 2: posa un límit de mida a la CI. Un guardià automàtic evita el problema d'arrel (lliçó 07-06):
- name: Rebutjar fitxers grans fora d'LFS
run: |
LIMIT=$((5 * 1024 * 1024))
GRANS=$(git diff --name-only --diff-filter=ACM origin/main...HEAD | while read -r f; do
[ -f "$f" ] || continue
# Els punters LFS són diminuts, així que mai no disparen l'avís
MIDA=$(wc -c < "$f")
[ "$MIDA" -gt "$LIMIT" ] && echo "$f ($MIDA bytes)"
done)
if [ -n "$GRANS" ]; then
echo "Fitxers de més de 5 MB fora d'LFS:"
echo "$GRANS"
echo "Declara el patró amb 'git lfs track' o treu-los del repositori."
exit 1
fiConsell 3: documenta LFS al CONTRIBUTING.md. Tres línies al principi: instal·la git-lfs, executa git lfs install, i si veus fitxers de 130 bytes, executa git lfs pull. Estalvia la meitat de les preguntes de suport.
Consell 4: prova la migració en un clon d'usar i llençar. Sempre. I verifica comparant el hash de l'arbre dels directoris de codi abans i després: si coincideixen, el codi és intacte.
Consell 5: git lfs prune de tant en tant. La memòria cau local creix sense límit. Amb lfs.pruneverifyremotealways = true és una operació segura.
Exercicis
Exercici 1: diagnosticar un repositori inflat
El Diego clona gestor-tasques i obté això:
$ git count-objects -vH count: 0 size: 0 bytes in-pack: 3847 packs: 1 size-pack: 512.84 MiB $ ls -lh recursos/ -rw-r--r-- 1 diego diego 130 ag. 1 09:14 mockup-panel.psd -rw-r--r-- 1 diego diego 84M ag. 1 09:14 mockup-llistat.psd -rw-r--r-- 1 diego diego 118M ag. 1 09:14 demo.mp4
Respon:
A. Per què mockup-panel.psd pesa 130 bytes i els altres dos no?
B. Per què el repositori ocupa 512 MB si suposadament fan servir LFS?
C. Quines ordres executaries per confirmar el teu diagnòstic?
D. Quina és la solució completa?
Exercici 2: configurar LFS des de zero, en l'ordre correcte
L'equip afegirà a gestor-tasques:
recursos/dissenys/*.psd— fitxers de disseny, ~80 MB, canvien setmanalment, s'hi iterarecursos/video/demo.mp4— 120 MB, es refà sencer cada sis mesosrecursos/icones/*.svg— text, uns pocs KB cadascunrecursos/icones/generats/*.png— exportats automàticament dels SVGdist/gestor-tasques.zip— paquet de distribució, generat a cada versió
Escriu la seqüència completa d'ordres i fitxers, decidint per a cada tipus si va a LFS, a Git normal, al .gitignore o fora del repositori. Justifica cada decisió i respecta l'ordre correcte dels commits.
Exercici 3: migració amb obstacles
gestor-tasques porta dos anys amb .psd i .mp4 versionats directament. El repositori pesa 3,2 GB. Es decideix migrar a LFS.
Restriccions:
- Hi ha tres propostes obertes del Diego.
mainestà protegida i exigeix revisió.- Hi ha etiquetes
v1.0.0av1.7.0, iv1.6.0és el que és en producció. - El Bruno té una branca local amb cinc commits sense enviar.
- Els commits van signats (08-05).
Escriu el pla complet, en ordre, indicant què passa amb cada restricció i quines comprovacions faries. Assenyala en quin punt exacte el procés és irreversible.
Solucions
Solució 1
A. mockup-panel.psd és un punter LFS; els altres dos són fitxers reals versionats directament a Git.
Només el primer va ser declarat a .gitattributes abans d'afegir-lo. Els altres dos, o bé es van afegir abans de configurar LFS, o bé el seu patró no es va declarar mai.
B. Perquè els 512 MB són els .psd i .mp4 que NO són a LFS, amb totes les seves versions històriques. Adoptar LFS a mitges no redueix res: el repositori continua arrossegant tot el que s'hi va ficar abans o fora dels patrons.
C. Diagnòstic pas a pas:
# 1. Què és realment a LFS?
git lfs ls-files
# Només hi apareixerà mockup-panel.psd
# 2. Quins patrons estan declarats?
git lfs track
cat .gitattributes
# Probablement només "recursos/mockup-panel.psd" o "*.psd" afegit tard
# 3. Què ocupa l'historial? (lliçó 08-06)
git rev-list --objects --all \
| git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
| awk '$1=="blob" {print $3, $4}' \
| sort -rn | head -15
# 4. La vista agregada per extensió, que és la definitiva
git lfs migrate info --everything --above=1MbEl pas 4 donarà una cosa com:
Aquest 9/12 és la pista clau: de dotze fitxers .psd de l'historial, nou són fora d'LFS.
D. Solució completa, en dues parts:
Part 1: arreglar la configuració per al futur.
git lfs track "*.psd"
git lfs track "*.mp4"
git add .gitattributes
git commit -m "chore: GT-262 declara tots els binaris a LFS"Part 2: netejar l'historial (reescriptura coordinada, apartat 9).
git lfs migrate import --everything --include="*.psd,*.mp4"
git lfs push --all origin
git push --force-with-lease --all origin
git push --force-with-lease --tags originI avisar l'equip que ha de clonar de nou. Sense la part 2 el repositori continuarà pesant 512 MB per sempre, perquè els objectes antics no desapareixen en canviar la configuració.
Solució 2
Decisions, amb el seu motiu:
| Fitxer | Decisió | Per què |
|---|---|---|
recursos/dissenys/*.psd |
LFS | Binari gran, canvia sovint, cal l'historial, no és derivat |
recursos/video/demo.mp4 |
Fora del repositori | No cal el seu historial (es refà sencer). Referència al README.md amb URL i hash |
recursos/icones/*.svg |
Git normal | És text: es diferencia, es fusiona i comprimeix bé. LFS seria una nosa |
recursos/icones/generats/*.png |
.gitignore |
Derivats dels SVG. Es generen a la CI |
dist/gestor-tasques.zip |
.gitignore |
Artefacte de construcció (lliçó 08-03). Es publica com a artefacte de la versió |
Sobre el .mp4: si l'equip prefereix versionar-lo, LFS és una opció defensable. Però com que no cal el seu historial, desar-lo fora evita consumir quota amb dues còpies de 120 MB.
Seqüència completa, en l'ordre correcte:
cd ~/gestor-tasques
# --- Pas 1: instal·lació (una vegada per màquina, cada persona) ---
git lfs install
# --- Pas 2: declarar els patrons LFS ---
git lfs track "recursos/dissenys/*.psd"
# --- Pas 3: .gitignore per al que és derivat ---
cat >> .gitignore <<'EOF'
# Recursos generats (GT-263)
recursos/icones/generats/
dist/
EOF
# --- Pas 4: verificar el que s'ha declarat ABANS de confirmar ---
git lfs track
cat .gitattributes
# --- Pas 5: confirmar la CONFIGURACIÓ primer, sola ---
git add .gitattributes .gitignore
git commit -m "chore: GT-263 configura Git LFS per als fitxers de disseny
Els .psd de recursos/dissenys/ passen a gestionar-se amb Git LFS: a
l'historial només hi queden punters de text i el contingut viu al
magatzem LFS del servidor.
Els PNG generats i el paquet de distribució s'ignoren: són derivats
i els produeix la canonada d'integració.
El vídeo de demostració no es versiona; el seu URL i el seu hash
queden documentats al README.
Requisit: executar 'git lfs install' abans de clonar."Confirmar la configuració sola i abans és el que garanteix que els filtres estiguin actius quan arribin els binaris.
# --- Pas 6: ara sí, els fitxers ---
git add recursos/dissenys/panel.psd recursos/dissenys/llistat.psd
git add recursos/icones/*.svg
# --- Pas 7: verificar ABANS de confirmar ---
git lfs statusObjects to be committed:
recursos/dissenys/panel.psd (LFS: 4d7a9f2)
recursos/dissenys/llistat.psd (LFS: 9e2f7a4)Aquesta comprovació és la clau de l'exercici. Si digués (File: ...) en lloc de (LFS: ...), el filtre no s'hauria aplicat i caldria desfer l'add, revisar .gitattributes i tornar a començar. Verificar aquí costa cinc segons; descobrir-ho després costa una reescriptura d'historial.
git commit -m "feat: GT-263 afegeix els dissenys del panell i del llistat"
# --- Pas 8: confirmar que a l'historial hi ha punters, no binaris ---
git show HEAD:recursos/dissenys/panel.psd
git cat-file -s HEAD:recursos/dissenys/panel.psd # ha de donar ~130
# --- Pas 9: publicar ---
git push origin main
git lfs ls-filesI al CONTRIBUTING.md:
## Abans de clonar
Aquest repositori fa servir Git LFS per als fitxers de disseny.
1. Instal·la git-lfs: `apt install git-lfs` / `brew install git-lfs`
2. Executa una vegada: `git lfs install`
3. Clona normalment.
Si un `.psd` pesa 130 bytes i comença per `version https://git-lfs...`,
tens un punter. Executa `git lfs pull`.
El vídeo de demostració no és al repositori: baixa'l de l'URL indicada
al README i verifica'n el hash.Solució 3
Pla complet, per fases.
Fase 0 — Preparació (reversible).
Amb aquesta dada es decideix si val la pena. Reduir de 3,2 GB a ~150 MB, sí; a 2,8 GB, no.
Restricció — les tres propostes del Diego: cal tancar-les abans. S'integren o s'abandonen. Una proposta basada en commits que deixaran d'existir queda inservible, i demanar a un col·laborador extern que refaci tres branques sobre un historial reescrit és una mala experiència. Aquesta és la restricció que marca el calendari.
Restricció — la branca local del Bruno: que l'enviï al servidor abans de la migració. Així es reescriurà juntament amb tota la resta i no l'haurà de reubicar a mà. Si no pot, haurà de fer el rebase --onto de l'apartat 9.
# Cada persona comprova que no li queda res
git log --branches --not --remotes --oneline
git stash list
git statusFase 1 — Còpies de seguretat (reversible).
git clone --mirror https://git.exemple.cat/gestor-tasques.git ~/copies/abans.git
git bundle create ~/copies/abans-$(date +%F).bundle --all
# Verificar que la còpia serveix de debò
git bundle verify ~/copies/abans-$(date +%F).bundleI, si en algun moment calguessin els binaris originals, baixar el contingut abans de perdre'l de vista.
Fase 2 — Assaig (reversible).
git clone https://git.exemple.cat/gestor-tasques.git /tmp/assaig
cd /tmp/assaig
git lfs migrate import --everything --include="*.psd,*.mp4"
# Quant s'ha reduït?
git count-objects -vH
# Verificació criptogràfica: el CODI no ha canviat
git rev-parse HEAD:src
git -C ~/gestor-tasques rev-parse HEAD:src # han de coincidir
# v1.6.0 continua tenint el mateix contingut de codi?
git rev-parse v1.6.0^{tree}
git cat-file -p v1.6.0^{tree}
# Anotar la correspondència entre etiquetes velles i noves
git for-each-ref --format='%(refname:short) %(objectname:short)' refs/tagsAquesta taula de correspondències cal desar-la i publicar-la: si algú té apuntat que en producció hi ha el commit 3f2a1b9, necessitarà saber quin és el seu equivalent nou.
Restricció — les signatures: en reescriure els commits, les signatures s'invaliden i es perden. L'objecte commit canvia, així que la signatura sobre l'objecte anterior deixa de ser vàlida. Cal assumir-ho i documentar-ho: els commits anteriors a la migració quedaran sense signatura verificable. És un argument seriós per fer la migració com abans millor o no fer-la mai, i una raó més per configurar LFS el primer dia. Els commits posteriors se signaran amb normalitat.
Restricció — v1.6.0 en producció: l'etiqueta canviarà de hash. Cal comprovar què apunta a aquell commit al sistema de desplegament (lliçó 10-05) i actualitzar-ho. I verificar que la nova v1.6.0 produeix el mateix artefacte que la vella: mateix codi, hash de commit diferent.
Fase 3 — Finestra de migració (PUNT IRREVERSIBLE).
El punt de no retorn és el
git push --force-with-lease. Tot l'anterior és reversible. A partir d'aquí, el servidor té l'historial nou i qualsevol que cloni o porti canvis obté la versió reescrita. Tornar enrere requereix restaurar des de la còpia mirall i repetir l'anunci.
# 1. Anunciar l'inici i demanar que ningú no enviï res
# 2. Desprotegir main temporalment al servidor <-- restricció de branca protegida
# 3. Migrar
cd ~/gestor-tasques
git fetch --all --prune
git lfs migrate import --everything --include="*.psd,*.mp4"
# 4. PRIMER el contingut LFS
git lfs push --all origin
# 5. Verificar que el magatzem el té abans de tocar les referències
git lfs ls-files --all | wc -l
# 6. ARA les referències <-- IRREVERSIBLE a partir d'aquí
git push --force-with-lease --all origin
git push --force-with-lease --tags origin
# 7. Tornar a protegir mainLa branca protegida exigeix desprotegir-la i tornar-la a protegir. Apunta-ho al guió: és el pas que més s'oblida, i deixar main desprotegida un cap de setmana és un risc innecessari.
Fase 4 — Recuperació de l'equip.
Missatge a l'equip:
L'historial de gestor-tasques s'ha reescrit per migrar els binaris a LFS. El repositori ha passat de 3,2 GB a ~150 MB. QUÈ HAS DE FER: 1. git lfs install (si encara no ho has fet) 2. Reanomena el teu clon actual i clona de nou. 3. git lfs pull NO facis 'git pull' sobre el teu clon vell: produirà una divergència total. Les etiquetes han canviat de hash. Correspondències: <enllaç> Els commits anteriors a avui han perdut la signatura; els nous se signen igual. Si tens feina local sense enviar, avisa'm abans de tocar res.
Fase 5 — Tancament.
I al servidor: demanar la recollida d'escombraries perquè alliberi l'espai dels objectes antics. Sense aquest pas, el repositori del servidor continuarà pesant 3,2 GB i la migració semblarà no haver servit de res.
Per últim, posar el guardià a la CI (consell 2) perquè això no torni a passar, i documentar LFS al CONTRIBUTING.md.
Conclusió
Git és excel·lent per al text i pèssim per als binaris grans, i ara saps exactament per què: no és una limitació arbitrària, és una conseqüència del model de dades. Cada versió és un blob complet, les deltes no funcionen sobre contingut ja comprimit, els objectes són immutables i tothom es baixa l'historial sencer.
- Git LFS substitueix el fitxer per un punter de text d'uns 130 bytes —
version,oid sha256:...,size— que sí que es versiona bé, i desa el contingut real en un magatzem a part. Funciona mitjançant els filtrescleanismudgede.gitattributes, el mateix mecanisme de la lliçó 08-04. - La configuració viu a
.gitattributescom afilter=lfs diff=lfs merge=lfs -text: elfilterfa la substitució,diffimergeregistren els controladors d'LFS, i-textevita que la normalització de finals de línia corrompi el binari. En estar versionada, tot l'equip l'hereta; el que no s'hereta és la instal·lació d'LFS. - L'ordre importa:
git lfs track, confirmar.gitattributessol, i després afegir els binaris. Verificar sempre ambgit lfs statusque diu(LFS: ...)i no(File: ...). - En clonar es baixen els punters i només el contingut de la revisió extreta.
GIT_LFS_SKIP_SMUDGE=1ilfs: falsea la CI eviten baixades innecessàries;git lfs pullles porta quan calen. Igit lfs fetch --allbaixa tot l'historial de contingut: fes-lo servir només per a còpies de seguretat. git lfs migrate importreescriu l'historial, amb tota la força de la regla d'or de la lliçó 05-01: canvien els hashos, canvien les etiquetes, s'invaliden les signatures, i tothom ha de clonar de nou. Assaja en una còpia, verifica amb el hash de l'arbre que el codi no s'ha tocat, publica primer el contingut LFS i després les referències, i no oblidis demanar la recollida d'escombraries del servidor.- Les limitacions són reals i cal conèixer-les abans: cal servidor, les quotes costen diners (també l'amplada de banda de baixada), les plataformes no es comporten igual, el clon deixa de ser autònom, i els fitxers LFS no es fusionen: cal triar versió guanyadora, o coordinar-se amb
--lockableigit lfs lock. - I hi ha alternatives:
git annexper a escenaris més flexibles, desar el binari fora amb una referència quan no necessites el seu historial, i sobretot repensar el flux: si el fitxer és generat, va al.gitignore; si pots versionar la font en lloc del resultat, fes-ho.
La idea que resumeix la lliçó:
LFS no fa que Git gestioni bé els binaris. Fa que Git deixi de gestionar-los, versionant al seu lloc una referència. És una solució excel·lent quan el binari és part real del producte i necessites el seu historial, i complexitat innecessària en qualsevol altre cas.
Per a gestor-tasques, amb els seus quatre fitxers de text, LFS era irrellevant fins ahir. Amb l'arribada dels fitxers de disseny ha passat a ser necessari, i ha arribat just a temps: configurar-lo avui costa dos commits; fer-ho d'aquí a un any hauria costat una reescriptura d'historial i una tarda de coordinació.
El que ve
LFS resol una de les tres maneres en què un repositori es torna ingovernable: els fitxers molt grans. En queden les altres dues, i són independents.
Un repositori pot tenir moltíssim historial —vint anys i centenars de milers de commits— encara que cada fitxer sigui diminut: clonar-lo triga una eternitat i git log s'arrossega. I pot tenir moltíssims fitxers —centenars de milers a la còpia de treball— encara que l'historial sigui curt: git status triga mig minut perquè ha de recórrer l'arbre sencer.
Cadascuna té el seu propi remei, i cap no serveix per a les altres dues. Els clons parcials amb --filter=blob:none ataquen el volum de contingut baixat. El sparse-checkout amb índex dispers ataca el nombre de fitxers al disc. El commit-graph ataca el cost de recórrer l'historial. I per damunt de tot hi ha la decisió que condiciona la resta: monorepo o multirepo, la comparativa que la lliçó 06-05 va deixar oberta i que el cas 3 de la 10-01 va deixar pendent d'explicar.
La lliçó 10-04: Escalant Git per a Projectes Grans tanca totes dues promeses, i comença per l'única regla que importa en optimització: mesurar abans de tocar res.
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ó
