Les cinc lliçons anteriors resolien problemes amb nom: he perdut commits, la meva branca ha divergit, el repositori és corrupte. Aquesta s'ocupa de la categoria que no té nom i que, a la pràctica, és la més freqüent:

Git està fent alguna cosa que no entenc, i no sé ni per on començar a mirar.

Un fitxer que apareix com a modificat i ningú no l'ha tocat. Un .gitignore que ignora el que no ha d'ignorar. Un push que triga quaranta segons en un projecte petit. Un hook que no s'executa. Una configuració que diu una cosa i un comportament que en diu una altra. Un git log que amaga commits que existeixen.

No són avaries: són desajustos entre el que et penses que hi ha i el que hi ha de debò. I per a això Git té una caixa d'eines que amb prou feines hem fregat: variables de traça que mostren el que passa per sota, ordres que diuen d'on surt cada ajust, i la capa de lampisteria (plumbing) que consulta la base de dades sense interpretacions.

Més important que les eines és el mètode, perquè sense ell les eines produeixen soroll. Tancarem amb un mètode de diagnòstic en quatre passos i amb una investigació real sobre gestor-tasques que combina bisect i blame per anar del símptoma al commit, i del commit a la línia i el seu perquè.

Contingut

  1. El mètode de diagnòstic, en quatre passos
  2. Variables de traça: veure el que Git fa per sota
  3. Depurar la configuració efectiva
  4. Depurar rutes i patrons: check-ignore, check-attr, ls-files
  5. Lampisteria per inspeccionar el graf
  6. Acotar amb git diff --stat i git log
  7. Una investigació real: de bisect a blame
  8. Un catàleg de misteris i les seves ordres

  1. El mètode de diagnòstic, en quatre passos

Abans de les eines, el procediment. És el que separa una investigació de vint minuts d'una tarda perduda.

flowchart TD
    A["1. REPRODUIR<br/>L'ordre mínima que provoca el símptoma"]
    B["2. AÏLLAR<br/>Treure variables: repo net, sense config,<br/>sense hooks, una altra màquina"]
    C["3. CONSULTAR L'ESTAT REAL<br/>No el recordat. Lampisteria i traces"]
    D["4. COMPROVAR LA HIPÒTESI<br/>Una predicció concreta i falsable"]
    A --> B --> C --> D
    D -->|"No es compleix"| C
    D -->|"Es compleix"| E["Arreglar la causa,<br/>no el símptoma"]

Pas 1: reproduir

Redueix el símptoma a l'ordre mínima que el provoca.

# Malament: "el push falla"
# Bé:
git push origin GT-241

Si no ho pots reproduir a voluntat, no pots verificar que ho has arreglat. I moltes vegades reduir el cas ja revela la causa: «només falla amb aquesta branca», «només amb aquest fitxer», «només des del portàtil de la Carla».

Pas 2: aïllar

Treu variables una a una:

# Sense configuració global ni de sistema
GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null git status

# Sense hooks (lliçó 06-01)
git -c core.hooksPath=/dev/null commit -m "prova"
git commit --no-verify -m "prova"

# Sense àlies ni configuració del repositori
git -c include.path= <ordre>

# En un repositori acabat de clonar, a /tmp
git clone <url> /tmp/prova-neta && cd /tmp/prova-neta

Si el problema desapareix sense la configuració global, ja saps on mirar. Si persisteix en un clon net, el problema és al repositori o al servidor, no a la teva màquina.

Pas 3: consultar l'estat real, no el recordat

Aquest és el pas que més vegades es passa per alt i el que més vegades resol.

No preguntis... Pregunta...
«Jo em penso que aquell fitxer està ignorat» git check-ignore -v <fitxer>
«Tinc user.email ben configurat» git config --list --show-origin --show-scope
«Aquell fitxer és a l'índex» git ls-files --stage <fitxer>
«Aquella branca apunta a aquell commit» git rev-parse <branca>
«El fitxer és text normal» git ls-files --eol <fitxer>
«El remot és en aquell commit» git ls-remote origin <branca>
«Aquell hook s'està executant» GIT_TRACE=1 git commit ...

La memòria i la documentació menteixen; les ordres no.

Pas 4: comprovar la hipòtesi

Formula una predicció concreta i falsable abans de tocar res:

«Si la causa és que .gitattributes marca *.css com a text eol=crlf, aleshores git check-attr eol estils.css ha de dir crlf, i git ls-files --eol estils.css ha de mostrar w/crlf

Executa, comprova, i només llavors arregla. Si la predicció falla, la hipòtesi era dolenta: torna al pas 3. Canviar coses a l'atzar fins que «funciona» deixa el problema latent i t'impedeix saber què el causava.

  1. Variables de traça: veure el que Git fa per sota

Git té un sistema de traces que s'activa amb variables d'entorn. Són la manera més directa de veure què està passant realment.

Variable Què mostra Quan fer-la servir
GIT_TRACE=1 Cada subordre que Git executa, amb els seus arguments Àlies estranys, hooks, ordres que en criden d'altres
GIT_TRACE_SETUP=1 Com Git localitza el repositori: .git, arrel, prefix «No és un repositori», worktrees, submòduls
GIT_TRACE_PERFORMANCE=1 Temps per etapa Ordres lentes (08-06)
GIT_TRACE_PACKET=1 Cada paquet del protocol amb el servidor fetch/push que fallen o van lents
GIT_TRACE_PACK_ACCESS=1 Accessos a packfiles Rendiment de la base d'objectes
GIT_CURL_VERBOSE=1 Diàleg HTTP complet Problemes amb remots HTTPS
GIT_SSH_COMMAND="ssh -v" Diàleg SSH complet Permission denied (publickey)
GIT_TRACE2_PERF=1 Traces modernes, estructurades Anàlisi fina
GIT_TRACE_SHALLOW=1 Lògica de clons superficials --depth (10-04)

Totes accepten una ruta absoluta en lloc d'1, per escriure a fitxer:

GIT_TRACE=/tmp/traces.log git status

GIT_TRACE: què està executant Git realment

GIT_TRACE=1 git status
10:14:22.104 git.c:463               trace: built-in: git status
10:14:22.108 run-command.c:657       trace: run_command: 'gpg' '--status-fd=2' '-bsau' '[email protected]'

Allà es veu, per exemple, si Git està cridant gpg (signatura de commits, lliçó 08-05), un hook, o un credential helper.

És especialment útil amb àlies que fan coses estranyes (lliçó 06-04):

GIT_TRACE=1 git lg
10:15:03.221 git.c:750               trace: alias expansion: lg => 'log' '--graph' '--abbrev-commit' '--date=relative'
10:15:03.222 git.c:463               trace: built-in: git log --graph --abbrev-commit --date=relative

I amb hooks que no s'executen:

GIT_TRACE=1 git commit -m "prova" 2>&1 | grep -i hook

Si no apareix res, el hook no s'està llançant. Causes habituals: no és executable (chmod +x), core.hooksPath apunta a un altre lloc, o el nom del fitxer té extensió.

ls -l .git/hooks/pre-commit
git config --get core.hooksPath

GIT_TRACE_SETUP: on es pensa Git que és

GIT_TRACE_SETUP=1 git status
10:16:41.003 trace.c:318  setup: git_dir: /home/ana/projectes/gestor-tasques/.git
10:16:41.003 trace.c:319  setup: git_common_dir: /home/ana/projectes/gestor-tasques/.git
10:16:41.003 trace.c:320  setup: worktree: /home/ana/projectes/gestor-tasques
10:16:41.003 trace.c:321  setup: cwd: /home/ana/projectes/gestor-tasques/src
10:16:41.003 trace.c:322  setup: prefix: src/

