La lliçó anterior va deixar gestor-tasques amb un historial llegible, bisecable i reversible. Però un historial impecable pot contenir brossa, i el de gestor-tasques en conté: el Bruno va pujar sense adonar-se'n la carpeta node_modules sencera —14.000 fitxers i 180 MB—, la Carla ha anat sembrant Thumbs.db pels directoris d'imatges, el macOS del Bruno deixa un .DS_Store a cada carpeta que obre al Finder, i en algun commit de fa mesos hi ha un fitxer config.js amb la contrasenya de la base de dades de proves.
Aquesta lliçó tracta de la primera línia de defensa contra tot això: .gitignore, el mecanisme amb què se li diu a Git quins fitxers no ha ni de proposar-se versionar. El vam esmentar de passada a la lliçó 02-04, en veure git status, i vam prometre desenvolupar-lo aquí.
És un fitxer enganyosament simple. La seva sintaxi té cantonades que confonen tothom —la negació, la barra inicial, el parany dels directoris— i, sobretot, amaga una regla que és la causa del 90 % de les preguntes sobre Git als fòrums: .gitignore no serveix per deixar de seguir un fitxer que ja està versionat.
Contingut
- Per què importa allò que no es versiona
- Què no ha d'entrar mai: taula per categories
- Com funciona: precedència i àmbit
- Sintaxi completa dels patrons
- La negació amb
!i el seu parany - Els tres nivells: projecte, local i global
- La regla que confon tothom: només afecta allò sense seguiment
- Treure del seguiment amb
git rm --cached - Depurar amb
git check-ignore -v - Forçar amb
git add -f - Plantilles per llenguatge
- El patró per a secrets:
.envi.env.exemple
- Per què importa allò que no es versiona
Versionar el que no toca té quatre costos, i els quatre són més grans del que sembla:
| Cost | Conseqüència concreta |
|---|---|
| Mida permanent | Els 180 MB de node_modules queden a l'historial per sempre, encara que després s'esborrin. Cada clon se'ls baixa (lliçó 08-06). |
| Conflictes absurds | Fitxers generats (compilats, bloquejos, .idea/workspace.xml) canvien a cada màquina i provoquen conflictes a cada fusió, sense cap significat. |
| Soroll a les revisions | Una PR de 30 línies útils amb 4.000 línies d'artefactes és irrevisable (lliçó 07-02). |
| Risc de seguretat | Un secret al repositori es replica a cada clon i no s'esborra esborrant el fitxer. És el que veurem a la lliçó 08-05. |
Hi ha un principi que ordena totes les decisions:
Es versiona la font, no el resultat. Si un fitxer es pot regenerar a partir d'altres fitxers del repositori, no es versiona. Si conté informació pròpia d'una màquina o d'una persona, no es versiona. Si és un secret, no es versiona mai.
Amb dues excepcions importants que convé conèixer, perquè són les que confonen:
- Els fitxers de bloqueig de dependències (
package-lock.json,yarn.lock,Cargo.locken aplicacions) sí que es versionen. No són un resultat: són la font de la reproductibilitat. Sense ells, dues persones instal·len versions diferents. - La configuració compartida del projecte (
.editorconfig,.gitattributes, la part del.vscode/que defineix tasques i extensions recomanades) sí que es versiona. És font, no resultat.
- Què no ha d'entrar mai: taula per categories
| Categoria | Exemples | Per què no |
|---|---|---|
| Dependències | node_modules/, vendor/, bower_components/, venv/, .venv/, target/ (Rust) |
Es regeneren amb npm ci, composer install, pip install -r. Milers de fitxers, desenes o centenars de MB, i contingut diferent segons el sistema operatiu. |
| Artefactes de compilació | dist/, build/, out/, *.o, *.class, *.pyc, __pycache__/, *.min.js generats |
Són la sortida del codi que sí que està versionat. Canvien a cada compilació i generen conflictes garantits. |
| Fitxers del sistema operatiu | .DS_Store, ._*, .Spotlight-V100 (macOS); Thumbs.db, Desktop.ini, $RECYCLE.BIN/ (Windows); .directory (Linux) |
No tenen res a veure amb el projecte: són metadades de l'explorador de fitxers. El Bruno i la Carla embruten el repositori sense voler. |
| Configuració de l'editor i de l'IDE | .idea/, .vscode/settings.json personal, *.swp, *.swo, .project, .classpath, *.sublime-workspace |
Reflecteixen preferències personals i rutes absolutes d'una màquina. Provoquen conflictes entre qui fa servir un editor i qui en fa servir un altre. |
| Secrets i credencials | .env, .env.local, config/secrets.yml, *.pem, *.key, id_rsa, fitxers de credencials de serveis |
Mai. Un secret a l'historial és un secret compromès, encara que l'esborris després (lliçó 08-05). |
| Registres i temporals | *.log, logs/, tmp/, *.tmp, *.bak, *.swp, npm-debug.log* |
Efímers per definició. Ningú els consultarà d'aquí a un any. |
| Bases de dades locals i bolcats | *.sqlite, *.db, dump.sql, dades-locals/ |
Grans, binaris i amb dades que poden ser personals. |
| Fitxers grans i binaris generats | Vídeos de prova, imatges exportades, *.zip, *.tar.gz |
Cada versió es desa sencera (lliçó 08-06). Si són binaris necessaris, la solució és Git LFS, lliçó 10-03. |
| Cobertura i resultats de proves | coverage/, .nyc_output/, junit.xml, .pytest_cache/ |
Sortida d'una execució concreta. |
| Memòries cau d'eines | .cache/, .parcel-cache/, .eslintcache, .next/, .nuxt/ |
Reconstruïbles i específiques de la màquina. |
El .gitignore que l'equip de gestor-tasques va acabar adoptant:
# --- Dependències ---------------------------------------------------
node_modules/
# --- Artefactes de compilació ---------------------------------------
dist/
build/
*.min.js
*.min.css
# --- Secrets (veure 08-05) ------------------------------------------
.env
.env.*
!.env.exemple
*.pem
*.key
# --- Registres i temporals ------------------------------------------
*.log
npm-debug.log*
tmp/
*.tmp
*.bak
# --- Cobertura i memòries cau ---------------------------------------
coverage/
.eslintcache
.cache/
# --- Sistema operatiu -----------------------------------------------
# macOS (Bruno)
.DS_Store
._*
.Spotlight-V100
.Trashes
# Windows (Carla)
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
Desktop.ini
$RECYCLE.BIN/
# Linux (Ana)
.directory
*~
# --- Editors --------------------------------------------------------
.idea/
*.swp
*.swo
.vscode/*
!.vscode/extensions.json
!.vscode/tasks.jsonFixa't en els dos usos de ! (negació) al final de blocs: !.env.exemple i !.vscode/extensions.json. Els expliquem a l'apartat 5, juntament amb el parany que amaguen.
- Com funciona: precedència i àmbit
Tres regles governen el comportament:
Regla 1: .gitignore s'aplica per directori i cap avall. Un .gitignore afecta el seu propi directori i tots els seus subdirectoris. En pots tenir diversos en un mateix repositori.
gestor-tasques/
├── .gitignore ← regles globals del projecte
├── app.js
├── imatges/
│ ├── .gitignore ← regles només per a imatges/ i els seus fills
│ └── originals/
└── proves/
└── .gitignore ← regles només per a proves/Regla 2: guanya el patró més específic. Git avalua, en aquest ordre de prioritat creixent:
core.excludesFile(global de l'usuari).git/info/exclude(local del repositori).gitignoredels directoris, de l'arrel cap avall (el més profund guanya)- La línia d'ordres (
git add -f)
Regla 3: dins d'un mateix fitxer, guanya l'última línia que coincideixi. Per això les negacions van després del patró que anul·len:
!errors.log # aquesta negació no serveix de res...
*.log # ...perquè aquesta línia posterior ho torna a ignorar totEl .gitignore es versiona. És part del projecte i ha de ser al primer commit. Si no hi és, cada persona de l'equip ha de descobrir pel seu compte què no ha de pujar, i algú fallarà.
- Sintaxi completa dels patrons
Git fa servir una sintaxi derivada dels globs de shell, amb algunes particularitats pròpies.
| Patró | Què fa | Exemple del que hi encaixa |
|---|---|---|
nom |
Qualsevol fitxer o directori anomenat així, a qualsevol profunditat | app.log, src/app.log, a/b/c/app.log |
nom/ |
Només si és un directori | logs/ sí; un fitxer anomenat logs no |
/nom |
Només a l'arrel del directori del .gitignore |
/build encaixa amb build a l'arrel, no amb src/build |
* |
Qualsevol seqüència de caràcters, sense creuar / |
*.log encaixa amb error.log, no amb logs/error.log (encara que el nom solt sí) |
? |
Exactament un caràcter | imatge?.png encaixa amb imatge1.png, no amb imatge10.png |
[abc] |
Un d'aquests caràcters | imatge[123].png encaixa amb imatge2.png |
[0-9] |
Un caràcter del rang | registre[0-9].txt encaixa amb registre7.txt |
**/ |
Qualsevol nombre de directoris (inclòs cap) | **/temporal encaixa amb temporal, a/temporal, a/b/temporal |
/** |
Tot el que hi ha a dins | logs/** encaixa amb tot el contingut de logs/ |
a/**/b |
b sota a, a qualsevol profunditat |
a/b, a/x/b, a/x/y/b |
!patro |
Des-ignora allò que encaixaria abans (amb límits: apartat 5) | |
# text |
Comentari (línia sencera) | |
\#literal |
Un fitxer que comença per # |
Encaixa amb el fitxer anomenat #literal |
nom\ |
Espai final literal (els espais finals s'ignoren llevat que vagin escapats) |
Els dos matisos que cal interioritzar
La barra inicial ancora a l'arrel. És la diferència més important i la més oblidada:
build/ # ignora QUALSEVOL directori build, a qualsevol profunditat
/build/ # ignora NOMÉS el build de l'arrel del projecteSi el teu projecte té build/ a l'arrel i també src/components/build/ que sí que vols versionar, necessites la barra inicial.
Una barra al mig del patró també ancora. Aquesta és la regla que sorprèn: si el patró conté una / en qualsevol posició que no sigui la final, s'interpreta com a relatiu al directori del .gitignore, no com a "a qualsevol profunditat".
doc/notes.txt # només el doc/notes.txt de l'arrel
notes.txt # qualsevol notes.txt, a qualsevol profunditat
**/doc/notes.txt # qualsevol doc/notes.txt, a qualsevol profunditatExemples comentats
# Tots els .log, siguin on siguin
*.log
# Però no els de la carpeta d'exemples (veure apartat 5)
!exemples/*.log
# Només el directori build de l'arrel
/build/
# Qualsevol directori anomenat tmp, a qualsevol profunditat
tmp/
# El fitxer config.local.js de l'arrel, no altres config.local.js
/config.local.js
# Qualsevol fitxer el nom del qual comenci per "esborrany"
esborrany*
# Fitxers numerats del 0 al 9
bolcat[0-9].sql
# Tot el contingut de dades/, però conservant la carpeta (veure més avall)
dades/*
!dades/.gitkeep
# Un fitxer que es diu literalment "!important"
\!importantEl truc de .gitkeep
Git no versiona directoris, només fitxers: un directori buit simplement no existeix per a Git. Si la teva aplicació necessita que existeixi dades/ en arrencar, el patró habitual és:
I crees un fitxer buit dades/.gitkeep. El nom no té res d'especial —Git no el coneix—; és només una convenció per dir "aquest fitxer existeix perquè el directori existeixi".
Fixa't que fem servir dades/* i no dades/. La raó és el parany de l'apartat següent.
- La negació amb
! i el seu parany
! i el seu paranyLa regla que més temps fa perdre a tot .gitignore:
Si un directori està ignorat, Git no hi entra. Per tant, no es pot des-ignorar un fitxer que sigui dins d'un directori ignorat.
És una optimització deliberada: Git no recorre milers de fitxers de node_modules/ només per si alguna regla posterior en rescata algun. I produeix aquesta fallada, que sembla un error de Git i no ho és:
dades/important.csv continua ignorat. Git ha descartat dades/ sencer i mai no ha arribat a avaluar la segona línia.
La manera correcta és ignorar el contingut, no el directori:
Amb dades/*, Git sí que entra a dades/, ignora cadascun dels seus fills individualment, i la negació en pot rescatar un.
Quan hi ha diversos nivells
Si el fitxer que vols rescatar és més profund, cal des-ignorar cada nivell intermedi:
Traduït: ignora-ho tot (*), però no ignoris els directoris (!*/, necessari perquè Git hi pugui entrar), i rescata explícitament aquell fitxer.
Aquest patró de llista blanca —"ignorar-ho tot i rescatar el que vull"— és agressiu però molt útil en repositoris on allò versionable és una minoria (per exemple, un repositori de configuració):
El cas del .gitignore de gestor-tasques
Tornem a les dues negacions que vam deixar pendents:
Funciona perquè .env.* ignora fitxers, no un directori, i la negació posterior en rescata un. Correcte.
Fixa't en .vscode/* i no .vscode/. Si haguéssim escrit .vscode/, les dues negacions no farien res. Aquest és exactament l'error que comet tothom la primera vegada.
- Els tres nivells: projecte, local i global
Hi ha tres llocs on declarar exclusions, i triar malament és la causa d'una discussió recurrent a l'equip: la Carla va proposar afegir .idea/ al .gitignore del projecte i l'Ana s'hi va oposar perquè ningú més fa servir aquell IDE. Tots dos tenien part de raó, i el problema era de nivell.
| Nivell | Fitxer | Es versiona? | A qui afecta? | Per a què |
|---|---|---|---|---|
| Projecte | .gitignore al repositori |
Sí | A tot l'equip | Allò que és del projecte: node_modules/, dist/, .env, coverage/ |
| Local | .git/info/exclude |
No | Només a tu, només en aquell repositori | Fitxers temporals teus en aquell projecte: notes-ana.md, proves-locals/ |
| Global | Ruta indicada per core.excludesFile |
No | A tu, a tots els teus repositoris | Allò que és de la teva màquina o la teva eina: .DS_Store, .idea/, *.swp |
El criteri
Aquest fitxer apareixeria a la màquina de qualsevol que treballés en aquest projecte? Si sí, va al
.gitignoredel projecte. Si apareix només pel teu sistema operatiu o el teu editor, va al teu global. Si és un caprici teu en aquest repositori concret, va a.git/info/exclude.
Estrictament, .DS_Store i .idea/ van al global. A la pràctica, gairebé tots els projectes els posen també al .gitignore del projecte, com a xarxa de seguretat per a qui no tingui el global configurat. És defensiu i és raonable; simplement cal saber que és una duplicació deliberada.
Configurar el global
# 1. Crear el fitxer
cat > ~/.gitignore_global <<'FI'
# Sistema operatiu
.DS_Store
._*
Thumbs.db
Desktop.ini
.directory
# Editors
.idea/
*.swp
*.swo
*~
.vscode/
# Eines personals
.env.personal
notes-privades.md
FI
# 2. Dir-li-ho a Git
git config --global core.excludesFile ~/.gitignore_global
# 3. Comprovar
git config --get core.excludesFileA macOS i Linux, Git ja fa servir ~/.config/git/ignore sense necessitat de configurar res si el fitxer existeix. És la ubicació per defecte segons l'estàndard XDG:
.git/info/exclude
És un .gitignore normal que viu dins de .git/. Com tot el que hi ha allà (lliçó 01-04), no es versiona ni es clona:
cat >> .git/info/exclude <<'FI'
# Les meves coses en aquest repositori, que ningú més necessita ignorar
notes-ana.md
proves-locals/
bolcat-avui.sql
FIAvantatge respecte a modificar el .gitignore del projecte: no embrutes el fitxer compartit amb les teves manies, i no les has de justificar en una revisió. Desavantatge: es perd si esborres i tornes a clonar el repositori.
- La regla que confon tothom: només afecta allò sense seguiment
Aquí hi ha la font del 90 % de les preguntes sobre .gitignore:
.gitignorenomés afecta els fitxers SENSE SEGUIMENT. Si un fitxer ja és a l'índex, Git continuarà registrant-ne els canvis per molts patrons que escriguis.
Recorda els tres estats de la lliçó 01-03 i el cicle de la lliçó 02-03. .gitignore actua exclusivament sobre la transició de sense seguiment a preparat: li diu a Git "ni me'l proposis a git status, ni l'agafis amb git add .". Sobre un fitxer que ja va creuar aquella frontera, no té cap efecte.
L'escenari típic, que li va passar al Bruno:
# Dilluns: el Bruno puja node_modules sense adonar-se'n
git add .
git commit -m "chore: primer commit"
git push# Dimarts: l'Ana ho veu i afegeix el .gitignore
echo "node_modules/" >> .gitignore
git add .gitignore && git commit -m "chore: ignora node_modules"
git statusOn branch main Changes not staged for commit: modified: node_modules/marked/package.json modified: node_modules/.package-lock.json ...
Continua sent-hi. El .gitignore no ha fet res, perquè aquells fitxers ja tenen seguiment.
flowchart LR
A["Sense seguiment"] -->|"git add"| B["Preparat"]
B -->|"git commit"| C["Confirmat / amb seguiment"]
A -.->|".gitignore actua AQUÍ<br/>i només aquí"| A
C -->|"git rm --cached"| A
La fletxa de tornada —git rm --cached— és l'única manera de retornar un fitxer a l'estat on .gitignore el pot protegir.
- Treure del seguiment amb
git rm --cached
git rm --cachedgit rm --cached treu el fitxer de l'índex però el deixa al teu disc. A partir d'aquell moment passa a estar sense seguiment, el .gitignore comença a aplicar-s'hi i desapareix de git status.
# Un fitxer
git rm --cached config.js
# Un directori sencer (recursiu)
git rm -r --cached node_modules/
# Comprovar abans de tocar res (--dry-run no modifica res)
git rm -r --cached --dry-run node_modules/El flux complet, tal com el va fer l'Ana:
# 1. Assegurar-se que el patró és al .gitignore
grep -q "^node_modules/" .gitignore || echo "node_modules/" >> .gitignore
# 2. Treure de l'índex, conservant-lo al disc
git rm -r --cached node_modules/
# 3. Verificar que git status queda net
git status --short
# 4. Confirmar
git commit -m "chore: deixa de versionar node_modules"
# 5. Avisar l'equip (veure més avall)
git pushEl truc per netejar-ho tot de cop
Quan hi ha molts fitxers que ara haurien d'estar ignorats, hi ha una recepta que els treu tots alhora respectant el .gitignore actual:
# Buida l'índex i el reconstrueix aplicant el .gitignore vigent
git rm -r --cached .
git add .
git status
git commit -m "chore: aplica el .gitignore als fitxers ja versionats"git rm -r --cached . no esborra res del disc: buida l'índex. git add . el torna a omplir, i aquesta vegada sí que respecta el .gitignore. La diferència entre tots dos estats és exactament el conjunt de fitxers que sobraven.
Comprova sempre amb
git statusabans de confirmar. Si el.gitignoreté un patró massa ampli, aquesta recepta pot treure del seguiment fitxers que sí que volies.
Els dos advertiments importants
Advertiment 1: el fitxer s'esborra a les altres màquines. Per a Git, un git rm --cached seguit de commit és un esborrat. Quan el Bruno i la Carla facin pull, aquell fitxer desapareixerà de la seva còpia de treball. Amb node_modules/ tant se val (es regenera amb npm ci), però si treus del seguiment un config.js que la gent necessita, els l'estàs esborrant. Avisa l'equip abans de fer-ho.
Advertiment 2: no esborra res de l'historial. I aquest és crític:
El fitxer desapareix de la punta, però continua a tots els commits anteriors:
git log --all --oneline -- config.js # allà continuen
git show HEAD~5:config.js # i el seu contingut és accessibleSi config.js contenia una contrasenya, la contrasenya continua al repositori i a tots els clons. git rm no és una eina de seguretat. El procediment correcte per a un secret ja filtrat —rotar primer la credencial, després reescriure l'historial— és el contingut de la lliçó 08-05.
- Depurar amb
git check-ignore -v
git check-ignore -vArriba el dia en què un fitxer s'ignora i no saps per què, o al revés. git check-ignore -v t'ho respon: et diu quin fitxer d'exclusió, quina línia i quin patró ho estan decidint.
Es llegeix: el patró dist/, a la línia 8 del fitxer .gitignore, és el que ignora dist/app.min.js.
Exemples de les tres respostes possibles:
Opcions útils
# Diversos fitxers de cop
git check-ignore -v app.js dist/app.min.js .DS_Store node_modules/marked/index.js
# Mostrar també els que NO s'ignoren, i per què
git check-ignore -v --no-index app.js
# Comprovar TOT el que git status amaga
git status --ignored --shortgit status --ignored mereix un apartat propi, perquè és la manera de descobrir sorpreses:
Cada !! és un fitxer o directori que Git està amagant. Si hi veus alguna cosa que sí que volies versionar, ja saps que un patró és massa ampli.
El cas especial de --no-index
Si un fitxer té seguiment, check-ignore pot resultar confús, perquè els patrons sí que hi encaixen encara que no tinguin efecte. Per preguntar "hi encaixaria algun patró, amb independència de si té seguiment?":
És a dir: hi ha un patró que el cobriria, però com que el fitxer ja és a l'índex, no fa efecte. Aquest diagnòstic —"el patró està bé, el problema és l'apartat 7"— és exactament el que necessites saber.
- Forçar amb
git add -f
git add -fDe vegades cal versionar un fitxer que un patró ampli està ignorant. git add -f (o --force) se salta les regles per a aquella operació concreta:
Sense -f, Git avisa i no fa res:
The following paths are ignored by one of your .gitignore files: dist/app.min.js hint: Use -f if you really want to add them.
Compte amb el que passa després. Un cop afegit amb -f, el fitxer passa a tenir seguiment, i per la regla de l'apartat 7 el .gitignore deixa de tenir-hi efecte per sempre: cada modificació apareixerà a git status. -f no és una excepció puntual, és un canvi d'estat permanent.
Per això gairebé sempre és millor arreglar el patró que forçar:
Així l'excepció queda documentada al repositori en lloc de viure a la memòria de qui un dia va escriure -f.
I un advertiment que enllaça amb la lliçó següent: -f és la via per on es colen els secrets. Algú té pressa, Git es queixa que .env està ignorat, i git add -f .env resol el problema dels propers cinc minuts a canvi de crear-ne un de permanent. Si Git es resisteix a afegir un fitxer, atura't a pensar per què.
- Plantilles per llenguatge
No cal escriure un .gitignore des de zero. Hi ha col·leccions mantingudes per la comunitat amb plantilles per llenguatge, framework, sistema operatiu i editor; la més coneguda és el repositori github/gitignore, i la majoria de plataformes ofereixen triar-ne una en crear el repositori.
Recomanacions d'ús:
- Parteix de la plantilla del teu llenguatge, però llegeix-la. Conté regles per a eines que potser no fas servir, i de vegades ignora coses que tu sí que vols.
- Combina, no concatenis a cegues. Una plantilla de Node més una de Python més tres d'editors produeixen un fitxer de 300 línies que ningú manté.
- Agrupa per seccions amb comentaris, com a l'exemple de l'apartat 2. Un
.gitignorees llegeix moltes vegades. - Les regles de sistema operatiu i editor, al teu global. Si tot l'equip té el global ben configurat, el
.gitignoredel projecte queda curt i significatiu. - Revisa'l quan canviïn les eines. Un
.gitignoreamb regles per a un empaquetador que vau deixar de fer servir fa dos anys és soroll.
Una comprovació útil de tant en tant, per veure quines regles ja no serveixen de res:
# Fitxers ignorats que existeixen realment a la teva còpia de treball
git status --ignored --short | grep '^!!'Si una regla no apareix mai aquí en cap màquina de l'equip, probablement sobra.
- El patró per a secrets:
.env i .env.exemple
.env i .env.exempleEl problema real: l'aplicació necessita saber la URL de la base de dades, la clau de l'API de correu i un secret de sessió. Aquests valors no poden ser al repositori, però qui cloni el projecte ha de saber quins valors necessita i com es diuen.
La solució convencional són dos fitxers:
.env — amb els valors reals. Ignorat, mai versionat.
BASE_DADES_URL=postgres://gestor:una-clau-de-veritat@localhost:5432/tasques
API_CORREU_CLAU=el-valor-real-que-no-va-al-repositori
SESSIO_SECRET=un-altre-valor-real-generat-a-l-atzar
PORT=3000.env.exemple — amb les claus i valors de mostra, sense res real. Versionat.
# Copia aquest fitxer a .env i omple els valors reals.
# El .env MAI no es versiona (veure .gitignore).
# Cadena de connexió a PostgreSQL
BASE_DADES_URL=postgres://usuari:contrasenya@localhost:5432/tasques
# Clau del servei d'enviament de correu.
# Demana-la a l'Ana o genera'n una de proves al plafó del servei.
API_CORREU_CLAU=posa-aqui-la-teva-clau
# Secret de sessió. Genera'n un amb: openssl rand -hex 32
SESSIO_SECRET=canvia-aixo-per-un-valor-aleatori
# Port local del servidor de desenvolupament
PORT=3000I al .gitignore, amb la negació que ja coneixem:
Per què funciona bé:
- El Diego clona el seu fork, copia
.env.exemplea.env, omple els seus valors i arrenca. La documentació de què cal és al repositori, actualitzada, perquè forma part del codi. - Quan algú afegeix una variable nova, la revisió de la PR detecta que falta a
.env.exemple. És un bon candidat a comprovació automàtica al CI. - Els valors de mostra són evidentment falsos (
posa-aqui-la-teva-clau), així que ningú els confon amb reals ni els intenta fer servir.
Els tres errors que arruïnen el patró:
- Posar valors reals a
.env.exemple"perquè sigui més còmode". Llavors el fitxer versionat és el que té el secret, i no has guanyat res. - Escriure
.env*sense la negació. El.env.exempletambé queda ignorat, ningú el puja, i el patró sencer deixa d'existir. - Afegir el
.gitignoremassa tard. Si.envja es va confirmar alguna vegada,git rm --cachedel treu de la punta però no de l'historial (apartat 8). El secret continua sent-hi.
Aquest tercer cas —el secret ja filtrat— té un procediment propi, amb un ordre de passos que importa molt: primer es rota la credencial, després es reescriu l'historial. És el nucli de la lliçó 08-05.
Errors Habituals i Consells
Error 1: creure que .gitignore deixa de seguir un fitxer ja versionat. És el malentès número u. Només actua sobre fitxers sense seguiment; per a la resta, git rm --cached.
Error 2: dades/ quan volies dades/*. Si el directori està ignorat, Git no hi entra i cap negació posterior funciona. Ignora el contingut, no el directori.
Error 3: posar la negació abans del patró. Guanya l'última línia que coincideix. !errors.log seguit de *.log no serveix de res.
Error 4: oblidar la barra inicial. build/ ignora qualsevol build del projecte; /build/ només el de l'arrel. Sol importar més del que sembla.
Error 5: fer servir git add -f com a solució habitual. Converteix el fitxer en seguit per sempre i l'excepció queda sense documentar. Arregla el patró i escriu la negació.
Error 6: .env* sense !.env.exemple. Trenca el patró de secrets sencer.
Error 7: confiar en git rm --cached per eliminar un secret. Només el treu de la punta. L'historial i tots els clons el conserven. Lliçó 08-05.
Error 8: omplir el .gitignore del projecte amb regles personals. .idea/ i *.swp són de la teva màquina; van a core.excludesFile. El .gitignore del projecte hauria de contenir només allò que li passaria a qualsevol.
Consell 1: crea el .gitignore al primer commit. Abans d'instal·lar dependències. És infinitament més barat que netejar-ho després.
Consell 2: git status --ignored de tant en tant. És l'única manera de veure què està amagant Git, i de detectar patrons massa amplis.
Consell 3: git check-ignore -v així que dubtis. Respon en un segon allò que si no es converteix en mitja hora de prova i error.
Consell 4: configura el teu core.excludesFile una vegada a la vida. T'estalvia embrutar tots els repositoris on treballis.
Consell 5: comenta el .gitignore per seccions. I explica els patrons estranys. D'aquí a un any ningú recordarà per què !config/produccio/ajustos.json hi és.
Consell 6: valida .env.exemple al CI. Una comprovació que compari les claus de .env.exemple amb les que el codi llegeix de process.env evita que es desincronitzin.
Exercicis
Exercici 1: el parany de la negació
En un repositori de proves, crea aquesta estructura:
- Escriu un
.gitignoreambdades/i!dades/public.csv. Comprova ambgit statusi ambgit check-ignore -vquepublic.csvcontinua ignorat, i explica per què. - Corregeix-ho perquè
public.csves versioni i tota la resta continuï ignorada. - Ara aconsegueix que a més es versioni
dades/copies/privat-2026.csv, sense versionarprivat.csv. - Verifica cada pas amb
git check-ignore -v.
Exercici 2: netejar un repositori ja contaminat
Simula el desastre del Bruno:
- Crea un repositori, un
app.js, un directorinode_modules/amb tres fitxers, un.envamb una contrasenya fictícia i un.DS_Store. - Confirma-ho tot sense
.gitignore(l'error de partida). - Afegeix ara un
.gitignorecorrecte, amb el patró de secrets de l'apartat 12. - Comprova que
git statuscontinua mostrant els fitxers que haurien d'estar ignorats i explica per què. - Treu-los del seguiment amb la recepta de l'apartat 8, sense esborrar-los del disc.
- Verifica que
git statusqueda net, que els fitxers continuen al teu disc i quegit status --ignoredels mostra com a ignorats. - Demostra que la contrasenya del
.envcontinua accessible a l'historial i digues quina lliçó ho resol.
Exercici 3: els tres nivells
- Configura un
core.excludesFileglobal amb les regles del teu sistema operatiu i el teu editor. - En un repositori de proves, afegeix a
.git/info/excludeun fitxernotes-personals.md. - Afegeix al
.gitignoredel projecte la regladist/. - Crea els tres tipus de fitxer i comprova amb
git check-ignore -vque cadascun s'ignora pel nivell correcte. - Per a cadascun d'aquests fitxers, decideix raonadament a quin nivell hauria d'estar la regla:
node_modules/,.idea/workspace.xml,proves-de-la-carla.js,coverage/,.DS_Store,.env.
Solucions
Solució 1:
mkdir -p /tmp/practica-ignore/dades/copies && cd /tmp/practica-ignore
git init -b main
echo "a,b" > dades/public.csv
echo "clau,valor" > dades/privat.csv
echo "hist" > dades/copies/privat-2026.csv# 1. La versió que NO funciona
cat > .gitignore <<'FI'
dades/
!dades/public.csv
FI
git status --shortdades/public.csv no apareix: continua ignorat.
El diagnòstic és explícit: qui decideix és la línia 1, amb el patró dades/. La negació de la línia 2 no s'avalua mai, perquè Git, en ignorar el directori dades/, no hi entra. És una optimització deliberada de rendiment.
# 2. La versió correcta: ignorar el contingut, no el directori
cat > .gitignore <<'FI'
dades/*
!dades/public.csv
FI
git status --shortgit check-ignore -v dades/public.csv # sense sortida, codi 1: NO ignorat
git check-ignore -v dades/privat.csv# 3. Rescatar també un fitxer d'un subdirectori
cat > .gitignore <<'FI'
dades/*
!dades/public.csv
!dades/copies/
dades/copies/*
!dades/copies/privat-2026.csv
FI
git status --shortLa clau del pas 3: cal des-ignorar el directori intermedi (!dades/copies/) perquè Git hi entri, i després tornar a ignorar-ne el contingut (dades/copies/*) per poder rescatar només allò que interessa. Cada nivell de profunditat exigeix el seu propi parell de línies.
Solució 2:
mkdir -p /tmp/practica-neteja/node_modules/marked && cd /tmp/practica-neteja
git init -b main
echo "console.log('gestor-tasques');" > app.js
echo '{"name":"marked"}' > node_modules/marked/package.json
echo "modul" > node_modules/marked/index.js
echo "{}" > node_modules/.package-lock.json
echo "BASE_DADES_URL=postgres://gestor:clau-ficticia@localhost/tasques" > .env
touch .DS_Store.DS_Store .env app.js node_modules/.package-lock.json node_modules/marked/index.js node_modules/marked/package.json
# 3. El .gitignore que hauria d'haver existit des del principi
cat > .gitignore <<'FI'
node_modules/
.env
.env.*
!.env.exemple
.DS_Store
FI
cat > .env.exemple <<'FI'
# Copia a .env i omple els valors reals.
BASE_DADES_URL=postgres://usuari:contrasenya@localhost:5432/tasques
FIContinua apareixent perquè ja té seguiment. .gitignore només actua a la frontera "sense seguiment → preparat", i aquests fitxers la van creuar al pas 2.
D .DS_Store D .env A .env.exemple A .gitignore D node_modules/.package-lock.json D node_modules/marked/index.js D node_modules/marked/package.json
Les D són exactament els fitxers que sobraven. app.js no apareix perquè el seu contingut no ha canviat.
# 6. Verificació
git status --short # buit
ls -a # .env, .DS_Store i node_modules/ CONTINUEN al disc
git status --ignored --shortAllà és. Qualsevol amb accés al repositori, ara o d'aquí a cinc anys, la pot recuperar amb una sola ordre, i tots els clons existents la contenen. git rm --cached protegeix el futur, no repara el passat. El procediment correcte —rotar primer la credencial i després reescriure l'historial amb git filter-repo— és el contingut de la lliçó 08-05.
Solució 3:
# 1. Global
cat > ~/.gitignore_global <<'FI'
.DS_Store
Thumbs.db
.idea/
*.swp
*~
FI
git config --global core.excludesFile ~/.gitignore_global# 2 i 3. Local del repositori i del projecte
mkdir /tmp/practica-nivells && cd /tmp/practica-nivells && git init -b main
echo "notes-personals.md" >> .git/info/exclude
echo "dist/" > .gitignore# 4. Un fitxer de cada tipus
mkdir dist && touch dist/app.min.js notes-personals.md .DS_Store app.js
git check-ignore -v dist/app.min.js notes-personals.md .DS_Store app.js.gitignore:1:dist/ dist/app.min.js .git/info/exclude:6:notes-personals.md notes-personals.md /home/ana/.gitignore_global:1:.DS_Store .DS_Store
app.js no apareix: no està ignorat per cap regla. Cadascun dels altres tres s'ignora exactament pel nivell que li correspon.
5. On va cada regla:
| Fitxer | Nivell correcte | Raó |
|---|---|---|
node_modules/ |
Projecte | És del projecte: li apareixerà a tothom qui executi npm install. Sense ell, qualsevol el pot pujar. |
.idea/workspace.xml |
Global | És del teu IDE, no del projecte. L'Ana amb Vim i el Bruno amb VS Code no el generen mai. (Molts projectes el dupliquen al .gitignore com a xarxa de seguretat; és una decisió defensiva legítima.) |
proves-de-la-carla.js |
.git/info/exclude |
És de la Carla i només d'aquest repositori. No té per què aparèixer al fitxer compartit ni justificar-se en una revisió. |
coverage/ |
Projecte | El genera l'eina de proves del projecte a la màquina de qualsevol. |
.DS_Store |
Global | El genera macOS, no el projecte. Que el Bruno el tingui al seu global protegeix tothom; posar-lo també al projecte és xarxa de seguretat. |
.env |
Projecte, sense cap dubte | És la regla de seguretat més important del repositori i no pot dependre que cada persona tingui ben configurat el seu global. Va al .gitignore versionat, sempre. |
L'última fila és el criteri de fons: com més gran sigui la conseqüència que la regla falti, més amunt ha d'estar i més versionada.
Conclusió
L'essencial d'aquesta lliçó:
- Es versiona la font, no el resultat. A fora queden dependències, artefactes de compilació, fitxers del sistema operatiu, configuració personal de l'editor, registres, temporals, binaris grans i —per damunt de tot— secrets. A dins es queden els fitxers de bloqueig de dependències i la configuració compartida del projecte.
- La sintaxi té tres regles que cal interioritzar: la barra final significa "només directoris", la barra inicial ancora a l'arrel, i qualsevol barra al mig del patró també ancora. Dins d'un fitxer, guanya l'última línia que coincideix.
- La negació amb
!té un límit dur: no es pot des-ignorar un fitxer dins d'un directori ignorat, perquè Git ni tan sols hi entra. S'ignora el contingut (dades/*), no el directori (dades/). - Hi ha tres nivells: el
.gitignoredel projecte (versionat, per a allò que li passa a qualsevol),.git/info/exclude(local, per a les teves coses en aquell repositori) icore.excludesFile(global, per a allò que genera el teu sistema operatiu o el teu editor). Com més greu sigui la conseqüència que falti una regla, més amunt i més versionada ha d'estar. - La regla que confon tothom:
.gitignorenomés afecta els fitxers sense seguiment. Sobre allò ja versionat no té cap efecte. La tornada enrere ésgit rm --cached, que treu de l'índex però conserva al disc, amb dos advertiments: esborra el fitxer a les altres màquines en ferpull, i no toca l'historial. git check-ignore -vrespon en un segon quin fitxer, quina línia i quin patró decideixen.git status --ignoredmostra allò que Git t'està amagant.git add -fforça, però converteix el fitxer en seguit per sempre. Gairebé sempre és millor escriure l'excepció com a negació, perquè quedi documentada.- El patró per a secrets és
.envignorat +.env.exempleversionat amb claus i valors evidentment falsos, més la negació!.env.exemple. És la millor documentació possible de quina configuració necessita el projecte.
gestor-tasques ja sap quins fitxers no han d'entrar. Queda l'altre costat del problema: els fitxers que sí que entren, però que Git no hauria de tractar com si tots fossin iguals. Un PNG no és un fitxer de text i no té sentit intentar fusionar-lo. Un CHANGELOG.md sí que voldria fusionar-se d'una manera especial. I, sobretot, hi ha el problema que la Carla arrossega des del mòdul 1: cada vegada que edita un fitxer a Windows, el git diff marca totes les línies com a modificades.
Aquest és el terreny de la lliçó 08-04: Atributs de Fitxer amb .gitattributes.
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ó
