La Carla ja ha arreglat l'esborrat de tasques gràcies al bisect. Però mentre era a app.js s'ha trobat amb això:

function normalitzaText(text) {
  return text
    .replace(/ /g, ' ')
    .replace(/[​-‍]/g, '')
    .trim();
}

Ningú de l'equip recorda haver escrit aquesta funció. No està documentada, no té comentaris i té l'aspecte d'un pedaç defensiu contra alguna cosa molt concreta. La temptació d'esborrar-la és enorme: són cinc línies que "no fan res".

Aquesta temptació és exactament l'escenari que descriu la tanca de Chesterton: no treguis una tanca del mig del camp fins que sàpigues per què la van posar. I en un repositori Git sempre es pot saber, perquè cada línia de cada fitxer prové d'un commit concret, amb el seu autor, la seva data i el seu missatge.

L'eina que fa aquesta consulta és git blame, i en aquesta lliçó l'esprémerem: no només per veure qui va tocar la línia per última vegada, sinó per travessar el soroll —reformatatges, codi mogut, canvis d'espaiat— i arribar al commit que de debò explica el perquè.

Contingut

  1. Què és exactament git blame
  2. Llegir la sortida
  3. Acotar: -L per línies i per funció
  4. Veure més enllà del soroll: -w, -M i -C
  5. Viatjar en el temps: <sha>^ i --since
  6. git log -L: l'evolució completa d'un rang de línies
  7. Ignorar commits de format: --ignore-rev i .git-blame-ignore-revs
  8. Resolent el misteri de normalitzaText
  9. Blame a la pràctica: atribució, no culpa

  1. Què és exactament git blame

git blame <fitxer> respon a una pregunta molt concreta:

Per a cada línia del fitxer tal com està ara, quin va ser l'últim commit que la va modificar?

Convé subratllar tres coses d'aquesta definició, perquè els tres malentesos més habituals en surten:

  • És "l'últim que la va tocar", no "el que la va escriure". Si l'Ana va escriure la línia al gener i el Bruno li va canviar una coma al juny, blame assenyala el Bruno. Els apartats 4 i 7 són precisament les eines per travessar aquest soroll.
  • Treballa sobre l'estat actual del fitxer. Les línies esborrades no hi apareixen: si t'interessen, l'eina és git log -L (apartat 6) o la cerca «pickaxe» -S de la lliçó 02-06.
  • No calcula res de nou. Tota la informació ja és al model de dades de la lliçó 01-04: blame recorre l'historial del fitxer comparant versions i atribueix cada línia. És una consulta, no una metadada desada.

  1. Llegir la sortida

cd ~/projectes/gestor-tasques
git blame app.js
9f3c7a1e (Ana Ferrer   2026-04-12 09:31:44 +0200   1) const llista = document.querySelector('#llista-tasques');
9f3c7a1e (Ana Ferrer   2026-04-12 09:31:44 +0200   2) const formulari = document.querySelector('#formulari-tasca');
4e8b2c9d (Bruno Salas  2026-05-03 17:22:08 +0200   3) const camp = document.querySelector('#nova-tasca');
^7d2a1f8 (Ana Ferrer   2026-03-28 11:05:12 +0200   4)
2c6e9a4f (Carla Vidal  2026-05-19 14:47:31 +0200   5) let tasques = carregaTasques();
00000000 (No Confirmat 2026-08-01 10:12:03 +0200   6) let filtreActiu = 'totes';

Les cinc columnes, d'esquerra a dreta:

Columna Contingut Detall
1 Hash abreujat del commit És el commit que va tocar aquella línia per última vegada
2 Autor L'autor, no el committer (la distinció de la lliçó 01-06)
3 Data i hora Data d'autoria, amb zona horària
4 Número de línia Al fitxer actual
5 Contingut La línia tal qual

I dues marques especials que cal saber llegir:

  • ^7d2a1f8, amb accent circumflex: la línia ve del commit més antic del rang analitzat. Sol ser el commit inicial (boundary commit). Vol dir "això és tan vell com el mateix historial que estic mirant".
  • 00000000 / "No Confirmat" (Not Committed Yet en anglès): la línia està modificada a l'arbre de treball i encara no s'ha confirmat. És teva, ara mateix.

Opcions de format que estalvien molt de temps:

git blame -s app.js            # només hash i línia: compacte, sense autor ni data
git blame -e app.js            # mostra el correu en lloc del nom
git blame --date=short app.js  # dates com 2026-04-12, sense hora ni zona
git blame -l app.js            # hashos complets de 40 caràcters
git blame -c app.js            # format compatible amb git annotate

I la que de debò es fa servir cada dia, perquè combina el just:

git blame --date=short -w app.js

Un detall pràctic: la sortida de blame és llarga i sol passar pel paginador. Per saltar directament a la zona que t'interessa, a less n'hi ha prou amb /normalitzaText + Enter. Però és molt millor acotar d'entrada, que és el que ve ara.

  1. Acotar: -L per línies i per funció

Gairebé mai vols el fitxer sencer. -L limita l'anàlisi a un rang:

git blame -L 78,92 app.js
8d4f2a7c (Bruno Salas  2026-06-22 11:14:38 +0200  78)   llista.addEventListener('click', (e) => {
8d4f2a7c (Bruno Salas  2026-06-22 11:14:38 +0200  79)     if (e.target.closest('.esborrar')) {
8d4f2a7c (Bruno Salas  2026-06-22 11:14:38 +0200  80)       esborraTasca(e.target.closest('.esborrar').dataset.id);

Maneres d'expressar el rang, totes vàlides:

Sintaxi Significat
-L 78,92 De la línia 78 a la 92
-L 78,+15 Quinze línies a partir de la 78
-L 78,-5 De la 73 a la 78
-L 78 De la 78 al final del fitxer
-L ,20 Del principi a la 20
-L :normalitzaText:app.js El bloc d'aquella funció, sense comptar línies
-L '/^function esborraTasca/',+30 Des de la línia que casa amb l'expressió regular

La forma -L :<nom>:<fitxer> és la més còmoda i la menys coneguda. Git localitza el bloc de la funció fent servir les mateixes regles de funcname que empra git diff per a les capçaleres dels fragments (lliçó 02-05):

git blame -L :normalitzaText:app.js
b7e2c4a9 (Carla Vidal  2026-02-14 16:03:55 +0100  43) function normalitzaText(text) {
b7e2c4a9 (Carla Vidal  2026-02-14 16:03:55 +0100  44)   return text
3f1a8d6c (Ana Ferrer   2026-05-30 10:18:22 +0200  45)     .replace(/ /g, ' ')
3f1a8d6c (Ana Ferrer   2026-05-30 10:18:22 +0200  46)     .replace(/[​-‍]/g, '')
b7e2c4a9 (Carla Vidal  2026-02-14 16:03:55 +0100  47)     .trim();
b7e2c4a9 (Carla Vidal  2026-02-14 16:03:55 +0100  48) }

Primera pista del misteri: la funció la va crear la Carla al febrer, i l'Ana hi va afegir els dos replace al maig. Dos commits, dos motius possiblement diferents.

El reconeixement de funcions depèn del llenguatge. Git porta patrons integrats per a molts (c, java, python, php, rust...). Per a JavaScript, el patró integrat javascript s'activa declarant-lo al .gitattributes amb *.js diff=javascript. Aquest fitxer, i tot el que permet configurar, és el tema de la lliçó 08-04; aquí n'hi ha prou de saber que si -L :funcio: no encerta, aquesta és la raó.

  1. Veure més enllà del soroll: -w, -M i -C

Aquí hi ha el veritable valor de blame, i el que separa l'ús ingenu de l'ús expert. L'atribució "en brut" menteix constantment, perquè:

  • Algú va reindentar el fitxer → totes les línies són seves.
  • Algú va moure una funció de lloc dins del mateix fitxer → totes les línies són seves.
  • Algú va extreure un mòdul a un fitxer nou → totes les línies són seves.

En els tres casos, el contingut no va canviar, i blame està assenyalant el missatger. Aquestes tres opcions ho corregeixen:

-w: ignorar els canvis d'espaiat

git blame -w app.js

Ignora les diferències que són només d'espais en blanc en comparar versions. Si el Bruno va passar el fitxer pel formatador i va canviar la indentació de 4 a 2 espais, -w mira a través d'aquell commit i continua atribuint la línia a qui va escriure el codi real.

És pràcticament gratis i gairebé mai molesta. Posa-la sempre.

-M: seguir codi mogut dins del mateix fitxer

git blame -M app.js

Detecta blocs de línies que es van moure o copiar dins del mateix fitxer i atribueix la línia al seu origen, no al commit que la va moure. És el cas de "he reordenat les funcions per agrupar les d'emmagatzematge".

-C: seguir codi mogut entre fitxers

git blame -C app.js

Detecta línies que vénen d'un altre fitxer modificat al mateix commit. És el cas d'extreure codi a un mòdul nou.

-C té nivells, segons quantes vegades es repeteixi, i aquesta és la taula que cal tenir a mà:

Opció Què busca Cost
-M Moviments i còpies dins del mateix fitxer Baix
-C A més, codi vingut d'altres fitxers modificats al mateix commit Mitjà
-C -C A més, codi vingut de qualsevol fitxer del commit que va crear el fitxer Alt
-C -C -C A més, codi vingut de qualsevol fitxer de qualsevol commit Molt alt

I es pot afinar el llindar de detecció amb un número: -M20 exigeix almenys 20 caràcters alfanumèrics per considerar que un bloc s'ha mogut (per defecte en són 40 per a -M).

A la pràctica, la invocació que resol el 95 % de les investigacions serioses és:

git blame -w -C -C -L :normalitzaText:app.js app.js

Fixa't que es pot combinar tot: ignorar espais, seguir moviments entre fitxers amb nivell mitjà-alt, i acotat a una funció. És més lent, però en un fitxer d'uns centenars de línies ni es nota.

Comparació pràctica de l'efecte, sobre les mateixes línies:

git blame -L 43,48 app.js
c9a2e7f4 (Bruno Salas  2026-07-11 09:02:17 +0200  43) function normalitzaText(text) {
c9a2e7f4 (Bruno Salas  2026-07-11 09:02:17 +0200  44)   return text
c9a2e7f4 (Bruno Salas  2026-07-11 09:02:17 +0200  45)     .replace(/ /g, ' ')
git blame -w -C -L 43,48 app.js
b7e2c4a9 (Carla Vidal  2026-02-14 16:03:55 +0100  43) function normalitzaText(text) {
b7e2c4a9 (Carla Vidal  2026-02-14 16:03:55 +0100  44)   return text
3f1a8d6c (Ana Ferrer   2026-05-30 10:18:22 +0200  45)     .replace(/ /g, ' ')

El primer resultat assenyala el Bruno i un commit de juliol: és el commit en què el Bruno va moure la funció d'index.html a app.js. Informativament, és inútil. El segon assenyala els commits que van escriure el contingut, que és el que buscàvem.

  1. Viatjar en el temps: <sha>^ i --since

blame accepta un punt de partida diferent d'HEAD:

git blame v1.0.0 -- app.js         # el fitxer tal com era a l'etiqueta
git blame 8d4f2a7 -- app.js        # tal com era en aquell commit

D'aquí surt la tècnica més útil de tot l'apartat: saltar per damunt d'un commit per veure què hi havia abans.

Quan blame et diu que la línia 45 la va tocar 3f1a8d6 i aquell commit resulta ser un reformatatge, vols saber qui la va tocar abans. La resposta és fer blame del pare d'aquell commit:

git blame 3f1a8d6^ -- app.js

3f1a8d6^ és "el pare de 3f1a8d6" (lliçó 02-06). En fer blame del fitxer en aquell punt, el commit 3f1a8d6 deixa d'existir per a l'anàlisi i apareix l'anterior. Repetint el procés es pot anar retrocedint capa a capa:

git blame 3f1a8d6^ -- app.js | sed -n '45p'
# → 1c8f4b2 (Carla Vidal 2026-03-02 ...)
git blame 1c8f4b2^ -- app.js | sed -n '45p'
# → b7e2c4a9 (Carla Vidal 2026-02-14 ...)

És tediós a mà, i per això existeix git log -L (apartat 6), que fa aquest recorregut d'una sola vegada. Però convé conèixer la tècnica, perquè funciona sempre i en qualsevol versió de Git.

Altres maneres d'acotar el rang temporal:

git blame --since=3.months app.js
git blame v1.0.0..HEAD -- app.js

Amb --since, les línies l'últim canvi de les quals és anterior al límit apareixen marcades amb ^ com a línies de frontera: "això ja hi era abans del període que t'interessa". És útil per respondre a "què s'ha tocat d'aquest fitxer aquest trimestre?" sense soroll de fa tres anys.

  1. git log -L: l'evolució completa d'un rang de línies

blame dona una foto: l'estat actual i qui va tocar cada línia per última vegada. git log -L dona la pel·lícula: tots els commits que han afectat un rang de línies, amb el seu diff.

git log -L :normalitzaText:app.js
commit 3f1a8d6c9e2b4a7f1d5c8e3b6a9f2d4c7e1b5a8f
Author: Ana Ferrer <[email protected]>
Date:   Fri May 30 10:18:22 2026 +0200

    Elimina els caràcters invisibles en normalitzar el text de les tasques

    En enganxar text des del gestor de correu, les tasques arribaven amb
    espais durs (U+00A0) i marques d'amplada zero. El resultat era
    que dues tasques visualment idèntiques no es detectaven com a duplicades
    i que el filtre per text no trobava res.

diff --git a/app.js b/app.js
--- a/app.js
+++ b/app.js
@@ -43,5 +43,7 @@
 function normalitzaText(text) {
   return text
+    .replace(/ /g, ' ')
+    .replace(/[​-‍]/g, '')
     .trim();
 }

commit b7e2c4a9f1d6c3e8b5a2f7d9c4e1b8a6f3d5c2e9
Author: Carla Vidal <[email protected]>
Date:   Sat Feb 14 16:03:55 2026 +0100

    Normalitza el text de les tasques abans de desar-les

    Els espais sobrants al principi i al final provocaven tasques
    duplicades aparents.

diff --git a/app.js b/app.js
--- a/app.js
+++ b/app.js
@@ -41,0 +43,5 @@
+function normalitzaText(text) {
+  return text
+    .trim();
+}

Misteri resolt en una sola ordre. Els dos replace no són arbitraris: protegeixen dels caràcters invisibles que arriben en enganxar text des del correu. Esborrar aquesta funció hauria reintroduït una fallada real i difícil de reproduir.

Les formes de -L són les mateixes que a blame:

git log -L 43,48:app.js                    # rang de línies
git log -L :normalitzaText:app.js          # una funció
git log -L '/^function esborraTasca/',+20:app.js

Combinacions molt útils:

git log -L :normalitzaText:app.js --oneline    # només la llista de commits, sense diffs
git log -L :normalitzaText:app.js -n 3         # els tres més recents
git log -L :desaTasques:app.js -L :carregaTasques:app.js   # dos rangs alhora

La comparació entre les dues eines, que és la que cal tenir clara:

git blame git log -L
Què mostra Estat actual: qui va tocar cada línia per última vegada Història completa: tots els canvis del rang
Línies esborrades No hi apareixen Sí que hi apareixen, als diffs
Sortida Una línia per línia de codi Un commit amb diff per cada canvi
Pregunta que respon "Qui va escriure això?" "Com hem arribat fins a això?"
Punt de partida típic Estàs llegint el fitxer i alguna cosa et grinyola Ja saps quina zona t'interessa
Cost Ràpid Més lent (recorre l'historial del fitxer)

El flux natural és encadenar-les: blame per localitzar la zona i el commit, log -L per entendre l'evolució, i git show <sha> per llegir el commit complet amb el seu missatge.

  1. Ignorar commits de format: --ignore-rev i .git-blame-ignore-revs

Aquest apartat resol el problema que més enverina el blame en projectes reals.

L'equip de gestor-tasques decideix adoptar Prettier. L'Ana l'executa sobre tot el projecte i confirma:

npx prettier --write .
git commit -am "Aplica el format de Prettier a tot el projecte"
[main a3f9c2e] Aplica el format de Prettier a tot el projecte
 3 files changed, 412 insertions(+), 398 deletions(-)

Un sol commit, cap canvi de comportament... i a partir d'ara git blame app.js atribueix a l'Ana gairebé totes les línies del fitxer. Tota la informació històrica ha quedat sepultada sota un commit cosmètic.

-w ajuda si el canvi va ser només d'espaiat, però Prettier també reordena, parteix línies llargues i canvia cometes: -w no n'hi ha prou.

--ignore-rev

git blame --ignore-rev a3f9c2e app.js

Git analitza el fitxer com si aquell commit no existís a efectes d'atribució. Les línies que va canviar s'atribueixen al commit anterior que les va tocar de debò. Si una línia no es pot reassignar netament, Git la marca amb un asterisc * per avisar que l'atribució és aproximada.

.git-blame-ignore-revs

Repetir --ignore-rev a mà no escala. La solució és un fitxer versionat amb la llista de commits a ignorar:

# .git-blame-ignore-revs
#
# Commits purament cosmètics que no han d'aparèixer a git blame.
# Requereix: git config blame.ignoreRevsFile .git-blame-ignore-revs
#
# Aplica el format de Prettier a tot el projecte (2026-07-25)
a3f9c2e7b1d4c8f2a6e9b3d5c7f1a4e8b2d6c9f3

# Migra a cometes simples a tot el JavaScript (2026-06-02)
7d1c4a8f3e6b9d2c5a8f1e4b7d3c6a9f2e5b8d1c

# Reindenta estils.css a 2 espais (2026-05-14)
2e9b5d8c1f4a7e3b6d9c2f5a8e1b4d7c3f6a9e2b

Dues regles de format que cal respectar:

  • Hashos complets de 40 caràcters. Els abreujats no valen; Git els rebutja amb un error.
  • Comentaris amb #. Documenta què era cada commit: d'aquí a un any ningú ho recordarà.

Per aconseguir el hash complet:

git rev-parse a3f9c2e

Ara s'activa a la configuració (lliçó 01-05):

git config blame.ignoreRevsFile .git-blame-ignore-revs

I a partir d'aquell moment, tots els git blame del repositori l'apliquen sols:

git blame app.js
b7e2c4a9 (Carla Vidal  2026-02-14 16:03:55 +0100  43) function normalitzaText(text) {
3f1a8d6c (Ana Ferrer   2026-05-30 10:18:22 +0200  45)     .replace(/ /g, ' ')

La història real ha tornat. I com que el fitxer està versionat, tot l'equip se'n beneficia tan bon punt executi aquell git config una vegada —el mateix patró de "fitxer versionat + un git config local" que vam veure amb core.hooksPath a la lliçó 06-01.

Altres opcions relacionades:

git blame --ignore-revs-file altra-llista.txt app.js  # usar un altre fitxer puntualment
git blame --ignore-rev HEAD app.js                   # ignorar l'últim commit
git config blame.markIgnoredLines true               # marcar amb '?' les línies afectades
git config blame.markUnblamableLines true            # marcar amb '*' les no reassignables

Com fer-ho bé des del principi: quan vagis a aplicar un reformatatge massiu, fes-lo en un commit propi i aïllat que no toqui res més, i afegeix-lo al .git-blame-ignore-revs al commit següent. Un reformatatge barrejat amb canvis funcionals és impossible d'ignorar netament i contamina l'historial per sempre. Aquesta disciplina és part del que veurem a la lliçó 08-02.

Nota pràctica: les plataformes d'allotjament (GitHub, GitLab) també llegeixen .git-blame-ignore-revs i l'apliquen a la seva vista de blame, així que el fitxer serveix tant en local com al web.

  1. Resolent el misteri de normalitzaText

Recapitulant la investigació completa de la Carla, que és el procediment estàndard davant de qualsevol "això per a què hi és?":

# 1. Qui va tocar aquestes línies per última vegada? Ignorant soroll.
git blame -w -C --date=short -L :normalitzaText:app.js app.js
b7e2c4a9 (Carla Vidal 2026-02-14 43) function normalitzaText(text) {
b7e2c4a9 (Carla Vidal 2026-02-14 44)   return text
3f1a8d6c (Ana Ferrer  2026-05-30 45)     .replace(/ /g, ' ')
3f1a8d6c (Ana Ferrer  2026-05-30 46)     .replace(/[​-‍]/g, '')
b7e2c4a9 (Carla Vidal 2026-02-14 47)     .trim();
b7e2c4a9 (Carla Vidal 2026-02-14 48) }
# 2. Què deia el commit que va afegir el que és estrany?
git show 3f1a8d6 --stat
commit 3f1a8d6c9e2b4a7f1d5c8e3b6a9f2d4c7e1b5a8f
Author: Ana Ferrer <[email protected]>
Date:   Fri May 30 10:18:22 2026 +0200

    Elimina els caràcters invisibles en normalitzar el text de les tasques

    En enganxar text des del gestor de correu, les tasques arribaven amb
    espais durs (U+00A0) i marques d'amplada zero. [...]

 app.js | 2 ++
# 3. Quina ha estat l'evolució completa de la funció?
git log -L :normalitzaText:app.js --oneline
# 4. Qui més fa servir aquesta funció? (pickaxe, lliçó 02-06)
git log -S "normalitzaText" --oneline
grep -n "normalitzaText" *.js
b7e2c4a9 Normalitza el text de les tasques abans de desar-les
3f1a8d6c Elimina els caràcters invisibles en normalitzar el text de les tasques
c9a2e7f4 Mou les funcions auxiliars d'index.html a app.js

Quatre ordres, dos minuts, i una resposta definitiva: la funció no es toca, i ara la Carla sap a més que hauria de portar un comentari explicatiu. L'hi afegeix:

/**
 * Normalitza el text d'una tasca abans de desar-la.
 *
 * Elimina espais durs (U+00A0) i marques d'amplada zero (U+200B-U+200D,
 * U+FEFF), que arriben en enganxar text des del gestor de correu i provoquen
 * que dues tasques visualment idèntiques es tractin com a diferents.
 * Vegeu el commit 3f1a8d6.
 */
function normalitzaText(text) {
  return text
    .replace(/ /g, ' ')
    .replace(/[​-‍]/g, '')
    .trim();
}

Fixa't en el detall: el comentari referencia el commit. És la manera de connectar el codi amb l'explicació completa sense repetir-la, i estalvia a la persona següent tota aquesta investigació.

  1. Blame a la pràctica: atribució, no culpa

El nom de l'ordre és un accident històric desafortunat. En anglès, to blame és "culpar", i això ha creat una associació desagradable: la idea que git blame serveix per assenyalar el responsable d'una nyap.

No és per a això. Git té fins i tot un àlies amb millor nom, heretat d'altres sistemes:

git annotate app.js      # el mateix, amb format lleugerament diferent

I "anotar" descriu molt millor el que fa: enriquir cada línia amb la seva procedència.

Els usos legítims, tots ells constructius:

  • Entendre el perquè. El cas d'aquesta lliçó. El missatge del commit conté el context que el codi no pot expressar.
  • Trobar a qui preguntar. No per retreure res, sinó perquè qui va escriure alguna cosa fa tres mesos té context que tu no tens.
  • Datar un canvi. "Aquesta línia és anterior o posterior a la migració?"
  • Avaluar el risc de tocar alguna cosa. Codi de fa tres anys que ningú ha tocat i del qual ningú es recorda: cal procedir amb cura.
  • Documentar cap enrere. Com acaba de fer la Carla.

I això porta a la conclusió de fons de la lliçó, que convé enunciar sense embuts:

git blame val exactament el que valen els teus missatges de commit.

Si el commit que apareix diu "Elimina els caràcters invisibles en normalitzar el text de les tasques" amb tres línies explicant el perquè, blame t'ha resolt el problema. Si diu "fix", "canvis" o "asdf", blame t'ha dit qui i quan, però no per què, que era l'única cosa que necessitaves.

Aquesta és la connexió directa amb la lliçó 08-01: Escrivint Bons Missatges de Confirmació. Allà veurem com s'escriu un missatge que expliqui la intenció i no la mecànica. I l'argument definitiu per fer-ho bé és aquest: no escrius el missatge per a avui, l'escrius per al git blame que algú farà d'aquí a dos anys —i aquest algú probablement seràs tu.

De la mateixa manera, els commits atòmics (lliçó 08-02) fan que el blame assenyali un canvi comprensible en lloc d'un commit de 900 línies titulat "feina del divendres".

Errors Habituals i Consells

Error 1: creure que blame diu qui va escriure la línia. Diu qui la va tocar per última vegada. Sense -w, -M, -C i --ignore-rev, la resposta sol ser "l'últim que va passar el formatador".

Error 2: fer blame del fitxer sencer. Centenars de línies de sortida per investigar-ne cinc. Fes servir sempre -L, i sobretot -L :funcio:fitxer.

Error 3: aturar-se al primer commit que apareix. Si és un moviment o un reformatatge, continua estirant del fil amb git blame <sha>^ -- <fitxer> o passa directament a git log -L.

Error 4: buscar amb blame una línia que ja no existeix. blame només veu l'estat actual. Per a codi esborrat, git log -L, git log -S "text" (pickaxe) o git log -p -- <fitxer>.

Error 5: hashos abreujats a .git-blame-ignore-revs. Git exigeix els 40 caràcters i falla amb un error poc clar si no els poses. Obtén-los amb git rev-parse.

Error 6: barrejar reformatatge i canvis funcionals en un commit. Aquell commit ja no es pot ignorar netament i contamina el blame del fitxer per sempre. El reformatatge, sempre en el seu propi commit.

Error 7: fer servir blame per repartir responsabilitats. A més de ser tòxic, és tècnicament poc fiable: l'atribució depèn de les opcions que facis servir.

Consell 1: crea un àlies. El veuràs a la lliçó 06-04, però avança'l ja:

git config --global alias.culpa "blame -w -C --date=short"

Consell 2: encadena les tres eines. blame per localitzar, log -L per a l'evolució, show per al commit complet. És el procediment estàndard i no falla.

Consell 3: quan resolguis un misteri, documenta'l al codi. Un comentari amb la referència al commit estalvia la investigació als següents.

Consell 4: blame a l'editor. Pràcticament tots els editors moderns tenen integració (GitLens al VS Code, :Git blame al Vim amb fugitive, l'anotació integrada d'IntelliJ). Ensenyen el commit de la línia del cursor sense sortir del fitxer. Configura'ls amb -w si et deixen.

Consell 5: combina'l amb bisect. Quan bisect (lliçó 06-02) et dona el commit culpable, blame sobre les línies que va canviar et diu què hi havia abans i per què estava així.

Exercicis

Exercici 1: blame bàsic i acotat

Crea un repositori amb un fitxer app.js que evolucioni en cinc commits de tres autors diferents (pots canviar d'autor amb git commit --author="Nom <correu>"). Després:

  1. Executa git blame app.js i comprova que cada línia s'atribueix al commit correcte.
  2. Acota amb -L a un rang de tres línies.
  3. Fes servir -L :nomFuncio:app.js per acotar a una funció.
  4. Prova -s, -e i --date=short i compara les sortides.

Exercici 2: l'efecte d'un reformatatge i com ignorar-lo

Sobre el repositori anterior:

  1. Fes un commit que reindenti tot el fitxer (de 2 a 4 espais) sense canviar res més.
  2. Executa git blame i comprova que ara totes les línies són d'aquell commit.
  3. Comprova que -w recupera l'atribució original.
  4. Fes un altre commit que canviï les cometes dobles per simples a tot el fitxer, i comprova que -w ja no n'hi ha prou.
  5. Crea un .git-blame-ignore-revs amb aquell commit, configura blame.ignoreRevsFile i comprova que l'atribució torna.

Exercici 3: la història completa d'una funció

Sobre el mateix repositori:

  1. Fes servir git log -L :nomFuncio:app.js i descriu l'evolució de la funció.
  2. Afegeix una línia a la funció, confirma-la, i esborra-la en un altre commit.
  3. Comprova que git blame no mostra rastre d'aquella línia, però git log -L .
  4. Localitza aquella mateixa línia esborrada fent servir la cerca «pickaxe» git log -S de la lliçó 02-06.

Solucions

Solució 1:

mkdir /tmp/practica-blame && cd /tmp/practica-blame
git init -b main

cat > app.js <<'FI'
function normalitzaText(text) {
  return text.trim();
}
FI
git add . && git commit -q -m "Afegeix la normalitzacio basica de text" \
  --author="Carla Vidal <[email protected]>"

cat > app.js <<'FI'
function normalitzaText(text) {
  return text.trim();
}

function desaTasques(tasques) {
  localStorage.setItem("tasques", JSON.stringify(tasques));
}
FI
git commit -qam "Afegeix el desament de tasques a localStorage" \
  --author="Ana Ferrer <[email protected]>"

cat > app.js <<'FI'
function normalitzaText(text) {
  return text
    .replace(/ /g, " ")
    .trim();
}

function desaTasques(tasques) {
  localStorage.setItem("tasques", JSON.stringify(tasques));
}
FI
git commit -qam "Converteix els espais durs en normalitzar" \
  --author="Bruno Salas <[email protected]>"
git blame --date=short app.js
5f2a8c1 (Carla Vidal 2026-08-01 1) function normalitzaText(text) {
9d3e7b4 (Bruno Salas 2026-08-01 2)   return text
9d3e7b4 (Bruno Salas 2026-08-01 3)     .replace(/ /g, " ")
9d3e7b4 (Bruno Salas 2026-08-01 4)     .trim();
5f2a8c1 (Carla Vidal 2026-08-01 5) }
5f2a8c1 (Carla Vidal 2026-08-01 6)
2c7f4a9 (Ana Ferrer  2026-08-01 7) function desaTasques(tasques) {

Fixa't en un detall instructiu: la línia 2 s'atribueix al Bruno encara que el return text conceptualment el va escriure la Carla. És perquè el Bruno va modificar aquella línia en partir l'expressió en diverses. blame opera sobre línies, no sobre intencions.

git blame -L 1,3 app.js
git blame -L :normalitzaText:app.js
git blame -s app.js          # només hash i línia
git blame -e app.js          # amb el correu

Solució 2:

# 1. Reformatatge d'indentació
sed -i 's/^  /    /' app.js
sed -i 's/^    \./        ./' app.js
git commit -qam "Reindenta app.js a 4 espais" \
  --author="Ana Ferrer <[email protected]>"

# 2. Ara tot és de l'Ana
git blame --date=short app.js
4a8c2e7 (Ana Ferrer 2026-08-01 2)     return text
4a8c2e7 (Ana Ferrer 2026-08-01 3)         .replace(/ /g, " ")
4a8c2e7 (Ana Ferrer 2026-08-01 4)         .trim();
# 3. Amb -w torna l'atribució real
git blame -w --date=short app.js
9d3e7b4 (Bruno Salas 2026-08-01 2)     return text
9d3e7b4 (Bruno Salas 2026-08-01 3)         .replace(/ /g, " ")
# 4. Canvi de cometes: -w ja no n'hi ha prou
sed -i 's/"/'"'"'/g' app.js
git commit -qam "Migra a cometes simples a tot el JavaScript" \
  --author="Ana Ferrer <[email protected]>"

git blame -w --date=short app.js
1b6d9f3 (Ana Ferrer 2026-08-01 3)         .replace(/ /g, ' ')
1b6d9f3 (Ana Ferrer 2026-08-01 8)     localStorage.setItem('tasques', ...

El canvi no és d'espaiat, així que -w no el travessa.

# 5. El fitxer de commits ignorats
git rev-parse HEAD > /tmp/hash.txt
{
  echo "# Commits cosmetics ignorats a git blame"
  echo "#"
  echo "# Migra a cometes simples a tot el JavaScript"
  cat /tmp/hash.txt
} > .git-blame-ignore-revs

git add .git-blame-ignore-revs
git commit -qm "Afegeix la llista de commits ignorats a blame"
git config blame.ignoreRevsFile .git-blame-ignore-revs

git blame -w --date=short app.js
9d3e7b4 (Bruno Salas 2026-08-01 3)         .replace(/ /g, ' ')
2c7f4a9 (Ana Ferrer  2026-08-01 8)     localStorage.setItem('tasques', ...

L'atribució real ha tornat, i ara és automàtica per a tot el repositori.

Solució 3:

git log -L :normalitzaText:app.js --oneline
1b6d9f3 Migra a cometes simples a tot el JavaScript
4a8c2e7 Reindenta app.js a 4 espais
9d3e7b4 Converteix els espais durs en normalitzar
5f2a8c1 Afegeix la normalitzacio basica de text

Quatre commits: dos de cosmètics i dos de contingut real. La pel·lícula completa, inclòs el que blame amb --ignore-revs amaga deliberadament.

# 2. Afegir i esborrar una línia
sed -i "s|        .trim();|        .replace(/\\\\s+/g, ' ')\n        .trim();|" app.js
git commit -qam "Comprimeix els espais multiples en normalitzar"

sed -i "/replace(\/\\\\s+\/g/d" app.js
git commit -qam "Reverteix la compressio d'espais: trencava les tabulacions"
# 3. blame no la veu; log -L sí
git blame app.js | grep "s+" || echo "(blame no mostra la línia esborrada)"
git log -L :normalitzaText:app.js --oneline | head -3
(blame no mostra la línia esborrada)
8e2c5f1 Reverteix la compressio d'espais: trencava les tabulacions
3d9a7b4 Comprimeix els espais multiples en normalitzar
1b6d9f3 Migra a cometes simples a tot el JavaScript
# 4. El pickaxe la troba igualment
git log -S "\\s+" --oneline -- app.js
8e2c5f1 Reverteix la compressio d'espais: trencava les tabulacions
3d9a7b4 Comprimeix els espais multiples en normalitzar

-S compta aparicions de la cadena i mostra els commits on aquest número canvia: el que la va introduir i el que la va treure. És l'eina per al que ja no hi és.

Conclusió

git blame converteix l'historial en una anotació línia a línia del codi que tens al davant. L'essencial:

  • Respon a "quin commit va tocar aquesta línia per última vegada?", sobre l'estat actual del fitxer. No diu qui la va escriure originalment, i no veu les línies esborrades.
  • La sortida té cinc columnes —hash, autor, data, número de línia i contingut—; ^ marca el commit frontera i 00000000 els canvis sense confirmar.
  • -L acota, i -L :funcio:fitxer és la manera més còmoda de fer-ho sense comptar línies.
  • -w, -M i -C són el que separa una atribució útil d'una d'inútil: ignoren l'espaiat i segueixen el codi mogut o copiat, dins i entre fitxers, amb nivells creixents (-C, -C -C, -C -C -C).
  • git blame <sha>^ -- <fitxer> salta per damunt d'un commit per veure què hi havia abans: la tècnica manual per estirar del fil.
  • git log -L dona la pel·lícula completa on blame dona la foto: tots els commits que van afectar un rang, amb els seus diffs, incloses les línies esborrades.
  • --ignore-rev i .git-blame-ignore-revs (amb blame.ignoreRevsFile) neutralitzen els reformatatges massius i tornen la història real. Perquè funcioni, el reformatatge ha d'estar en el seu propi commit aïllat.
  • El procediment estàndard davant de qualsevol "això per a què hi és?": blamelog -Lshow, i grep/log -S per veure qui més en depèn.
  • I el més important: és atribució, no culpa, i val el que valguin els teus missatges de commit (lliçó 08-01).

La Carla i l'equip ja tenen un arsenal de consulta considerable: log amb els seus filtres i la seva cerca «pickaxe», bisect, blame, log -L, show. El problema és que les invocacions útils són cada cop més llargues —git blame -w -C --date=short -L :funcio:app.js app.js no s'escriu a mà dues vegades— i que hi ha vistes de l'historial que encara no sabem demanar: el graf complet de branques, només les fusions, qui ha contribuït quant, o un format d'una línia amb colors que es llegeixi d'un cop d'ull.

Toca pujar de nivell amb git log i, sobretot, deixar d'escriure tot això a mà. És la lliçó 06-04: Git Log i Àlies.

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