Resol una família sencera de misteris:

  • «No és un repositori Git» estant dins d'un: et diu quin .git troba (o que no en troba cap).
  • Treballes en un repositori que no és el que et penses: típic amb submòduls (lliçó 06-05) i worktrees (06-06), on git_dir i git_common_dir difereixen.
  • Els patrons de .gitignore no funcionen com esperes: el prefix explica des d'on s'interpreten les rutes.

GIT_SSH_COMMAND i GIT_CURL_VERBOSE: problemes de xarxa

Reprenent l'apartat 5.8 de la lliçó 09-01, aquí hi ha la versió completa.

GIT_SSH_COMMAND="ssh -v" git fetch 2>&1 | head -40
debug1: Reading configuration data /home/ana/.ssh/config
debug1: /home/ana/.ssh/config line 4: Applying options for git.exemple.cat
debug1: Connecting to git.exemple.cat [192.0.2.10] port 22.
debug1: Offering public key: /home/ana/.ssh/id_rsa RSA SHA256:xxxx agent
debug1: Authentications that can continue: publickey
debug1: Offering public key: /home/ana/.ssh/id_ed25519 ED25519 SHA256:yyyy agent
debug1: Server accepts key: /home/ana/.ssh/id_ed25519 ED25519 SHA256:yyyy agent
debug1: Authentication succeeded (publickey).

Cada línia és diagnòstic:

