La Carla obre una incidència amb un títol incòmode: "El botó d'esborrar tasca no esborra res". Ho reprodueix en tres navegadors. Prems la paperera, la tasca parpelleja i continua allà.
El que desconcerta és que funcionava. A la versió v1.0.0, que l'equip va etiquetar fa dos mesos (lliçó 05-05), l'esborrat funcionava perfectament: hi ha una demostració gravada que ho prova. Entre aquella etiqueta i main hi ha 214 commits.
Les opcions evidents són dolentes. Llegir-se els 214 diffs és un dia de feina i una garantia de no veure res. Buscar per paraules clau amb git log -S "esborrar" (lliçó 02-06) dona vint-i-tants candidats, i cap no sembla culpable. I preguntar al xat de l'equip només produeix teories.
Però hi ha una propietat del problema que ho canvia tot: l'historial està ordenat, i en algun punt d'aquesta seqüència el comportament va passar de "funciona" a "no funciona". Això és exactament l'escenari d'una cerca binària, i Git en porta una implementació integrada: git bisect. Amb ella, aquests 214 commits es redueixen a vuit proves.
Contingut
- La idea: cerca binària sobre l'historial
- Quants passos calen de veritat
- Una sessió manual completa
git bisect skip: commits que no es poden provargit bisect logireplay: no perdre la feina- Automatitzar amb
git bisect run - L'script de prova per a
gestor-tasques - Els codis de sortida, amb detall
--first-parent: saltar-se l'interior de les fusionsgit bisect terms: quan no és "bo/dolent"- Què fa que un historial sigui bisecable
- La idea: cerca binària sobre l'historial
La premissa de bisect és una de sola:
Existeix un commit concret a partir del qual el comportament va canviar. Abans d'ell, bé; des d'ell, malament.
Si això es compleix, no cal provar els commits un a un. N'hi ha prou de provar el del mig:
- Si el del mig està malament, el culpable és a la primera meitat. La segona meitat es descarta sencera.
- Si el del mig està bé, el culpable és a la segona meitat. La primera es descarta sencera.
I es repeteix sobre la meitat que queda. Cada prova elimina la meitat dels candidats.
flowchart TD
A["214 candidats<br/>v1.0.0 (bé) ... main (malament)"] --> B["Provar el commit del mig"]
B -- "funciona → good" --> C["107 candidats<br/>(meitat superior)"]
B -- "no funciona → bad" --> D["107 candidats<br/>(meitat inferior)"]
C --> E["Provar el nou mig"]
D --> E
E --> F["54 → 27 → 13 → 7 → 3 → 2 → 1"]
F --> G["Commit culpable identificat"]
Git fa tota la feina de logística: calcula quin commit cal provar, fa el checkout per tu (en detached HEAD, lliçó 03-02), porta el compte dels descartats i al final et diu el hash exacte. Tu només respons una pregunta binària a cada pas: funciona o no?
- Quants passos calen de veritat
El nombre de proves és el logaritme en base 2 del nombre de candidats, arrodonit cap amunt. La diferència amb la cerca lineal és brutal i val la pena veure-la en una taula:
| Commits entre bo i dolent | Proves amb bisect (log₂) |
Proves una a una (pitjor cas) |
|---|---|---|
| 8 | 3 | 8 |
| 32 | 5 | 32 |
| 100 | 7 | 100 |
| 214 | 8 | 214 |
| 1 000 | 10 | 1 000 |
| 10 000 | 14 | 10 000 |
| 1 000 000 | 20 | 1 000 000 |
Aquest és el titular: duplicar la mida de l'historial afegeix una sola prova. Un repositori amb un milió de commits es biseca en vint passos. És la raó per la qual bisect continua sent útil per gran que sigui el projecte.
El mateix Git t'ho diu en començar:
"106 revisions per provar, aproximadament 7 passos". No és una estimació optimista: és matemàtica.
- Una sessió manual completa
La Carla comença. El primer, deixar l'arbre de treball net: bisect farà molts checkout, i no pot si hi ha canvis sense confirmar. Si en té, git stash (lliçó 05-04).
Pas 1: iniciar la sessió.
No respon res. Git ha entrat en mode bisecció: ha creat referències internes a .git/refs/bisect/ i un fitxer .git/BISECT_LOG.
Pas 2: marcar els dos extrems.
git bisect bad # el commit actual (main) està malament
git bisect good v1.0.0 # l'etiqueta v1.0.0 estava béBisecting: 106 revisions left to test after this (roughly 7 steps) [9f3c7a1e5b2d8c4f6a0e3b7d9c1f5a8e2b4d6c9f] Extreu la validació del formulari a la seva funció
Fixa't en el que ha passat: Git ha fet checkout del commit intermedi. L'arbre de treball de la Carla és ara el del commit 9f3c7a1, i HEAD està desacoblat:
HEAD detached at 9f3c7a1 You are currently bisecting, started from main. (use "git bisect reset" to get back to the original branch) nothing to commit, working tree clean
Pas 3: el bucle. La Carla obre index.html al navegador, crea una tasca, prem la paperera:
Funciona. Ho marca:
Bisecting: 53 revisions left to test after this (roughly 6 steps) [4e8b2c9d7f1a3e5c6b0d8f2a4c7e9b1d3f5a7c8e] Afegeix el filtre per estat de la tasca
De 106 a 53 d'un cop. I així successivament:
Bisecting: 26 revisions left to test after this (roughly 5 steps) [7a1f5c3e9b2d6a8c4f0e7b3d5a9c1e6f2b8d4a7c] Reescriu el pintat del llistat
Bisecting: 12 revisions left to test after this (roughly 4 steps) [2c6e9a4f8b1d7c3e5a0f9b2d6c8e4a1f7b3d5c9e] Extreu els estils del llistat a estils.css
La Carla continua: good, bad, good, bad... Cinc proves més tard:
8d4f2a7c1e6b9d3f5a8c0e2b4d7f9a1c3e5b8d6f is the first bad commit commit 8d4f2a7c1e6b9d3f5a8c0e2b4d7f9a1c3e5b8d6f Author: Bruno Salas <[email protected]> Date: Mon Jun 22 11:14:38 2026 +0200 Delega els esdeveniments del llistat al contenidor app.js | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-)
Vuit proves. 214 commits. Un culpable amb nom, data i diff.
Pas 4: mirar el diff.
diff --git a/app.js b/app.js
--- a/app.js
+++ b/app.js
@@ -78,18 +78,16 @@
- document.querySelectorAll('.esborrar').forEach((boto) => {
- boto.addEventListener('click', (e) => {
- esborraTasca(e.target.dataset.id);
- });
- });
+ llista.addEventListener('click', (e) => {
+ if (e.target.classList.contains('esborrar')) {
+ esborraTasca(e.target.dataset.id);
+ }
+ });Aquí ho tens. El Bruno va canviar els escoltadors individuals per delegació d'esdeveniments al contenidor, que és una millora legítima. Però el botó d'esborrar conté un <svg> amb la icona de paperera, així que en prémer, e.target és el <svg>, no el botó: classList.contains('esborrar') dona false i no passa res. Amb e.target.closest('.esborrar') funcionaria.
Sense bisect, trobar això hauria portat hores. Amb ell, vuit proves i un diff de dotze línies.
Pas 5: sortir. I aquest pas no és opcional:
Previous HEAD position was 8d4f2a7 Delega els esdeveniments del llistat al contenidor Switched to branch 'main'
reset neteja l'estat de bisecció i torna HEAD on era en començar. Si te n'oblides, et quedes en detached HEAD sobre un commit antic, i el següent git status serà una sorpresa desagradable.
Resum de les ordres de la sessió:
| Ordre | Què fa |
|---|---|
git bisect start |
Inicia la sessió |
git bisect bad [<sha>] |
Marca un commit com a defectuós (per defecte, HEAD) |
git bisect good [<sha>] |
Marca un commit com a correcte |
git bisect start <dolent> <bo> |
Drecera: inicia i marca els dos extrems de cop |
git bisect skip |
"No puc provar aquest" (apartat 4) |
git bisect log |
Mostra la sessió fins ara |
git bisect replay <fitxer> |
Reprodueix una sessió desada |
git bisect visualize (o view) |
Obre gitk/log amb els candidats restants |
git bisect run <ordre> |
Automatitza tota la sessió (apartat 6) |
git bisect reset [<ref>] |
Acaba i torna al punt de partida |
La drecera de l'inici estalvia dues ordres i és la que es fa servir a la pràctica:
git bisect skip: commits que no es poden provar
git bisect skip: commits que no es poden provarA la sisena prova, la Carla es troba això:
python3 -m http.server 8000
# La pàgina apareix completament en blanc. La consola del navegador diu:
# Uncaught SyntaxError: Unexpected token '}' a app.js:96Aquest commit està trencat per un altre motiu. No és que l'esborrat no funcioni: és que l'aplicació sencera no arrenca. Marcar-lo bad seria mentir a bisect i el podria portar pel camí equivocat; marcar-lo good, encara pitjor.
La resposta correcta és:
Bisecting: 6 revisions left to test after this (roughly 3 steps) [5b8e2d4a9c7f1e3b6d0a8c2f4e7b9d1a3c5f8e2b] Corregeix la clau sobrant a app.js
skip li diu a Git: "aquest commit no em dona informació". Git tria un altre candidat proper i continua. Si acabes saltant-te'n massa i no en pot aïllar un de sol, t'ho dirà:
There are only 'skip'ped commits left to test. The first bad commit could be any of: 3e7a1c5 ... 8d4f2a7 ... b2c9e4f ... We cannot bisect more!
En aquest cas et dona un conjunt petit de sospitosos, que ja és infinitament millor que 214.
També es poden saltar rangs sencers, si saps que tota una zona no compila:
Motius habituals per fer skip: el commit no compila, falten dependències que encara no eren al package.json, o el commit és d'un canvi de format massiu que fa impossible executar res. skip és el teu amic; mentir a bisect no.
git bisect log i replay: no perdre la feina
git bisect log i replay: no perdre la feinaA mitja bisecció llarga pot passar de tot: t'equivoques en marcar, es tanca el terminal, o has d'atendre una altra cosa. git bisect log desa la sessió:
git bisect start # status: waiting for both good and bad commits # bad: [c8f2a1e...] Afegeix el comptador de tasques pendents git bisect bad c8f2a1e... # status: waiting for good commit(s), bad commit known # good: [1a5c9f3...] Versió 1.0.0 git bisect good 1a5c9f3... # good: [9f3c7a1...] Extreu la validació del formulari a la seva funció git bisect good 9f3c7a1... # bad: [4e8b2c9...] Afegeix el filtre per estat de la tasca git bisect bad 4e8b2c9...
Desar-ho i recuperar-ho després:
git bisect log > /tmp/sessio-esborrat.txt
git bisect reset # deixar-ho per avui
# Demà, o en una altra màquina:
git bisect replay /tmp/sessio-esborrat.txtreplay reconstrueix la sessió fins on vas arribar i et deixa al següent commit per provar.
I aquí hi ha el seu altre ús, el més valuós: corregir un error de marcatge. Si t'adones que vas dir good on havies de dir bad, no cal començar de zero:
git bisect log > /tmp/sessio.txt
# Editar /tmp/sessio.txt: treure la línia equivocada i les posteriors
git bisect reset
git bisect replay /tmp/sessio.txt
- Automatitzar amb
git bisect run
git bisect runLa sessió manual funciona, però vuit voltes d'"obrir el navegador, crear una tasca, prémer, marcar" són vuit oportunitats d'equivocar-se i vint minuts de la vida de la Carla.
Si la comprovació es pot escriure com una ordre que retorna 0 quan està bé i alguna cosa diferent de 0 quan està malament, git bisect run ho fa tot sol:
Git prova, executa l'ordre, interpreta el codi de sortida, marca, avança i repeteix fins a trobar el culpable. Sense intervenció humana.
Amb una bateria de proves, és literalment això:
I per a un sol cas, pot ser prou una ordre d'una línia:
# Encara existeix la funció esborraTasca a app.js?
git bisect run grep -q "function esborraTasca" app.jsgrep -q retorna 0 si troba, 1 si no. És exactament el contracte que bisect run espera.
- L'script de prova per a
gestor-tasques
gestor-tasquesCom que la fallada de la Carla és de comportament al navegador, cal un script una mica més elaborat. L'equip té una prova automatitzada amb Node:
#!/usr/bin/env bash
#
# eines/provar-esborrat.sh
# Retorna 0 si l'esborrat de tasques funciona, 1 si no.
# Pensat per a 'git bisect run'.
set -uo pipefail # ATENCIÓ: sense -e, volem controlar els codis a mà
# 1. Hi són els fitxers que necessitem? Si no, aquest commit no és avaluable.
if [ ! -f app.js ] || [ ! -f index.html ]; then
echo "→ Falten fitxers del projecte: commit no avaluable"
exit 125
fi
# 2. Dependències: s'instal·len sense soroll; si falla, no és culpa del codi
if [ -f package.json ]; then
npm ci --silent >/dev/null 2>&1 || {
echo "→ npm ci ha fallat: commit no avaluable"
exit 125
}
fi
# 3. El JavaScript és sintàcticament vàlid? Si no, tampoc és avaluable
if ! node --check app.js >/dev/null 2>&1; then
echo "→ app.js no és sintàcticament vàlid: commit no avaluable"
exit 125
fi
# 4. La prova de veritat
if node eines/prova-esborrat.mjs >/dev/null 2>&1; then
echo "→ L'esborrat FUNCIONA"
exit 0
else
echo "→ L'esborrat NO funciona"
exit 1
fiI la prova en si, que simula el DOM mínim necessari:
// eines/prova-esborrat.mjs
// Prova mínima: crear una tasca, esborrar-la i comprovar que desapareix.
import { JSDOM } from 'jsdom';
import { readFileSync } from 'node:fs';
const html = readFileSync('index.html', 'utf8');
const dom = new JSDOM(html, { runScripts: 'outside-only' });
const { document } = dom.window;
// Exposar l'entorn del navegador que espera app.js
globalThis.window = dom.window;
globalThis.document = document;
globalThis.localStorage = dom.window.localStorage;
// Carregar l'aplicació
dom.window.eval(readFileSync('app.js', 'utf8'));
// 1. Crear una tasca
const camp = document.querySelector('#nova-tasca');
const formulari = document.querySelector('#formulari-tasca');
camp.value = 'Tasca de prova del bisect';
formulari.dispatchEvent(new dom.window.Event('submit'));
if (document.querySelectorAll('.tasca').length !== 1) {
console.error('La tasca no s\'ha creat: la prova no és concloent');
process.exit(125); // no avaluable, no és la fallada que busquem
}
// 2. Prémer la icona de la paperera (el <svg> dins del botó)
const icona = document.querySelector('.tasca .esborrar svg')
?? document.querySelector('.tasca .esborrar');
icona.dispatchEvent(new dom.window.MouseEvent('click', { bubbles: true }));
// 3. Ha desaparegut?
const queden = document.querySelectorAll('.tasca').length;
process.exit(queden === 0 ? 0 : 1);Els tres punts que fan que aquest script funcioni bé amb bisect:
- Prova una sola cosa. Només l'esborrat. Si la prova fallés també per altres motius,
bisecttrobaria el primer commit que trenca qualsevol cosa, que no és el que busquem. - Distingeix "no funciona" de "no es pot avaluar". Aquest és el paper del
125, i és el que veurem ara. - És silenciós i determinista. Sense sortida interactiva, sense dependre de la xarxa, sense fallades intermitents. Una prova que a vegades falla enverina tota la bisecció.
Llançant-lo:
running './eines/provar-esborrat.sh' → L'esborrat FUNCIONA Bisecting: 53 revisions left to test after this (roughly 6 steps) [4e8b2c9] Afegeix el filtre per estat de la tasca running './eines/provar-esborrat.sh' → L'esborrat NO funciona Bisecting: 26 revisions left to test after this (roughly 5 steps) ... 8d4f2a7c1e6b9d3f5a8c0e2b4d7f9a1c3e5b8d6f is the first bad commit commit 8d4f2a7c1e6b9d3f5a8c0e2b4d7f9a1c3e5b8d6f Author: Bruno Salas <[email protected]> Date: Mon Jun 22 11:14:38 2026 +0200 Delega els esdeveniments del llistat al contenidor app.js | 22 ++++++++++------------ bisect run success
Quaranta segons, sense tocar el teclat. I un advertiment sobre el #!: l'script ha de ser executable (chmod +x) i estar fora de l'arbre de treball o present a tots els commits. Si viu dins del repositori i als commits antics no existia, bisect no el trobarà en fer checkout d'aquells commits. La solució habitual és copiar-lo a /tmp i executar-lo des d'allà:
- Els codis de sortida, amb detall
Aquest és el contracte complet de git bisect run, i conèixer-lo és la diferència entre una automatització que funciona i una que dona resultats falsos:
| Codi de sortida | Significat per a bisect |
Quan fer-lo servir |
|---|---|---|
| 0 | El commit és bo | La comprovació passa |
| 1–124 | El commit és dolent | La comprovació falla |
| 125 | Saltar aquest commit (skip) |
No es pot avaluar: no compila, falten dependències |
| 126 | Dolent (l'ordre no és executable) | Error de permisos: revisa'l |
| 127 | Dolent (ordre no trobada) | Error de ruta: revisa-la |
| 128–255 | Avorta la bisecció | Error greu; Git s'atura i avisa |
Tres conseqüències pràctiques:
- El
125és la peça clau. És l'equivalent automàtic degit bisect skip. Sense ell, un commit que no compila es marcaria com a "dolent" ibisectt'assenyalaria un culpable equivocat. - El
126i el127són paranys mortals. Si l'script no és executable o la ruta està malament,bisectmarcarà tots els commits com a dolents i "trobarà" com a culpable el primer del rang. Si el resultat et sembla absurd, comprova primer que l'script s'executa a mà:./el-meu-script.sh; echo $?. - Evita
exit 128i superiors. Unkill -9d'un procés dona 137, per exemple. Si el teu script pot acabar així, embolcalla'l per normalitzar el codi de sortida.
Comprovació prèvia obligatòria, sempre, abans de llançar un bisect run:
# En un commit que saps BO
git switch --detach v1.0.0
./eines/provar-esborrat.sh; echo "codi: $?" # ha de ser 0
# En un commit que saps DOLENT
git switch --detach main
./eines/provar-esborrat.sh; echo "codi: $?" # ha de ser 1Trenta segons que eviten bisecar durant deu minuts cap a una resposta inventada.
--first-parent: saltar-se l'interior de les fusions
--first-parent: saltar-se l'interior de les fusionsL'historial de gestor-tasques té fusions (lliçó 03-03), i bisect per defecte explora tots els commits accessibles, inclosos els interns de cada branca fusionada. Això té dos inconvenients:
- Els commits intermedis d'una branca en desenvolupament solen estar a mitges: pot ser que ni compilin, i acabes fent molts
skip. - A efectes de
main, el que importa és en quina fusió va entrar el problema, no quin dels sis commits de la branca el va introduir.
Amb --first-parent, Git només considera la línia principal: els commits directes de main i els commits de fusió, sense entrar dins de les branques. És exactament el mateix concepte de --first-parent que veurem a git log a la lliçó 06-04, i el mateix "primer pare" que triava -m 1 en revertir una fusió (lliçó 05-06).
gitGraph commit id: "v1.0.0" commit id: "a1" branch funcionalitat/esdeveniments commit id: "f1" commit id: "f2" commit id: "f3" checkout main commit id: "a2" merge funcionalitat/esdeveniments id: "M1" commit id: "a3"
Sense --first-parent, els candidats són a1, f1, f2, f3, a2, M1, a3. Amb --first-parent, només a1, a2, M1, a3.
Quan fer servir cadascun:
| Situació | Mode |
|---|---|
L'equip fusiona branques i main sempre està sa |
--first-parent: més ràpid, menys skip |
| Vols el commit exacte dins de la branca culpable | Sense --first-parent |
| L'historial és lineal (rebase abans d'integrar) | Tant se val: no hi ha fusions |
| Molts commits intermedis trencats | --first-parent, gairebé obligat |
La tàctica de dues fases és la millor de les dues: primer --first-parent per identificar la fusió, després una segona bisecció dins d'aquella branca:
git bisect start --first-parent main v1.0.0
git bisect run /tmp/provar.sh
# → resulta ser la fusió M1
git bisect reset
git bisect start M1^2 M1^1 # dins de la branca: dolent=punta, bo=base
git bisect run /tmp/provar.shM1^2 és el segon pare (la punta de la branca fusionada) i M1^1 el primer (la base a main), amb la notació de la lliçó 02-06.
git bisect terms: quan no és "bo/dolent"
git bisect terms: quan no és "bo/dolent"bisect no és només per buscar regressions. Serveix per localitzar qualsevol canvi d'estat monòton a l'historial. Els exemples típics:
- En quin commit es va arreglar una fallada? (aquí "bo" i "dolent" estan invertits)
- En quin commit l'arrencada va passar de trigar 200 ms a 900 ms?
- En quin commit el paquet va passar de 400 KB a 1,2 MB?
En aquests casos, la terminologia per defecte confon. git bisect terms permet reanomenar-la:
git bisect start --term-old=trencat --term-new=arreglat
git bisect trencat v1.0.0
git bisect arreglat mainA partir d'aquí, a cada pas respons git bisect trencat o git bisect arreglat, i Git busca el primer commit arreglat.
| Àlies | Equival a | Significat |
|---|---|---|
--term-old / --term-good |
good |
L'estat antic, el d'abans del canvi |
--term-new / --term-bad |
bad |
L'estat nou, el que busquem |
El que cal entendre és que a Git li són igual les paraules: sempre busca el primer commit amb l'estat "nou", sigui "trencat", "arreglat", "lent" o "gras". Consultar els termes actius en qualsevol moment:
Un exemple de rendiment, mesurant amb el mateix script:
#!/usr/bin/env bash
# /tmp/provar-mida.sh — 0 si el bundle pesa menys de 500 KB
npm ci --silent >/dev/null 2>&1 || exit 125
npm run build --silent >/dev/null 2>&1 || exit 125
bytes=$(wc -c < dist/app.min.js)
echo "→ ${bytes} bytes"
[ "$bytes" -lt 512000 ]git bisect start --term-old=lleuger --term-new=pesat
git bisect lleuger v1.0.0
git bisect pesat main
git bisect run /tmp/provar-mida.shL'última línia de l'script és la que decideix: [ "$bytes" -lt 512000 ] retorna 0 si es compleix i 1 si no, que és just el contracte de bisect run.
- Què fa que un historial sigui bisecable
bisect és una eina que et torna el que li has donat. La seva eficàcia depèn directament de la qualitat de l'historial, i aquí és on el mòdul 5 i aquest es donen la mà:
| Propietat de l'historial | Efecte sobre bisect |
|---|---|
| Cada commit compila i arrenca | La bisecció és neta, sense skip |
| Commits petits i atòmics | El culpable és un diff de 10 línies, no de 800 |
| Un canvi lògic per commit | El diff assenyala la causa directament |
| Missatges descriptius | Entens el perquè del canvi culpable a l'instant |
| Commits gegants "feina del divendres" | Trobes el commit... i continues sense saber quina línia |
| Commits que no compilen | skip continu, resultat difús o inconcloent |
| Branques fusionades amb història bruta | Soroll: es resol amb --first-parent |
Dit d'una altra manera: el rebase interactiu de la lliçó 05-02 no és cosmètica. Quan consolides els fixup!, divideixes un commit gegant i t'assegures que cadascun deixa el projecte en un estat funcional, el que estàs construint és un historial bisecable. La factura de no fer-ho es paga el dia que cal trobar una regressió de fa dos mesos.
I amb --exec (lliçó 05-02) pots comprovar per endavant que tota una branca és bisecable abans d'integrar-la:
Git aplica cada commit i executa la comprovació a cadascun. Si algun falla, t'atura allà.
Tot això —commits atòmics, historial que compila, què consolidar abans d'integrar— és el tema de la lliçó 08-02: Mantenint un Historial Net.
bisectés la millor justificació pràctica que existeix per a aquestes pràctiques: converteix una virtut abstracta en minuts de feina estalviats.
Una última nota d'abast: bisect respon a "quin commit va canviar aquest comportament?". No respon a "per què el meu repositori està en un estat estrany?" ni "què està fent Git per sota?". Aquest diagnòstic —reflog, fsck, GIT_TRACE i companyia— és el terreny de les lliçons del mòdul 9, i en particular de la 09-06: Tècniques Avançades de Depuració.
Errors Habituals i Consells
Error 1: començar amb l'arbre de treball brut. bisect fa checkout a cada pas i fallarà o arrossegarà els teus canvis entre commits. Confirma o fes git stash abans.
Error 2: oblidar el git bisect reset. Et quedes en detached HEAD sobre un commit de juny. Tot commit que hi facis quedarà orfe.
Error 3: invertir good i bad en començar. L'ordre de la drecera és git bisect start <DOLENT> <BO>. Si l'inverteixes, Git buscarà en el sentit contrari i no trobarà res coherent.
Error 4: marcar bad un commit que està trencat per un altre motiu. Fes servir skip. Mentir a bisect produeix un culpable equivocat amb tota l'aparença de ser correcte.
Error 5: oblidar l'exit 125 a l'script. Sense ell, els commits que no compilen compten com a dolents i el resultat és brossa.
Error 6: un script no executable o amb la ruta malament. Codis 126/127: tot surt "dolent" i el culpable és el primer commit del rang. Prova l'script a mà abans.
Error 7: deixar l'script dins del repositori. Als commits antics pot no existir. Copia'l a /tmp i executa'l des de fora.
Error 8: una prova no determinista. Si falla una de cada cinc vegades, bisect et donarà una resposta diferent cada cop. Assegura el determinisme abans d'automatitzar.
Consell 1: tria el "bo" més recent que puguis. Començar a v1.0.0 en lloc del primer commit del projecte estalvia passos. Les etiquetes (lliçó 05-05) són perfectes per a això.
Consell 2: desa sempre el git bisect log. Una sessió llarga és feina valuosa. Un fitxer de text et permet reprendre-la o corregir un marcatge erroni.
Consell 3: primer --first-parent, després dins de la branca. Ràpid per localitzar la fusió, precís per localitzar el commit.
Consell 4: bisect run amb npm test sovint n'hi ha prou. Si ja tens proves, no escriguis res de nou.
Consell 5: quan trobis el culpable, no el rebentis sense més. Mira'l, entén-lo i decideix: git revert si està publicat (lliçó 05-06), o una correcció cap endavant si el canvi en si era bo i només faltava un detall —com en el cas del Bruno, on la delegació d'esdeveniments era correcta i només faltava closest().
Exercicis
Exercici 1: bisecció manual
Crea un repositori amb 15 commits on el fitxer valor.txt conté 10, i a partir del commit número 9 passa a contenir 99 (la "fallada"). Després:
- Inicia una bisecció amb
maincom a dolent i el primer commit com a bo. - A cada pas, comprova
cat valor.txti marcagoodobad. - Comprova que Git identifica exactament el commit 9.
- Quants passos has necessitat? Coincideix amb log₂(14)?
Exercici 2: bisecció automàtica amb run
Amb el mateix repositori de l'exercici 1:
- Escriu un script a
/tmpque retorni 0 sivalor.txtconté10i 1 si no. - Prova l'script a mà en un commit bo i en un de dolent abans de fer-lo servir.
- Llança
git bisect runi comprova que troba el mateix commit. - Afegeix al repositori un commit intermedi en què
valor.txtno existeixi, i fes que l'script retorni125en aquest cas. Comprova que la bisecció continua funcionant.
Exercici 3: termes personalitzats
Crea un repositori de 10 commits on estat.txt conté trencat i a partir del commit 7 passa a contenir arreglat. Fent servir --term-old i --term-new, troba el primer commit arreglat. Escriu també el bisect run corresponent.
Solucions
Solució 1:
mkdir /tmp/practica-bisect && cd /tmp/practica-bisect
git init -b main
for i in $(seq 1 15); do
if [ "$i" -lt 9 ]; then echo "10" > valor.txt; else echo "99" > valor.txt; fi
echo "linia $i" >> registre.txt
git add .
git commit -q -m "Commit numero $i"
done
git log --oneline | tail -1 # el primer commit (bo)Quatre proves per a 14 candidats. log₂(14) ≈ 3,8, que arrodonit cap amunt són 4. Exacte.
Solució 2:
cat > /tmp/provar-valor.sh <<'FI'
#!/usr/bin/env bash
# 0 = bo, 1 = dolent, 125 = no avaluable
[ -f valor.txt ] || { echo "→ sense valor.txt: no avaluable"; exit 125; }
valor=$(cat valor.txt)
echo "→ valor = $valor"
[ "$valor" = "10" ]
FI
chmod +x /tmp/provar-valor.shComprovació prèvia, que mai s'ha de saltar:
cd /tmp/practica-bisect
git switch --detach $(git log --oneline | tail -1 | cut -d' ' -f1)
/tmp/provar-valor.sh; echo "codi: $?"git bisect start main $(git log --oneline | tail -1 | cut -d' ' -f1)
git bisect run /tmp/provar-valor.shrunning '/tmp/provar-valor.sh'
→ valor = 10
Bisecting: 3 revisions left to test after this (roughly 2 steps)
running '/tmp/provar-valor.sh'
→ valor = 99
...
7b2f5a9... is the first bad commit
Commit numero 9
bisect run successEl commit no avaluable:
git switch -c amb-forat main
git rm -q valor.txt && git commit -q -m "Treu temporalment valor.txt"
echo "99" > valor.txt && git add . && git commit -q -m "Restaura valor.txt"
git bisect start amb-forat $(git log --oneline main | tail -1 | cut -d' ' -f1)
git bisect run /tmp/provar-valor.shQuan la bisecció arriba al commit sense valor.txt, l'script retorna 125 i Git el salta:
running '/tmp/provar-valor.sh' → sense valor.txt: no avaluable Bisecting: 1 revision left to test after this (roughly 1 step)
El culpable continua sent el commit 9: el 125 ha evitat que un commit inavaluable falsegés el resultat.
Solució 3:
mkdir /tmp/practica-bisect-terms && cd /tmp/practica-bisect-terms
git init -b main
for i in $(seq 1 10); do
if [ "$i" -lt 7 ]; then echo "trencat" > estat.txt; else echo "arreglat" > estat.txt; fi
echo "linia $i" >> registre.txt
git add . && git commit -q -m "Commit numero $i"
done
primer=$(git log --oneline | tail -1 | cut -d' ' -f1)git bisect start --term-old=trencat --term-new=arreglat
git bisect trencat "$primer"
git bisect arreglat maincat estat.txt # trencat
git bisect trencat
cat estat.txt # arreglat
git bisect arreglat
cat estat.txt # arreglat
git bisect arreglatAmb automatització:
git bisect reset
cat > /tmp/provar-estat.sh <<'FI'
#!/usr/bin/env bash
[ -f estat.txt ] || exit 125
grep -q '^trencat$' estat.txt # 0 = estat antic (trencat), 1 = nou
FI
chmod +x /tmp/provar-estat.sh
git bisect start --term-old=trencat --term-new=arreglat
git bisect trencat "$primer"
git bisect arreglat main
git bisect run /tmp/provar-estat.sh
git bisect resetL'important: encara que els termes es diguin trencat i arreglat, l'script continua retornant 0 per a l'estat antic i 1 per al nou. bisect no interpreta les paraules; només busca la frontera.
Conclusió
git bisect converteix una cerca desesperada en un procediment mecànic i acotat. L'essencial:
- És una cerca binària sobre l'historial: cada prova descarta la meitat dels candidats, així que calen log₂(n) proves. 214 commits són 8 proves; un milió, vint.
- La sessió manual és
start→bad→good→ bucle de proves →reset. Elresetfinal no és opcional: sense ell et quedes en detached HEAD. git bisect skipés la resposta honesta a un commit que no es pot avaluar. Mentir marcantgoodobadprodueix un culpable fals.git bisect logireplaysalven sessions llargues i permeten corregir un marcatge erroni sense començar de zero.git bisect run <ordre>automatitza tot el procés. El contracte són els codis de sortida: 0 bo, 1–124 dolent, 125 saltar, 128+ avortar. El125és el que separa una automatització fiable d'una que inventa respostes.- Prova sempre l'script a mà en un commit bo i en un de dolent abans de llançar
bisect run, i treu-lo de l'arbre de treball (/tmp) perquè existeixi a tots els commits. --first-parentlimita la cerca a la línia principal: menys soroll i menysskip. La tàctica de dues fases —primer la fusió, després dins de la branca— combina velocitat i precisió.git bisect termsgeneralitza l'eina a qualsevol canvi d'estat monòton: quan es va arreglar alguna cosa, quan va començar a anar lent, quan va engreixar el paquet.- I la conclusió de fons: un historial de commits petits que compilen és un historial bisecable. La lliçó 08-02 ho desenvolupa;
bisectés la raó per la qual importa.
La Carla ja té el commit culpable i el diff exacte. Però en mirar app.js per arreglar-lo es troba una altra cosa: vint línies més avall hi ha una funció de normalització de text que ningú sap per què hi és, amb un replace de caràcters estranys que sembla arbitrari. Ningú de l'equip recorda haver-la escrit. Esborrar-la sembla temptador... i probablement trencaria alguna cosa.
Abans de tocar codi que no entens, cal saber qui va escriure cada línia, quan i en quin commit, per poder llegir el missatge que n'explica el perquè. Aquesta és l'eina de la lliçó 06-03: Git Blame.
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ó