Línia Què et diu
Reading configuration data ~/.ssh/config Quin fitxer de configuració s'aplica
Applying options for <host> Quin bloc Host ha coincidit
Connecting to <ip> port 22 On va realment (és l'amfitrió correcte?)
Offering public key: <ruta> Quines claus ofereix i en quin ordre
Server accepts key Quina ha acceptat
Authentications that can continue: publickey Ha rebutjat l'anterior i continua provant

El cas clàssic: tens diverses claus, SSH ofereix l'equivocada primer i el servidor talla després de N intents. La solució és un bloc a ~/.ssh/config:

Host git.exemple.cat
    User git
    IdentityFile ~/.ssh/id_ed25519_exemple
    IdentitiesOnly yes

IdentitiesOnly yes és la clau: força a oferir només aquella.

Per a HTTPS:

GIT_CURL_VERBOSE=1 git fetch 2>&1 | head -40
* Connected to git.exemple.cat (192.0.2.10) port 443
> GET /equip/gestor-tasques.git/info/refs?service=git-upload-pack HTTP/2
> User-Agent: git/2.45.0
< HTTP/2 401
< www-authenticate: Basic realm="Git"
* Issue another request to this URL
> Authorization: Basic YW5hOnh4eA==
< HTTP/2 200

Diagnostica servidors intermediaris corporatius, certificats, redireccions i credencials. Un 401 persistent apunta al credential helper (lliçó 04-03):

git config --get credential.helper
git credential-cache exit          # buidar credencials a la memòria cau
printf 'protocol=https\nhost=git.exemple.cat\n\n' | git credential fill

Compte: GIT_CURL_VERBOSE pot mostrar capçaleres Authorization. No enganxis la seva sortida en un tiquet públic sense revisar-la (lliçó 08-05).

GIT_TRACE_PACKET: el protocol, paquet a paquet

El nivell més profund. Mostra el diàleg exacte amb el servidor.

GIT_TRACE_PACKET=1 git fetch 2>&1 | head -30
packet:        git< version 2
packet:        git< agent=git/2.45.0
packet:        git< ls-refs=unborn
packet:        git< fetch=shallow wait-for-done
packet:        git< object-format=sha1
packet:        git> command=ls-refs
packet:        git> peel
packet:        git> ref-prefix refs/heads/
packet:        git< b52c9d1e4a7f3c8b6d2e9a5f1c7b3d8e4a6f2c9b refs/heads/main
packet:        git< 7d3a8f4a2c6e9b1d5f3a8c6e2b9d4f7a1c5e8b3d refs/heads/GT-241

Per a què serveix de debò:

  • Veure quines referències anuncia el servidor i quina versió del protocol es negocia.
  • Diagnosticar un fetch lent: si el servidor anuncia 40.000 referències, aquí hi ha el problema (lliçó 08-06, higiene de referències).
  • Entendre un push rebutjat per un hook de servidor (lliçó 07-06): el missatge del hook viatja en aquests paquets.
  • Comprovar si es fa servir el protocol v2, molt més eficient:
git config --get protocol.version      # hauria de ser 2 a Git modern
git -c protocol.version=2 fetch

Traces per a totes les ordres, temporalment

# A la sessió actual
export GIT_TRACE=1
export GIT_TRACE_SETUP=1
# ...reproduir el problema...
unset GIT_TRACE GIT_TRACE_SETUP

I un guió per capturar-ho tot de cop quan cal enviar un informe:

#!/usr/bin/env bash
# capturar-traces.sh <ordre git...>
LOG=/tmp/git-traces-$(date +%s).log
GIT_TRACE="$LOG" \
GIT_TRACE_SETUP="$LOG" \
GIT_TRACE_PERFORMANCE="$LOG" \
GIT_TRACE_PACKET="$LOG" \
  "$@"
echo "Traça a: $LOG"
echo "REVISA el fitxer abans de compartir-lo: pot contenir credencials."

  1. Depurar la configuració efectiva

Reprenent la lliçó 01-05. Git llegeix la configuració de diversos llocs i l'últim guanya. Quan alguna cosa es comporta de manera inesperada, la configuració és sospitosa número u.

git config --list --show-origin --show-scope
system  /etc/gitconfig          core.autocrlf=false
global  /home/ana/.gitconfig    user.name=Ana Ferrer
global  /home/ana/.gitconfig    [email protected]
global  /home/ana/.gitconfig    pull.rebase=true
local   .git/config             remote.origin.url=git.exemple.cat:equip/gestor-tasques.git
local   .git/config             [email protected]
local   .git/config             core.autocrlf=input

Dues columnes noves respecte del --list de sempre:

  • --show-scope: en quin nivell és (system, global, local, worktree, command).
  • --show-origin: el fitxer exacte, inclosos els que vénen per include.path.

A l'exemple, user.email apareix dues vegades: el local guanya. Aquí hi ha l'explicació del «he configurat bé el correu i els commits surten amb un altre» (lliçó 09-01, apartat 5.3).

El valor efectiu d'un ajust

# Quin valor guanya
git config --get user.email

# TOTS els valors definits, en ordre de precedència (l'últim guanya)
git config --get-all user.email

# Amb el seu origen
git config --get-all --show-origin user.email

# Tot el que comenci per un prefix
git config --get-regexp '^remote\.'
git config --get-regexp '^alias\.'
git config --get-regexp '^advice\.'

Aquest últim mereix atenció: si algú va copiar una configuració que silencia els advice.*, estàs perdent els suggeriments que resolen la meitat dels problemes del mòdul (lliçó 09-01, apartat 6).

Precedència i anul·lacions

Nivell Fitxer Prioritat
system /etc/gitconfig Més baixa
global ~/.gitconfig o ~/.config/git/config Mitjana
local .git/config Alta
worktree .git/config.worktree Més alta (si extensions.worktreeConfig)
-c a la línia d'ordres Màxima
Variables GIT_* Entorn Depèn de l'ajust
# Provar un valor sense canviar res, només per a aquesta ordre
git -c core.autocrlf=false status
git -c diff.noprefix=false diff

# Veure què passa SENSE configuració d'usuari
GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null git status

L'última és la prova definitiva de «és cosa de la meva configuració?».

Configuració condicional, la font de sorpreses

# ~/.gitconfig
[includeIf "gitdir:~/projectes/feina/"]
    path = ~/.gitconfig-feina

[includeIf "gitdir:~/projectes/personal/"]
    path = ~/.gitconfig-personal

És una funcionalitat excel·lent per separar identitats, però produeix el desconcert d'«aquí funciona i allà no». --show-origin ho desembolica en un segon, perquè anomena el fitxer inclòs.

I ull amb la barra final: gitdir:~/projectes/feina/ (amb /) coincideix amb el directori i els seus descendents; sense ella, el comportament canvia.

  1. Depurar rutes i patrons: check-ignore, check-attr, ls-files

Aquí viuen els misteris més freqüents del dia a dia.

git check-ignore -v: per què s'ignora (o no) un fitxer

Reprenent la lliçó 08-03:

git check-ignore -v dades/bolcat.sql
.gitignore:12:*.sql	dades/bolcat.sql

Lectura: fitxer de regles : línia : patró que decideix, i el fitxer afectat. No hi ha cap ambigüitat possible.

# Diversos fitxers alhora
git check-ignore -v app.js dades/bolcat.sql node_modules/x/y.js

# Tot el que s'està ignorant al projecte
git status --ignored --short

# I el cas desconcertant: per què NO s'ignora?
git check-ignore -v --no-index config-local.json
echo $?

Si check-ignore no torna res i el codi de sortida és 1, cap regla no l'ignora. I si el fitxer apareix igualment a git status tot i estar ignorat, la causa és gairebé sempre la mateixa:

git ls-files --error-unmatch config-local.json
config-local.json

És a l'índex. .gitignore només afecta els fitxers sense seguiment; un que ja té seguiment es continua vigilant encara que el patró coincideixi. La solució és la de la lliçó 08-03:

git rm --cached config-local.json
git commit -m "chore: deixa de versionar la configuració local"

Altres causes menys evidents, que check-ignore -v revela en anomenar el fitxer de regles:

Fitxer que pot estar decidint On viu
.gitignore del repositori En qualsevol directori del projecte
.git/info/exclude Local, no versionat
core.excludesFile Global, típicament ~/.gitignore_global
Un .gitignore d'un directori pare Un de més específic guanya
Una regla de negació !patró Reactiva alguna cosa ignorada abans

I el parany clàssic dels directoris:

git check-ignore -v captures/pantalla.png
.gitignore:8:captures/	captures/pantalla.png

Si un directori està ignorat, Git ni tan sols hi entra, així que una regla de negació per a un fitxer de dins no funciona:

captures/
!captures/important.png      # NO funciona: Git no entra a captures/
captures/*
!captures/important.png      # SÍ que funciona

git check-attr: quins atributs s'apliquen

Reprenent la lliçó 08-04:

git check-attr -a estils.css
estils.css: text: set
estils.css: eol: lf
estils.css: diff: css
# Un atribut concret
git check-attr eol text diff -- estils.css index.html app.js

# D'on surt cada regla
git check-attr -a --source=HEAD estils.css

# Per a tots els fitxers seguits
git ls-files | git check-attr --stdin -a | grep -v unspecified

Això explica de cop una família de misteris:

Símptoma Ordre Causa habitual
El diff d'un fitxer surt com a binari git check-attr diff -- <f> -diff o binary a .gitattributes
Un fitxer no es fusiona mai git check-attr merge -- <f> merge=binary o merge=ours
Els finals de línia canvien sols git check-attr text eol -- <f> text, eol=crlf, o core.autocrlf
Un fitxer es transforma en confirmar git check-attr filter -- <f> Un filter (LFS, clean/smudge)
git blame dona resultats estranys .git-blame-ignore-revs (06-03)

git ls-files: què hi ha a l'índex de debò

git status interpreta; git ls-files mostra. És la finestra directa a l'índex.

# El que té seguiment
git ls-files

# Amb metadades: mode, hash i etapa
git ls-files --stage app.js
100644 6f2b9d4a8c1e5f3b7d9a2c6e4f8b1d3a5c7e9f2b 0	app.js
Camp Significat
100644 Fitxer normal. 100755 = executable. 120000 = enllaç simbòlic. 160000 = submòdul
Hash El blob que hi ha a l'índex
0 L'etapa. 0 = normal. 1/2/3 = conflicte (lliçó 03-05)

Aquest últim camp és or durant un conflicte:

git ls-files --stage | awk '$3 != 0'
100644 4f8a2e6... 1	app.js
100644 b52c9d1... 2	app.js
100644 7d3a8f4... 3	app.js

Tres etapes: base comuna, la nostra i la seva. Exactament el que vam veure a la lliçó 03-05, ara visible en cru.

Els modes que resolen misteris:

# Per què Git diu que aquest script ha canviat si només li he donat permisos?
git ls-files --stage build.sh
100644 8a1f6c3... 0	build.sh
ls -l build.sh
-rwxr-xr-x 1 ana ana 412 jul 28 10:02 build.sh

Índex 100644, disc executable: aquest és el canvi. Es corregeix amb:

git update-index --chmod=+x build.sh

I el mode --eol, que resol el misteri dels finals de línia:

git ls-files --eol app.js estils.css index.html
i/lf    w/crlf  attr/text=auto  	app.js
i/lf    w/lf    attr/            	estils.css
i/crlf  w/crlf  attr/-text       	index.html
Columna Significat
i/ Com està a l'índex (el que es desa al repositori)
w/ Com està al directori de treball (al disc)
attr/ Quin atribut se li aplica

La primera línia és el cas sa de la Carla a Windows: LF al repositori, CRLF al seu disc (lliçó 08-04). La tercera és un problema: i/crlf significa que s'han desat CRLF dins del repositori, que és justament el que no es vol.

Altres modes útils:

git ls-files --others                        # sense seguiment
git ls-files --others --exclude-standard      # sense seguiment i no ignorats
git ls-files --ignored --exclude-standard     # ignorats
git ls-files --deleted                        # esborrats del disc però encara a l'índex
git ls-files --modified                       # modificats
git ls-files --unmerged                       # en conflicte

--others --exclude-standard és exactament la llista de «fitxers nous» que mostra git status, sense la resta de la sortida. I --deleted explica el «he esborrat el fitxer i Git continua queixant-se».

  1. Lampisteria per inspeccionar el graf

Quan la pregunta és sobre l'estructura del repositori, la resposta és a la capa de lampisteria que vam veure a la lliçó 01-04.

git rev-parse: traduir qualsevol cosa a un hash

git rev-parse HEAD
git rev-parse main
git rev-parse HEAD~3
git rev-parse GT-241@{2}
git rev-parse v1.5.0^{commit}      # l'etiqueta anotada apunta a un tag; això dona el commit

I els seus modes informatius, que resolen preguntes d'entorn:

git rev-parse --show-toplevel        # arrel del projecte
git rev-parse --git-dir              # on és el .git
git rev-parse --git-common-dir       # el .git compartit (worktrees, 06-06)
git rev-parse --abbrev-ref HEAD      # nom de la branca actual
git rev-parse --is-inside-work-tree  # true/false
git rev-parse --is-bare-repository   # true/false
git rev-parse --symbolic-full-name @{u}   # l'upstream complet
/home/ana/projectes/gestor-tasques
/home/ana/projectes/gestor-tasques/.git
GT-241
true
refs/remotes/origin/GT-241

Són la base de qualsevol guió que hagi de treballar amb repositoris de manera robusta.

git rev-list: comptar i llistar commits

# Quants commits hi ha
git rev-list --count HEAD

# Quants per davant i per darrere (lliçó 09-03)
git rev-list --left-right --count main...origin/main

# Commits que van tocar un fitxer
git rev-list HEAD -- app.js | head

# Tots els objectes assolibles (lliçó 08-06)
git rev-list --objects --all | wc -l

# Els commits arrel (n'hi ha més d'un? → històries no relacionades)
git rev-list --max-parents=0 HEAD

# Només fusions, o només el que no són fusions
git rev-list --merges HEAD | head
git rev-list --no-merges HEAD | head

Aquest --max-parents=0 és el diagnòstic exacte de les unrelated histories de la lliçó 09-03: si torna dos hashos, hi ha dues arrels.

git cat-file --batch-check: inspecció massiva

# Tipus i mida d'un objecte
echo 4f8a2e6 | git cat-file --batch-check
4f8a2e6c9b1d5e3a7f2c8b6d9e4a1f5c3b7d2e9a commit 241
# Comprovar si existeix (sense bolcar contingut)
git cat-file -e 4f8a2e6 && echo "existeix" || echo "no existeix"

# Un informe de tots els objectes per tipus
git rev-list --objects --all \
  | git cat-file --batch-check='%(objecttype)' \
  | sort | uniq -c
  18432 blob
   3204 commit
  21908 tree
     47 tag

És la mateixa tècnica de l'anàlisi de mides de la lliçó 08-06, aplicada aquí a entendre la composició del repositori.

git verify-pack: què hi ha dins d'un packfile

git verify-pack -v .git/objects/pack/pack-*.idx | head -5
b52c9d1e4a7f3c8b6d2e9a5f1c7b3d8e4a6f2c9b commit 241 168 12
7d3a8f4a2c6e9b1d5f3a8c6e2b9d4f7a1c5e8b3d tree   118  94 180
4f8a2e6c9b1d5e3a7f2c8b6d9e4a1f5c3b7d2e9a blob  8241 2104 274
6f2b9d4a8c1e5f3b7d9a2c6e4f8b1d3a5c7e9f2b blob   412  87 2378 1 4f8a2e6c9b1d5e3a

Columnes: hash, tipus, mida real, mida comprimida, posició al pack i —a l'última línia— profunditat de delta i objecte base. És la vista de baix nivell del que explicava la lliçó 08-06: l'última entrada es desa com a diferència d'una altra.

# Estadístiques de les cadenes de delta
git verify-pack -v .git/objects/pack/pack-*.idx | tail -20

git for-each-ref: totes les referències amb les seves dades

git for-each-ref --sort=-committerdate --format='%(refname:short)  %(objectname:short)  %(committerdate:relative)  %(authorname)' refs/heads
GT-241     9c4e7b2  fa 2 hores      Ana Ferrer
GT-238     7d3a8f4  fa 3 dies       Bruno Salas
main       b52c9d1  fa 4 dies       Carla Vidal
# Branques sense upstream: existeixen només en aquesta màquina (lliçó 09-05)
git for-each-ref --format='%(refname:short) %(upstream)' refs/heads | awk '$2==""{print $1}'

# Referències que apunten a objectes inexistents (lliçó 09-05)
git for-each-ref --format='%(refname) %(objectname)' | while read -r r s; do
  git cat-file -e "$s" 2>/dev/null || echo "TRENCADA: $r"
done

git ls-remote: què hi ha al servidor, sense baixar res

git ls-remote origin
git ls-remote origin 'refs/heads/GT-*'
git ls-remote --heads origin main
b52c9d1e4a7f3c8b6d2e9a5f1c7b3d8e4a6f2c9b	refs/heads/main

Consulta el servidor directament, sense fetch i sense tocar el teu repositori. Resol preguntes com «existeix aquella branca al servidor?» o «el meu origin/main està al dia?»:

[ "$(git ls-remote origin main | cut -f1)" = "$(git rev-parse origin/main)" ] \
  && echo "al dia" || echo "la meva còpia d'origin/main està desactualitzada"

  1. Acotar amb git diff --stat i git log

Abans d'una investigació fina, convé acotar. Aquestes són les eines de gra gruixut.

# Què va canviar entre dues versions, per fitxer
git diff --stat v1.4.0 v1.5.0
 app.js         |  84 ++++++++++++++++-----
 estils.css     |  12 ++--
 index.html     |   6 +-
 README.md      |  31 ++++++++
 4 files changed, 108 insertions(+), 25 deletions(-)
# Només els noms, per veure l'abast d'un cop d'ull
git diff --name-only v1.4.0 v1.5.0

# Amb el tipus de canvi: Afegit, Modificat, Esborrat, Reanomenat
git diff --name-status v1.4.0 v1.5.0

# Resum de reanomenaments i canvis de mode
git diff --summary v1.4.0 v1.5.0

# Comparar només l'efecte d'una branca (lliçó 07-02)
git diff --stat main...GT-241

I git log en mode investigació (lliçó 06-04):

# Commits que van tocar una funció concreta
git log -L :calculaPendents:app.js

# Commits que van afegir o treure una cadena (pickaxe, lliçó 02-06)
git log -S "localStorage" --oneline

# Commits el diff dels quals coincideix amb una expressió regular
git log -G "gestor\.tasques\.v[0-9]" --oneline

# Qui i quan, en un rang de dates
git log --since="2026-07-01" --until="2026-07-31" --format='%h %an %ad %s' --date=short

# Només el que va entrar per la primera línia de pares (lliçó 08-02)
git log --first-parent --oneline main

-L :funció:fitxer és especialment potent i poc coneguda: segueix una funció concreta al llarg de l'historial, fins i tot quan es mou dins del fitxer.

  1. Una investigació real: de bisect a blame

Ho ajuntarem tot en un cas complet sobre gestor-tasques.

El símptoma. La Carla informa: «El comptador de tasques pendents mostra un número de més quan hi ha tasques ocultes pel filtre. A la versió 1.4 funcionava.»

Pas 1: reproduir

El mínim que provoca la fallada, i escrit com una prova automatitzable:

cat > /tmp/prova-comptador.sh <<'EOF'
#!/usr/bin/env bash
# Surt 0 si el comptador és correcte, 1 si no.
node -e '
  const fs = require("fs");
  const src = fs.readFileSync("app.js", "utf8");
  const ctx = { localStorage: { getItem: () => null, setItem: () => {} }, document: null };
  // ... arrencada mínima del programa ...
  const tasques = [
    { text: "a", completada: false, oculta: false },
    { text: "b", completada: true,  oculta: false },
    { text: "c", completada: false, oculta: true  }
  ];
  const esperat = 2;
  const obtingut = calculaPendents(tasques);
  process.exit(obtingut === esperat ? 0 : 1);
' 2>/dev/null
EOF
chmod +x /tmp/prova-comptador.sh

Una prova automatitzable és el que converteix bisect de tediós en instantani.

Pas 2: acotar el rang

git tag --sort=-creatordate | head -3
git diff --stat v1.4.0 v1.5.0 -- app.js
v1.5.0
v1.4.0
v1.3.0
 app.js | 84 ++++++++++++++++-----
 1 file changed, 62 insertions(+), 22 deletions(-)
git rev-list --count v1.4.0..v1.5.0
34

34 commits entre les dues versions. Revisar-los a mà són hores; bisect són cinc passos.

Pas 3: git bisect run

Reprenent la lliçó 06-02:

git bisect start
git bisect bad v1.5.0
git bisect good v1.4.0
git bisect run /tmp/prova-comptador.sh
Bisecting: 16 revisions left to test after this (roughly 4 steps)
[7d3a8f4a2c6e9b1d5f3a8c6e2b9d4f7a1c5e8b3d] GT-238 extreu la creació de l'element
running  /tmp/prova-comptador.sh
Bisecting: 8 revisions left to test after this (roughly 3 steps)
...
4f8a2e6c9b1d5e3a7f2c8b6d9e4a1f5c3b7d2e9a is the first bad commit
commit 4f8a2e6c9b1d5e3a7f2c8b6d9e4a1f5c3b7d2e9a
Author: Bruno Salas <[email protected]>
Date:   Wed Jul 15 11:23:04 2026 +0200

    GT-231 afegeix el filtre de tasques ocultes

 app.js | 18 ++++++++++++------
 1 file changed, 12 insertions(+), 6 deletions(-)
git bisect reset

Del símptoma al commit en cinc passos automàtics. Ara sabem quan es va trencar.

Pas 4: entendre el commit

git show 4f8a2e6
 function calculaPendents(tasques) {
-  return tasques.filter(t => !t.completada).length;
+  return tasques.filter(t => !t.completada || t.oculta).length;
 }

Aquí està: el || t.oculta compta també les ocultes. Però saber quina línia falla no és saber per què hi és, i arreglar-la sense entendre-ho pot trencar el que aquell commit venia a resoldre.

Pas 5: git blame per al perquè

Reprenent la lliçó 06-03:

git blame -L 12,20 app.js
4f8a2e6c (Bruno Salas  2026-07-15 11:23:04 +0200 12) function calculaPendents(tasques) {
4f8a2e6c (Bruno Salas  2026-07-15 11:23:04 +0200 13)   return tasques.filter(t => !t.completada || t.oculta).length;
4f8a2e6c (Bruno Salas  2026-07-15 11:23:04 +0200 14) }
b52c9d1e (Ana Ferrer   2026-06-28 09:14:22 +0200 15)
b52c9d1e (Ana Ferrer   2026-06-28 09:14:22 +0200 16) function renderitzaTasques() {
# La història completa d'aquella funció, encara que s'hagi mogut
git log -L :calculaPendents:app.js --format='%h %an %ad %s' --date=short
commit 4f8a2e6 Bruno Salas 2026-07-15 GT-231 afegeix el filtre de tasques ocultes
commit 8a1f6c3 Ana Ferrer  2026-05-02 GT-198 extreu el càlcul a la seva pròpia funció
commit 2f8c6e1 Ana Ferrer  2026-04-11 GT-142 mostra el comptador de pendents
# El missatge complet del commit culpable: el «per què»
git log -1 --format=%B 4f8a2e6
GT-231 afegeix el filtre de tasques ocultes

Les tasques arxivades deixen d'aparèixer al llistat. S'afegeix la
marca `oculta` i es filtra a renderitzaTasques().

El comptador ha de continuar incloent-les perquè l'especificació de
GT-231 diu que «pendents» compta totes les tasques no completades,
siguin visibles o no.

Aquí hi ha la troballa, i és el que canvia tot el desenllaç. El comportament no és un error: és una decisió deliberada i documentada de GT-231. El que hi ha és una contradicció entre dues especificacions: GT-231 diu que comptin totes les no completades; la Carla espera que compti només les visibles.

Si haguéssim «arreglat» la línia així que la vam veure, hauríem trencat GT-231 i el cicle hauria tornat a començar d'aquí a dues setmanes.

# Qui més en depèn? (pickaxe, lliçó 02-06)
git log -S "calculaPendents" --oneline
git grep -n "calculaPendents" -- '*.js'

La conclusió de la investigació no és un pedaç, sinó una pregunta per a l'equip: què significa «pendents»? I la resposta, sigui quina sigui, es documenta al commit que la implementi.

El recorregut, en una taula

Pas Pregunta Eina Lliçó
1 Quin és el símptoma exacte? Un guió de prova reproduïble
2 En quin rang buscar? git diff --stat, git rev-list --count 02-05
3 Quin commit el va introduir? git bisect run 06-02
4 Què va canviar aquell commit? git show 02-06
5 Per què hi ha aquella línia? git blame, git log -L, %B 06-03, 08-01
6 Qui en depèn? git log -S, git grep 02-06

bisect et porta del símptoma al commit. blame i el missatge del commit et porten del commit a la intenció. El primer sense el segon produeix pedaços que trenquen una altra cosa. I aquí es veu, retrospectivament, per què la lliçó 08-01 insistia tant a explicar el perquè als missatges: aquell paràgraf del Bruno ha estalviat un error.

  1. Un catàleg de misteris i les seves ordres

La taula de consulta ràpida del «Git fa alguna cosa estranya».

Misteri Ordre de diagnòstic Causa habitual
Un fitxer surt modificat i no l'he tocat git ls-files --eol <f>, git check-attr -a <f> Finals de línia, o filter (08-04)
Un fitxer ignorat apareix a status git ls-files --error-unmatch <f> Ja tenia seguiment (08-03)
Un fitxer no s'ignora encara que és al .gitignore git check-ignore -v <f> Regla en un altre fitxer, o negació mal posada
Un fitxer s'ignora i no hauria git check-ignore -v <f> Un .gitignore d'un directori pare, o el global
Un script perd el permís d'execució git ls-files --stage <f> Mode 100644 a l'índex; update-index --chmod=+x
Un hook no s'executa GIT_TRACE=1 git commit, ls -l .git/hooks/ No és executable, o core.hooksPath (06-01)
L'autor dels commits no és el que espero git config --list --show-origin | grep user Un user.email local trepitjant el global
«No és un repositori Git» estant dins GIT_TRACE_SETUP=1 git status Worktree, submòdul, o .git perdut
push/fetch fallen per autenticació GIT_SSH_COMMAND="ssh -v" git fetch Clau equivocada oferta primer (04-03)
push/fetch van molt lents GIT_TRACE_PACKET=1, git ls-remote | wc -l Milers de referències (08-06)
git status triga segons GIT_TRACE_PERFORMANCE=1 git status Recorregut de fitxers sense seguiment (08-06)
git log no mostra un commit que existeix git log --all, git reflog, git cat-file -t És en una altra branca, o és inassolible (09-04)
Un merge diu «Already up to date» sense ser-ho git merge-base <a> <b>, git log --graph --all Fusió revertida (05-06)
Dues branques no es poden fusionar git rev-list --max-parents=0 HEAD Històries no relacionades (09-03)
Un submòdul apareix sempre com a modificat git diff --submodule, git ls-files --stage Commit del submòdul diferent (06-05)
El diff surt com a binari git check-attr diff -- <f> -diff o binary a .gitattributes
Un àlies fa alguna cosa inesperada GIT_TRACE=1 git <àlies> Expansió de l'àlies (06-04)
Git es comporta diferent en dues carpetes git config --list --show-origin --show-scope includeIf condicional
El remot és en un commit que no esperava git ls-remote origin <branca> Algú ha publicat, o ha forçat (09-03)

I un guió de diagnòstic general, per quan no saps ni per on començar:

#!/usr/bin/env bash
# diagnostic-git.sh — foto completa de l'estat real del repositori
set -u
echo "=== ENTORN ==="
git --version
git rev-parse --show-toplevel
git rev-parse --git-dir
echo "branca: $(git rev-parse --abbrev-ref HEAD)"
echo "upstream: $(git rev-parse --symbolic-full-name '@{u}' 2>/dev/null || echo 'cap')"

echo; echo "=== ESTAT ==="
git status -sb | head -20

echo; echo "=== DIVERGÈNCIA ==="
git rev-list --left-right --count HEAD...@{u} 2>/dev/null || echo "sense upstream"

echo; echo "=== CONFIGURACIÓ CLAU ==="
git config --list --show-scope 2>/dev/null \
  | grep -E '(user\.|core\.(autocrlf|eol|hooksPath|excludesFile|fsmonitor)|pull\.|push\.|merge\.|diff\.)'

echo; echo "=== HOOKS ACTIUS ==="
RUTA=$(git config --get core.hooksPath || echo "$(git rev-parse --git-dir)/hooks")
find "$RUTA" -maxdepth 1 -type f -perm -u+x ! -name '*.sample' 2>/dev/null

echo; echo "=== REFERÈNCIES ==="
echo "branques locals: $(git branch | wc -l) | remotes: $(git branch -r | wc -l) | etiquetes: $(git tag | wc -l)"
echo "sense publicar: $(git for-each-ref --format='%(refname:short) %(upstream)' refs/heads | awk '$2==""{print $1}' | tr '\n' ' ')"

echo; echo "=== INTEGRITAT ==="
git fsck --connectivity-only --no-progress 2>&1 | grep -vE '^(dangling|notice|Checking)' || echo "sense errors"

echo; echo "=== ÚLTIMS MOVIMENTS ==="
git reflog --date=relative -8

Desa'l. El dia que et faci falta, t'estalviarà quinze minuts d'ordres soltes.

Errors Habituals i Consells

Error 1: canviar coses a l'atzar fins que funcioni. Deixa el problema latent i no n'aprens res. Formula una hipòtesi falsable i comprova-la.

Error 2: refiar-se de la memòria en lloc de consultar l'estat real. «Jo em penso que aquell fitxer està ignorat» és diferent de git check-ignore -v.

Error 3: no aïllar. Abans d'investigar a fons, comprova si el problema persisteix sense configuració global, sense hooks i en un clon net. Aquest descart costa un minut.

Error 4: activar GIT_TRACE_PACKET d'entrada. És el nivell més profund i produeix molt soroll. Comença per GIT_TRACE i GIT_TRACE_SETUP.

Error 5: enganxar la sortida de GIT_CURL_VERBOSE en un tiquet públic. Pot contenir capçaleres Authorization (08-05). Revisa-la abans.

Error 6: fer servir git config --list sense --show-origin --show-scope. Sense aquestes opcions no saps quin valor guanya ni d'on surt, que és justament el que investigues.

Error 7: arreglar la línia que bisect assenyala sense llegir el missatge del commit. Pots trencar la funcionalitat que aquell commit venia a implementar, com hauria passat a l'apartat 7.

Error 8: intentar bisect sense una prova automatitzable. Amb git bisect run i un guió, trenta commits són cinc passos automàtics; a mà, mitja hora d'avorriment i errors.

Error 9: confondre git status amb la realitat. status interpreta; ls-files --stage, check-attr i rev-parse mostren.

Consell 1: el mètode abans que les eines. Reproduir, aïllar, consultar l'estat real, comprovar la hipòtesi.

Consell 2: git config --list --show-origin --show-scope com a reflex. Resol una fracció sorprenent dels «Git fa alguna cosa estranya».

Consell 3: git check-ignore -v i git check-attr -a. Dues ordres que donen la resposta exacta a dues de les preguntes més freqüents del dia a dia.

Consell 4: git ls-files --eol en qualsevol problema de finals de línia. La taula i/ i w/ ho diu tot d'un cop d'ull.

Consell 5: desa el guió de diagnòstic de l'apartat 8. És la foto completa en trenta segons.

Consell 6: escriu la prova abans de bisecar. L'esforç es recupera al primer bisect run, i la prova es queda al projecte.

Exercicis

Exercici 1: el misteri del fitxer sempre modificat

  1. Crea un repositori amb app.js, estils.css i index.html, i confirma'ls.
  2. Afegeix un .gitattributes amb *.css text eol=crlf i confirma'l.
  3. Executa git status i observa el resultat. Després executa git ls-files --eol i git check-attr -a estils.css.
  4. Explica exactament què està passant fent servir les columnes i/ i w/.
  5. Resol-ho amb git add --renormalize . (lliçó 08-04) i comprova amb ls-files --eol que les columnes quadren.
  6. Repeteix l'exercici amb un fitxer al qual .gitattributes marqui -diff i comprova què canvia a git diff.

Exercici 2: configuració, ignorats i traces

  1. Crea un repositori i configura user.email diferent al nivell local i al global.
  2. Fes servir git config --list --show-origin --show-scope per determinar quin guanya, i confirma-ho fent un commit i mirant %ae.
  3. Crea un .gitignore amb *.log, un .git/info/exclude amb !important.log i un core.excludesFile global amb temporal*. Crea els tres fitxers corresponents i fes servir git check-ignore -v amb cadascun per determinar quina regla decideix.
  4. Crea un hook pre-commit que imprimeixi alguna cosa, fes-lo no executable, i intenta confirmar. Fes servir GIT_TRACE=1 per demostrar que no es llança.
  5. Fes-lo executable i repeteix, comprovant a la traça que ara sí que apareix.
  6. Executa GIT_TRACE_SETUP=1 git status des d'un subdirectori i explica cada línia de la sortida.

Exercici 3: la investigació completa

  1. Crea un repositori amb un app.js que contingui una funció calculaPendents correcta, i fes vint commits que toquin altres parts del projecte.
  2. En un commit intermedi, introdueix la fallada de l'apartat 7 (afegir || t.oculta) amb un missatge de commit que expliqui el perquè.
  3. Fes deu commits més a sobre.
  4. Escriu un guió de prova que surti amb 0 si la funció és correcta i amb 1 si no.
  5. Troba el commit culpable amb git bisect run i anota quants passos ha necessitat.
  6. Fes servir git show, git blame -L i git log -L :calculaPendents:app.js per reconstruir la història completa d'aquella funció.
  7. Llegeix el missatge complet del commit culpable amb git log -1 --format=%B i explica per què la resposta correcta no és «canviar la línia».

Solucions

Solució 1:

rm -rf /tmp/p9-06 && mkdir /tmp/p9-06 && cd /tmp/p9-06 && git init -q -b main
git config user.name "Carla Vidal"; git config user.email "[email protected]"
printf 'console.log(1);\n' > app.js
printf 'body { margin: 0; }\n' > estils.css
printf '<h1>Gestor</h1>\n' > index.html
git add . && git commit -q -m "chore: estructura inicial"

# 2. El .gitattributes
printf '*.css text eol=crlf\n' > .gitattributes
git add .gitattributes && git commit -q -m "chore: força CRLF als CSS"
# 3. El símptoma
git status --short
(sense sortida)

Sorprenentment, res. L'atribut s'aplica en escriure al disc, i el fitxer al disc encara té LF. Força'n l'actualització:

rm estils.css && git checkout -- estils.css
git status --short
file estils.css
 M estils.css
estils.css: ASCII text, with CRLF line terminators

Aquí hi ha el misteri típic: un fitxer modificat que ningú no ha tocat.

git ls-files --eol
git check-attr -a estils.css
i/lf    w/crlf  attr/text eol=crlf 	estils.css
i/lf    w/lf    attr/              	app.js
i/lf    w/lf    attr/              	index.html
i/lf    w/lf    attr/              	.gitattributes
estils.css: text: set
estils.css: eol: crlf
# 4. L'explicació
git diff --stat estils.css
 estils.css | 2 +-

Lectura de les columnes:

  • i/lf: a l'índex hi ha LF, que és el que es va desar al commit original.
  • w/crlf: al disc hi ha CRLF, perquè l'atribut eol=crlf el converteix en extreure'l.
  • Git compara índex i disc byte a byte, veu una diferència a cada final de línia, i ho marca com a modificat.

No és un error: és l'atribut funcionant, sobre un fitxer que es va desar abans que l'atribut existís.

# 5. La solució
git add --renormalize .
git status --short
git commit -q -m "chore: renormalitza els finals de línia"
git ls-files --eol estils.css
M  estils.css
i/lf    w/crlf  attr/text eol=crlf 	estils.css
git status --short
(sense sortida)

Net. --renormalize reescriu l'índex aplicant els atributs actuals: ara l'índex desa LF amb coneixement de l'atribut, i la comparació quadra. És exactament el procediment de la lliçó 08-04.

# 6. Amb -diff
printf '*.css text eol=crlf\nindex.html -diff\n' > .gitattributes
git add .gitattributes && git commit -q -m "chore: index.html sense diff"
printf '<h1>Gestor de tasques</h1>\n' > index.html
git diff index.html
git check-attr diff -- index.html
diff --git a/index.html b/index.html
index 8a1f6c3..4f8a2e6 100644
Binary files a/index.html and b/index.html differ
index.html: diff: unset

-diff fa que Git tracti el fitxer com a binari a efectes de diff. És útil per a minificats i generats, i desconcertant si no saps que hi és: git check-attr diff ho resol en un segon.

Solució 2:

rm -rf /tmp/p9-06b && mkdir /tmp/p9-06b && cd /tmp/p9-06b && git init -q -b main
git config --global user.email "[email protected]" 2>/dev/null
git config user.name "Ana Ferrer"
git config user.email "[email protected]"      # local, diferent

# 2. Qui guanya
git config --list --show-origin --show-scope | grep user.email
global	/home/ana/.gitconfig	[email protected]
local	.git/config	[email protected]
git config --get user.email
echo "x" > f.txt && git add . && git commit -q -m "prova"
git log -1 --format='%ae'

El local guanya, perquè és més a prop del repositori. És exactament el mecanisme de l'apartat 3, i la causa habitual del «he configurat bé el correu».

# 3. Les tres capes d'ignorats
printf '*.log\n' > .gitignore
printf '!important.log\n' > .git/info/exclude
printf 'temporal*\n' > /tmp/gitignore-global
git config core.excludesFile /tmp/gitignore-global

touch sortida.log important.log temporal-1.txt normal.txt

for f in sortida.log important.log temporal-1.txt normal.txt; do
  printf '%-18s ' "$f"
  git check-ignore -v "$f" || echo "(no ignorat)"
done
sortida.log        .gitignore:1:*.log	sortida.log
important.log      .git/info/exclude:1:!important.log	important.log
temporal-1.txt     /tmp/gitignore-global:1:temporal*	temporal-1.txt
normal.txt         (no ignorat)

Cadascun decidit per un fitxer diferent, i check-ignore -v l'anomena amb el seu número de línia. Fixa't en important.log: la regla que coincideix és la negació, i per això el fitxer no està ignorat:

git status --short | grep important
?? important.log

Quan check-ignore -v torna un patró que comença per !, significa «l'ha rescatat aquesta regla».

# 4. El hook que no s'executa
cat > .git/hooks/pre-commit <<'EOF'
#!/usr/bin/env bash
echo "HOOK EXECUTAT"
EOF
# sense chmod +x
echo "y" >> f.txt
GIT_TRACE=1 git commit -q -am "prova hook" 2>&1 | grep -ci "pre-commit"
git log -1 --format=%s
0
prova hook

Zero mencions al hook a la traça, i el commit s'ha fet. El hook no s'ha llançat.

ls -l .git/hooks/pre-commit
-rw-r--r-- 1 ana ana 46 jul 31 13:02 .git/hooks/pre-commit
# 5. Amb permís d'execució
chmod +x .git/hooks/pre-commit
echo "z" >> f.txt
GIT_TRACE=1 git commit -am "prova hook 2" 2>&1 | grep -i "pre-commit"
13:03:11.402 run-command.c:657  trace: run_command: '.git/hooks/pre-commit'
HOOK EXECUTAT

Aquí està: run_command el llança i el echo apareix. GIT_TRACE=1 és el diagnòstic definitiu per a «el meu hook no s'executa» (lliçó 06-01).

# 6. GIT_TRACE_SETUP des d'un subdirectori
mkdir -p src/components && cd src/components
GIT_TRACE_SETUP=1 git status 2>&1 | head -6
13:04:22.101 trace.c:318  setup: git_dir: /tmp/p9-06b/.git
13:04:22.101 trace.c:319  setup: git_common_dir: /tmp/p9-06b/.git
13:04:22.101 trace.c:320  setup: worktree: /tmp/p9-06b
13:04:22.101 trace.c:321  setup: cwd: /tmp/p9-06b/src/components
13:04:22.101 trace.c:322  setup: prefix: src/components/
Línia Què diu
git_dir On és el .git que s'està fent servir
git_common_dir El .git compartit: diferent en un worktree (06-06)
worktree L'arrel del projecte
cwd Des d'on has llançat l'ordre
prefix La ruta relativa que Git anteposa als arguments

Aquest prefix explica per què git add . des d'un subdirectori només afegeix aquell subdirectori, i per què els patrons s'interpreten com s'interpreten.

Solució 3:

rm -rf /tmp/p9-06c && mkdir /tmp/p9-06c && cd /tmp/p9-06c && git init -q -b main
git config user.name "Ana Ferrer"; git config user.email "[email protected]"

cat > app.js <<'EOF'
function calculaPendents(tasques) {
  return tasques.filter(t => !t.completada).length;
}
module.exports = { calculaPendents };
EOF
git add . && git commit -q -m "GT-142 mostra el comptador de pendents"

# 1. Vint commits de farciment
for i in $(seq 1 20); do echo "// canvi $i" >> altres.js; git add .; git commit -q -m "chore: canvi $i"; done
# 2. El commit culpable, amb un missatge que explica el perquè
sed -i 's/!t.completada)/!t.completada || t.oculta)/' app.js
git commit -q -am "GT-231 afegeix el filtre de tasques ocultes

Les tasques arxivades deixen d'aparèixer al llistat. S'afegeix la
marca \`oculta\` i es filtra a renderitzaTasques().

El comptador ha de continuar incloent-les perquè l'especificació de
GT-231 diu que \"pendents\" compta totes les tasques no completades,
siguin visibles o no."
CULPABLE=$(git rev-parse --short HEAD)

# 3. Deu commits més
for i in $(seq 21 30); do echo "// canvi $i" >> altres.js; git add .; git commit -q -m "chore: canvi $i"; done
git rev-list --count HEAD
32
# 4. La prova
cat > /tmp/prova-comptador.sh <<'EOF'
#!/usr/bin/env bash
node -e '
  const { calculaPendents } = require(process.cwd() + "/app.js");
  const tasques = [
    { completada: false, oculta: false },
    { completada: true,  oculta: false },
    { completada: false, oculta: true  }
  ];
  process.exit(calculaPendents(tasques) === 2 ? 0 : 1);
' 2>/dev/null
EOF
chmod +x /tmp/prova-comptador.sh
/tmp/prova-comptador.sh; echo "estat actual: $?"
estat actual: 1

La prova falla a HEAD: reproduït.

# 5. Bisect
git bisect start
git bisect bad HEAD
git bisect good HEAD~31
git bisect run /tmp/prova-comptador.sh 2>&1 | tail -12
Bisecting: 15 revisions left to test after this (roughly 4 steps)
running  '/tmp/prova-comptador.sh'
Bisecting: 7 revisions left to test after this (roughly 3 steps)
running  '/tmp/prova-comptador.sh'
Bisecting: 3 revisions left to test after this (roughly 2 steps)
running  '/tmp/prova-comptador.sh'
Bisecting: 1 revision left to test after this (roughly 1 step)
running  '/tmp/prova-comptador.sh'
4f8a2e6c9b1d5e3a7f2c8b6d9e4a1f5c3b7d2e9a is the first bad commit
    GT-231 afegeix el filtre de tasques ocultes
bisect found first bad commit
git bisect reset
echo "Culpable esperat: $CULPABLE"
Culpable esperat: 4f8a2e6

Cinc passos automàtics per a 31 commits. El logaritme en base 2 de 31 és una mica menys de 5: exactament el que predeia la lliçó 06-02.

# 6. La història de la funció
git show 4f8a2e6 -- app.js
 function calculaPendents(tasques) {
-  return tasques.filter(t => !t.completada).length;
+  return tasques.filter(t => !t.completada || t.oculta).length;
 }
git blame -L 1,3 app.js
4f8a2e6c (Ana Ferrer 2026-07-31 13:12:04 +0200 1) function calculaPendents(tasques) {
4f8a2e6c (Ana Ferrer 2026-07-31 13:12:04 +0200 2)   return tasques.filter(t => !t.completada || t.oculta).length;
8a1f6c3d (Ana Ferrer 2026-07-31 13:11:58 +0200 3) }
git log -L :calculaPendents:app.js --format='%h %ad %s' --date=short | grep -E '^commit|^[0-9a-f]{7} '
4f8a2e6 2026-07-31 GT-231 afegeix el filtre de tasques ocultes
8a1f6c3 2026-07-31 GT-142 mostra el comptador de pendents

Dos commits en tota la vida d'aquella funció. -L :funció:fitxer la segueix encara que es mogui de lloc dins del fitxer, cosa que blame -L 1,3 no faria.

# 7. El perquè
git log -1 --format=%B 4f8a2e6
GT-231 afegeix el filtre de tasques ocultes

Les tasques arxivades deixen d'aparèixer al llistat. S'afegeix la
marca `oculta` i es filtra a renderitzaTasques().

El comptador ha de continuar incloent-les perquè l'especificació de
GT-231 diu que «pendents» compta totes les tasques no completades,
siguin visibles o no.

Per què la resposta correcta no és «canviar la línia»:

El comportament és intencionat i està justificat per escrit. No hi ha cap error de programació: hi ha dues especificacions que es contradiuen. GT-231 diu que el comptador inclogui les ocultes; el que la Carla espera és el contrari.

Si es treu el || t.oculta sense més:

  1. Es trenca GT-231, que algú va demanar i algú va validar.
  2. Ningú no sabrà per què, perquè el commit que ho desfaci probablement dirà «corregeix el comptador».
  3. D'aquí a dues setmanes, qui va demanar GT-231 obrirà un tiquet idèntic en sentit contrari, i el cicle començarà una altra vegada.

La sortida correcta és portar la contradicció a qui la pugui resoldre, i que la decisió —sigui quina sigui— quedi escrita al missatge del commit que la implementi (lliçó 08-01), amb una referència als dos tiquets.

El missatge del Bruno ha estalviat un error. Aquell paràgraf de tres línies és la diferència entre una correcció i un bucle.

Conclusió

Aquesta lliçó anava sobre entendre per què Git està fent el que fa.

  • El mètode va abans que les eines: reproduir amb l'ordre mínima, aïllar traient variables (configuració global, hooks, un clon net), consultar l'estat real i no el recordat, i comprovar una hipòtesi concreta abans de canviar res.
  • Les variables de traça ensenyen el que passa per sota: GIT_TRACE per a les subordres, àlies i hooks; GIT_TRACE_SETUP per saber quin repositori es pensa Git que fa servir; GIT_SSH_COMMAND="ssh -v" i GIT_CURL_VERBOSE per a autenticació i xarxa; GIT_TRACE_PACKET per al protocol. Comença per les suaus i revisa la sortida abans de compartir-la.
  • git config --list --show-origin --show-scope resol una fracció sorprenent dels misteris, perquè diu quin valor guanya i de quin fitxer surt, inclosos els includeIf condicionals.
  • git check-ignore -v i git check-attr -a responen amb precisió les dues preguntes més freqüents del dia a dia: per què s'ignora (o no) un fitxer, i quins atributs se li apliquen.
  • git ls-files és la finestra a l'índex: --stage per a modes i etapes de conflicte, --eol per als finals de línia amb les seves columnes i/ i w/, --others --exclude-standard per al que és realment nou.
  • La lampisteria contesta sobre el graf sense interpretacions: rev-parse per traduir a hashos i conèixer l'entorn, rev-list per comptar i llistar, cat-file --batch-check per a inspecció massiva, verify-pack per a l'interior d'un packfile, for-each-ref per a totes les referències i ls-remote per al servidor sense baixar res.
  • I la investigació completa: bisect porta del símptoma al commit; blame i el missatge del commit porten del commit a la intenció. Saltar-se el segon pas produeix pedaços que trenquen una altra cosa.

El mòdul, en una idea

El mòdul 8 va acabar anunciant desastres. Aquest els ha resolt tots, i la lliçó de fons és una de sola:

A Git, gairebé res no es perd de debò. El que es perd és la calma.

Els commits són objectes immutables que sobreviuen encara que cap referència no els apunti (09-01). reset mou una branca, no destrueix història (09-02). Una divergència és una decisió pendent, no una avaria (09-03). El reflog recorda per on has passat, i amb ell torna el reset --hard, la branca esborrada, el rebase fallit i el desament temporal eliminat (09-04). Un repositori danyat és un problema de logística, perquè el clon de qualsevol company és una còpia gairebé completa (09-05). I quan res d'això no encaixa, hi ha traces, lampisteria i un mètode (09-06).

El que és veritablement fràgil és el que mai no va arribar a ser un objecte: el canvi sense confirmar, el fitxer sense seguiment. Per això el consell més rendible de tot el mòdul no és una ordre, sinó un hàbit: confirma aviat, marca amb git branch abans del que és arriscat, i publica les teves branques.

El que ve

L'Ana, el Bruno, la Carla i el Diego dominen Git a gestor-tasques. Saben construir l'historial, manipular-lo amb criteri, col·laborar amb un procés, mantenir bons hàbits i sortir dels embolics.

Però gestor-tasques són quatre fitxers i un equip de quatre persones. El món real és més gran i més estrany.

Hi ha projectes amb cinquanta mil fitxers i vint anys d'història, on un git status sense optimitzar triga mig minut. Hi ha repositoris que guarden vídeos, models 3D i fitxers de disseny de centenars de megabytes, i que necessiten un sistema diferent per emmagatzemar-los. Hi ha organitzacions on Git no el fa servir una persona en un terminal, sinó cent canonades de desplegament que clonen, etiqueten i publiquen sense intervenció humana. Hi ha integracions amb editors, amb sistemes de tiquets, amb plataformes de revisió i amb eines d'anàlisi que canvien per complet l'experiència diària. I hi ha un Git que continua evolucionant: SHA-256, referències empaquetades en un format nou, clons parcials, índexs dispersos.

Al mòdul 10: Git al Món Real veurem estudis de cas de com fan servir Git projectes i organitzacions reals, la integració amb altres eines del dia a dia, Git LFS per a fitxers grans, com escalar Git en repositoris enormes i monorepos, el paper de Git a DevOps com a peça central del lliurament continu, i cap a on va Git en els pròxims anys.

Comencem mirant com ho fan els altres, a la lliçó 10-01: Estudis de Cas.

Dominant Git: De Principiant a Avançat

Mòdul 1: Introducció a Git

Mòdul 2: Operacions Bàsiques de Git

Mòdul 3: Branques i Fusió

Mòdul 4: Treballant amb Repositoris Remots

Mòdul 5: Operacions Avançades de Git

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

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

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

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

Mòdul 10: Git al Món Real

© Copyright 2026. Tots els drets reservats