A la lliçó anterior vam aprendre a decidir amb precisió què entra a cada confirmació. Però hi ha una pregunta prèvia que git status no respon: què he canviat exactament?
git status et diu quins fitxers han canviat i en quina zona són. No et diu quines línies. I aquesta diferència és enorme: confirmar sense haver llegit els teus propis canvis és la manera més habitual de ficar a l'historial un console.log de depuració, una contrasenya de proves, un bloc de codi comentat o un canvi que et pensaves haver desfet.
git diff és l'eina que respon aquesta pregunta. En aquesta lliçó veurem les seves tres formes fonamentals —i per què donen resultats diferents segons quines zones comparen—, aprendrem a llegir el format unified diff línia a línia (un format que apareix a Git, a les revisions de codi, als pedaços i a mitja informàtica), i recorrerem les opcions que fan llegible una sortida difícil.
En acabar hauries d'haver adquirit un hàbit: git diff --staged just abans de cada git commit.
Contingut
- Les tres formes de
git diffi què compara cadascuna git diff: directori de treball contra àrea de preparaciógit diff --staged: àrea de preparació contra l'últim commitgit diff HEAD: tot el que ha canviat- Llegir el format unified diff pas a pas
- Comparar confirmacions concretes
- Limitar la comparació a fitxers o rutes
- Opcions que fan llegible la sortida
git difftool: comparar amb una eina visual- L'hàbit de revisar abans de confirmar
- Les tres formes de
git diff i què compara cadascuna
git diff i què compara cadascunaTot l'aparent misteri de git diff es dissol amb una idea: sempre compara dues de les tres zones, i la forma que facis servir determina quines.
graph LR
WT["DIRECTORI<br/>DE TREBALL"]
IDX["ÀREA DE<br/>PREPARACIÓ"]
REPO["ÚLTIM COMMIT<br/>(HEAD)"]
WT ---|"git diff"| IDX
IDX ---|"git diff --staged"| REPO
WT -.-|"git diff HEAD"| REPO
En forma de taula:
| Ordre | Compara | Respon la pregunta |
|---|---|---|
git diff |
Directori de treball ↔ Àrea de preparació | Què he canviat i encara no he preparat? |
git diff --staged |
Àrea de preparació ↔ HEAD |
Què entrarà exactament al pròxim commit? |
git diff --cached |
Idèntic a --staged |
(Sinònim antic, continua funcionant) |
git diff HEAD |
Directori de treball ↔ HEAD |
Què ha canviat en total des de l'últim commit? |
git diff <sha1> <sha2> |
Dues confirmacions | Què va canviar entre aquestes dues versions? |
Dues conseqüències que convé interioritzar des del principi:
-
git difftot sol no mostra els canvis preparats. Si ho prepares tot ambgit add -Ai després executesgit diff, la sortida estarà buida. No és que no hagis canviat res: és que no hi ha diferència entre el teu disc i l'àrea de preparació. Aquest és el desconcert número u ambgit diff. -
git diff HEADés la suma de les altres dues. Preparat o no, si difereix de l'últim commit, hi apareix. -
Cap de les tres mostra fitxers sense seguiment. Un fitxer nou que no has afegit mai no té «versió anterior» amb la qual comparar-lo, així que
git diffl'ignora completament. Per a això hi hagit status.
git diff: directori de treball contra àrea de preparació
git diff: directori de treball contra àrea de preparacióTornem al repositori de l'Ana. Acaba de retocar estils.css i encara no ha preparat res:
diff --git a/estils.css b/estils.css
index 2c9d4e6..8f1a3b7 100644
--- a/estils.css
+++ b/estils.css
@@ -12,4 +12,5 @@ body {
#llista li {
padding: 0.5rem 0;
- border-bottom: 1px solid #ddd;
+ border-bottom: 1px solid #e5e7eb;
+ cursor: pointer;
}En tres segons sap què ha fet: ha suavitzat el color de la vora i ha afegit el cursor de punter. Cap sorpresa, cap rastre de depuració.
Ara prepara el canvi i repeteix:
Exacte: el disc i l'àrea de preparació coincideixen, així que no hi ha res a mostrar. Per veure aquell canvi cal preguntar per l'altra comparació.
git diff --staged: àrea de preparació contra l'últim commit
git diff --staged: àrea de preparació contra l'últim commitAquesta és la forma més important de totes, perquè mostra literalment el que confirmaràs:
diff --git a/estils.css b/estils.css
index 2c9d4e6..8f1a3b7 100644
--- a/estils.css
+++ b/estils.css
@@ -12,4 +12,5 @@ body {
#llista li {
padding: 0.5rem 0;
- border-bottom: 1px solid #ddd;
+ border-bottom: 1px solid #e5e7eb;
+ cursor: pointer;
}--cached és un sinònim exacte i més antic. --staged es va afegir a Git 1.6 perquè resultava més comprensible; fes servir el que prefereixis, encara que --staged és més clar.
El cas MM vist amb diff
Aquí és on git diff demostra el seu valor. L'Ana continua treballant i afegeix una línia més a estils.css després d'haver preparat:
#llista li {
padding: 0.5rem 0;
border-bottom: 1px solid #e5e7eb;
cursor: pointer;
transition: opacity 0.2s;
}Ara les tres formes donen tres respostes diferents:
@@ -12,4 +12,5 @@
#llista li {
padding: 0.5rem 0;
- border-bottom: 1px solid #ddd;
+ border-bottom: 1px solid #e5e7eb;
+ cursor: pointer;
}@@ -12,4 +12,6 @@
#llista li {
padding: 0.5rem 0;
- border-bottom: 1px solid #ddd;
+ border-bottom: 1px solid #e5e7eb;
+ cursor: pointer;
+ transition: opacity 0.2s;
}Tres preguntes, tres respostes precises. El MM de git status t'avisa que hi ha alguna cosa estranya; git diff et diu exactament què.
git diff HEAD: tot el que ha canviat
git diff HEAD: tot el que ha canviatCompara el teu directori de treball amb l'última confirmació, ignorant l'àrea de preparació. És la resposta a «què he tocat des de l'últim commit?», que és la pregunta que un es fa quan torna de dinar o quan reprèn la feina al matí.
HEAD no té res d'especial aquí: és simplement una referència a una confirmació, i git diff n'accepta qualsevol. Aquestes variants són igual de vàlides:
git diff HEAD~1 # contra la confirmació anterior a l'última
git diff HEAD~5 # contra la de fa cinc confirmacions
git diff 4e7f2a9 # contra una confirmació concretaLa notació HEAD~n i la resta de formes de referir-se a una confirmació les veurem en detall a Visualitzant l'Historial de Confirmacions.
- Llegir el format unified diff pas a pas
El format que produeix git diff s'anomena unified diff i és un estàndard de la informàtica des dels anys vuitanta. El veuràs a Git, a les revisions de codi de GitHub i GitLab, als pedaços de correu i a la sortida de desenes d'eines. Saber llegir-lo és una habilitat transferible.
Dissequem una sortida completa, línia per línia. L'Ana ha modificat app.js:
diff --git a/app.js b/app.js
index 7b2e8f1..3c9d4a2 100644
--- a/app.js
+++ b/app.js
@@ -28,8 +28,12 @@ function pintaLlista() {
llista.innerHTML = '';
for (const tasca of tasques) {
const li = document.createElement('li');
li.textContent = tasca.text;
- li.className = 'tasca';
+ li.className = tasca.feta ? 'tasca feta' : 'tasca';
+ li.addEventListener('click', function () {
+ tasca.feta = !tasca.feta;
+ pintaLlista();
+ });
llista.appendChild(li);
}
}Línia 1: la capçalera
Indica que comença la comparació d'un fitxer. a/ és la versió antiga i b/ la nova; són prefixos convencionals, no directoris reals. En un reanomenament hi veuries noms diferents a cada costat.
Si el diff inclou diversos fitxers, veuràs una d'aquestes línies per cadascun: és el separador que et diu on comença cada bloc.
Línia 2: els identificadors d'objecte
Els hashos abreujats dels blobs antic i nou, i el mode de fitxer (100644 = fitxer normal). Això connecta directament amb El Model de Dades de Git: el diff no és una entitat desada a Git, sinó una cosa que Git calcula al vol comparant dos blobs. Si el mode canviés (per exemple, en fer un fitxer executable), hi veuries old mode i new mode en línies a part.
Línies 3 i 4: els marcadors de fitxer
--- marca la versió antiga i +++ la nova. Per això, al cos del diff, - significa «era a la versió antiga» i + significa «és a la nova».
Casos especials que aclareixen molt:
Línia 5: la capçalera de fragment (@@)
Aquesta és la línia que més costa al principi i la que conté més informació. Es llegeix així:
@@ -<línia inicial antiga>,<nre. de línies antigues> +<línia inicial nova>,<nre. de línies noves> @@ <context>
Aplicat a l'exemple:
| Part | Valor | Significat |
|---|---|---|
-28,8 |
antiga | A la versió antiga, aquest bloc comença a la línia 28 i abasta 8 línies |
+28,12 |
nova | A la versió nova, comença a la línia 28 i abasta 12 línies |
function pintaLlista() { |
context | La funció o secció on és el canvi |
Dels números se'n dedueix tot: 8 línies s'han convertit en 12, per tant el bloc ha crescut en 4. I efectivament, si comptes el cos: 7 línies de context, 1 d'eliminada i 5 d'afegides. Així es verifica qualsevol capçalera @@:
- Línies antigues = context + eliminades → 7 + 1 = 8.
- Línies noves = context + afegides → 7 + 5 = 12.
El text del final no és una línia del fitxer: Git l'extreu buscant cap amunt l'última línia que sembla una declaració de funció o secció. Serveix per orientar-te quan el diff és llarg. Es pot afinar per llenguatge amb .gitattributes, que veurem a Atributs de Fitxer amb .gitattributes.
Quan el nombre de línies és 1, s'omet: @@ -12 +12 @@ significa «una línia a cada costat».
El cos: les línies del fragment
Cada línia del cos comença amb un caràcter que n'indica la naturalesa:
| Primer caràcter | Significa |
|---|---|
(espai) |
Context: la línia existeix igual a totes dues versions |
- |
Eliminada: era a la versió antiga, ja no hi és |
+ |
Afegida: abans no hi era, ara sí |
\ |
Nota especial, gairebé sempre \ No newline at end of file |
Aplicat al nostre exemple:
llista.innerHTML = ''; ← context
for (const tasca of tasques) { ← context
const li = document.createElement('li'); ← context
li.textContent = tasca.text; ← context
- li.className = 'tasca'; ← ELIMINADA
+ li.className = tasca.feta ? 'tasca feta' : 'tasca'; ← AFEGIDA
+ li.addEventListener('click', function () { ← AFEGIDA
+ tasca.feta = !tasca.feta; ← AFEGIDA
+ pintaLlista(); ← AFEGIDA
+ }); ← AFEGIDA
llista.appendChild(li); ← contextDues observacions importants:
- Git no entén de «línies modificades». Una modificació es representa sempre com una eliminació seguida d'una addició. Per això el primer parell de línies apareix com a
-i+encara que conceptualment sigui «la mateixa línia, canviada». - Les línies de context hi són per alguna cosa. Per defecte Git mostra 3 línies de context a cada costat del canvi, perquè el puguis situar. S'ajusta amb
-U<n>:
git diff -U0 # sense context: només les línies canviades
git diff -U10 # deu línies de context a cada costatEl detall de \ No newline at end of file
Significa que aquell fitxer no acaba amb un salt de línia. És una convenció POSIX que moltes eines donen per feta, i la seva absència genera diffs sorollosos: si algú afegeix el salt final, totes les línies properes poden aparèixer com a canviades. Configurar l'editor perquè afegeixi sempre el salt final evita aquest soroll.
Exercici mental ràpid
Abans de continuar, llegeix aquest fragment i respon: quantes línies tenia el bloc abans i quantes en té ara?
@@ -45,5 +45,3 @@ function actualitzaComptador() {
const pendents = tasques.filter(t => !t.feta).length;
- console.log('DEBUG pendents:', pendents);
- console.log('DEBUG total:', tasques.length);
document.querySelector('#comptador').textContent = pendents;
}Cinc abans, tres després: s'han eliminat dues línies de depuració. Aquest és exactament el tipus de troballa que justifica revisar el diff abans de confirmar.
- Comparar confirmacions concretes
git diff també compara dos punts qualssevol de l'historial:
Mostra tot el que va canviar entre aquelles dues confirmacions, agregat en un sol diff. Tant se val quantes confirmacions hi hagi pel mig: compara els dos estats finals.
Formes equivalents i molt utilitzades:
# Què va introduir l'última confirmació
git diff HEAD~1 HEAD
# Els canvis de les últimes tres confirmacions, junts
git diff HEAD~3 HEAD
# Des d'una confirmació fins a l'estat actual del disc
git diff 1a4c8d6Aquesta última mereix una nota: quan li dones un sol argument, git diff compara aquella confirmació amb el teu directori de treball, no amb HEAD. És la mateixa lògica que git diff HEAD.
Veure què va introduir una confirmació concreta
Per inspeccionar una confirmació aïllada, l'idiomàtic és:
que mostra les metadades (autor, data, missatge) i el diff. És el tema de la lliçó següent.
Una nota sobre branques
git diff accepta noms de branca exactament igual que hashos:
I existeix una notació de tres punts, git diff main...desenvolupament, que compara des del punt en què totes dues es van separar. Com que en aquest mòdul treballem sempre sobre main, ho deixem apuntat: es desenvolupa al mòdul 3.
- Limitar la comparació a fitxers o rutes
En un canvi que toca quinze fitxers, veure el diff complet és inabastable. S'acota amb -- seguit de rutes:
# Un fitxer concret
git diff -- app.js
# Diversos
git diff -- app.js estils.css
# Un directori sencer
git diff -- src/
# Un patró
git diff -- "*.css"El doble guionet -- separa les opcions de les rutes. És opcional quan no hi ha ambigüitat, així que git diff app.js funciona igual. Es torna obligatori quan un nom de fitxer es podria confondre amb un nom de branca o de confirmació:
Sense el --, Git intentaria interpretar main com una referència i, si la branca existís, obtindries una cosa completament diferent del que demanaves. Acostumar-se a posar -- abans de les rutes és un bon hàbit.
Es combina amb tot l'anterior:
- Opcions que fan llegible la sortida
--stat: el resum
app.js | 27 +++++++++++++++++++++----- estils.css | 9 ++++++++- index.html | 3 ++- 3 files changed, 33 insertions(+), 6 deletions(-)
Cada línia mostra el fitxer, el nombre total de línies afectades i una barra proporcional de + (addicions) i - (eliminacions). És la primera vista que convé mirar davant d'un canvi gran: et diu on has de mirar després.
Variants:
git diff --shortstat HEAD~3 HEAD
# → 3 files changed, 33 insertions(+), 6 deletions(-)
git diff --numstat HEAD~3 HEAD
# → 22 5 app.js
# → 8 1 estils.css
# → 2 1 index.html--numstat dona addicions, eliminacions i nom separats per tabuladors: ideal per processar amb scripts.
--name-only i --name-status: només els fitxers
--name-status afegeix una lletra per fitxer: M modificat, A afegit, D esborrat, R reanomenat (el número indica el percentatge de similitud: R100 és un reanomenament sense canvis de contingut).
Aquestes opcions són molt pràctiques encadenades amb altres ordres:
--word-diff: comparar per paraules
En un fitxer de text (documentació, README, contingut HTML), canviar una paraula fa que tota la línia aparegui com a eliminada i afegida. És il·legible:
-El gestor de tasques permet afegir i llistar tasques pendents de l'equip.
+El gestor de tasques permet afegir, llistar i esborrar tasques pendents de l'equip.Amb --word-diff el canvi real salta a la vista:
El gestor de tasques permet [-afegir i llistar-]{+afegir, llistar i esborrar+} tasques pendents de l'equip.El que s'ha eliminat va entre [- -] i el que s'ha afegit entre {+ +}. Amb color al terminal encara és més clar.
Variants:
git diff --word-diff=color # només color, sense marcadors
git diff --color-words # equivalent abreujatÉs imprescindible per revisar textos i molt útil en CSS i HTML.
-w: ignorar espais en blanc
Un reformatatge automàtic, un canvi d'indentació de tabuladors a espais o un ajust de l'editor poden generar un diff de dues-centes línies on el canvi real és de dues. La família d'opcions d'espais en blanc ho resol:
| Opció | Efecte |
|---|---|
-w, --ignore-all-space |
Ignora tots els espais, siguin on siguin |
-b, --ignore-space-change |
Ignora canvis en la quantitat d'espais, no la seva aparició o desaparició |
--ignore-space-at-eol |
Ignora espais al final de línia |
--ignore-blank-lines |
Ignora línies en blanc afegides o eliminades |
Un avís: -w serveix per llegir, no per decidir. Si el fitxer és sensible a la indentació (Python, YAML, Makefile), amagar els canvis d'espaiat pot ocultar-te un error real. Fes-lo servir per localitzar el canvi de fons i després torna al diff complet.
Altres opcions útils
# Detectar codi mogut i mostrar-lo amb un altre color
git diff --color-moved
# Diferències entre caràcters, no paraules
git diff --word-diff-regex=.
# Forçar el color encara que la sortida vagi a un fitxer o a una canonada
git diff --color=always
# Mostrar també els fitxers binaris com a diferència binària
git diff --binary
# Comparació de més qualitat (algoritme patience)
git diff --patience--color-moved és una joia poc coneguda: quan refactoritzes movent un bloc d'un lloc a un altre, distingeix visualment «això s'ha mogut» de «això és nou».
git difftool: comparar amb una eina visual
git difftool: comparar amb una eina visualPer a diffs grans o per a gent que se'n surt millor amb una vista de dues columnes, Git pot delegar en una eina externa:
Accepta exactament els mateixos arguments que git diff.
Veure quines eines tens disponibles
'git difftool --tool=<tool>' may be set to one of the following: vimdiff vimdiff2 nvimdiff The following tools are valid, but not currently available: araxis bc kdiff3 meld opendiff vscode ...
Configurar-lo
Per a Visual Studio Code, que és el que fa servir l'Ana:
git config --global diff.tool vscode
git config --global difftool.vscode.cmd 'code --wait --diff "$LOCAL" "$REMOTE"'Per a Meld (multiplataforma, gratuït i molt clar):
Per a l'opendiff de macOS, que li va bé a en Bruno:
I un ajust que gairebé tothom acaba posant:
Sense ell, Git pregunta abans d'obrir cada fitxer, cosa que és esgotadora quan el canvi n'afecta deu.
Les variables $LOCAL i $REMOTE que apareixen a la configuració són fitxers temporals que Git crea amb cada versió i passa a l'eina. Tot això es desa al ~/.gitconfig amb el mecanisme que vam veure a Configurant Git:
[diff]
tool = vscode
[difftool]
prompt = false
[difftool "vscode"]
cmd = code --wait --diff "$LOCAL" "$REMOTE"Terminal o eina visual?
git diff al terminal |
git difftool |
|
|---|---|---|
| Velocitat | Instantani | Obre una finestra per fitxer |
| Canvis petits | Perfecte | Excessiu |
| Refactoritzacions grans | Difícil de seguir | Molt més clar |
| Ús en scripts | Sí | No |
| Funciona per SSH sense escriptori | Sí | No |
La recomanació pràctica: fes servir git diff com a eina per defecte —és més ràpida i sempre hi és— i reserva difftool per als canvis grans.
Menció a part: existeix també
git mergetool, l'equivalent per resoldre conflictes de fusió. Es configura de manera anàloga i el veurem a Resolent Conflictes de Fusió.
- L'hàbit de revisar abans de confirmar
Tot l'anterior es condensa en una rutina de trenta segons que convé automatitzar mentalment:
git status -s # quins fitxers hi ha en joc?
git diff # què em queda sense preparar?
git add -p # preparar amb criteri
git diff --staged # és EXACTAMENT això el que vull confirmar?
git commit -m "..."El pas realment valuós és el penúltim. El que detecta habitualment:
- Restes de depuració:
console.log,print, punts d'interrupció. - Credencials o URL de proves.
- Blocs de codi comentat que anaves a esborrar.
- Canvis accidentals per autoformatat de l'editor en fitxers que no tocaves.
- Fitxers que s'han colat en un
git add -Aapressat. - Canvis que et pensaves haver fet i no hi eren.
Si prefereixes no executar una ordre a part, l'opció -v de git commit inclou el diff a la plantilla del missatge:
I per deixar-ho activat sempre:
És probablement l'ajust amb millor relació entre esforç i benefici de tot Git.
Errors Habituals i Consells
- Executar
git diffdesprés degit addi creure que no hi ha canvis. La sortida buida significa «el disc coincideix amb l'àrea de preparació». El que busques ésgit diff --staged. - Esperar veure fitxers nous a
git diff. Un fitxer sense seguiment no té versió anterior, així que no hi apareix. Per a això hi hagit status. Si el vols veure, prepara'l abans (git add -N fitxerel registra com a buit i fa que els seus continguts surtin al diff). - Llegir a l'inrevés la capçalera
@@. El primer número és de la versió antiga i el segon de la nova. Confondre'ls porta a interpretar malament el sentit del canvi. - Pensar que Git desa diffs. No els desa: desa instantànies i calcula el diff quan l'hi demanes, com vam veure al model de dades. Per això pots comparar dues confirmacions qualssevol, per llunyanes que siguin.
- Abusar de
-w. En llenguatges sensibles a la indentació, ignorar espais pot ocultar-te l'error que busques. És una eina de lectura, no de revisió final. - No fer servir
--abans d'una ruta ambigua. Si tens un fitxer que es diu igual que una branca,git diff mainno farà el que et penses. - Revisar un canvi de 40 fitxers amb el diff complet. Comença per
--statper veure el mapa, i baixa al detall només on importi. - Consell: defineix àlies per al que facis servir més:
git config --global alias.d diffigit config --global alias.ds "diff --staged". - Consell: si la sortida de
git diffse't queda «atrapada» al paginador, recorda que es recorre amb les fletxes o la barra espaiadora i se'n surt ambq. Per desactivar-lo puntualment,git --no-pager diff. - Consell:
git diff --statjust després degit add -Aés la manera més ràpida de detectar que has afegit alguna cosa que no volies.
Exercicis
Exercici 1: Les tres zones, tres diffs diferents
Munta aquest escenari i respon raonadament abans d'executar cada ordre:
mkdir -p ~/practica/diffs && cd ~/practica/diffs
git init
cat > app.js <<'FI'
const tasques = [];
function afegeixTasca(text) {
tasques.push(text);
}
FI
git add app.js && git commit -m "Versió inicial"
# Canvi 1: es prepara
sed -i 's/tasques.push(text);/tasques.push({ text: text, feta: false });/' app.js
git add app.js
# Canvi 2: NO es prepara
echo "" >> app.js
echo "function comptaTasques() { return tasques.length; }" >> app.js- Què mostrarà
git status -s? - Què mostrarà
git diff? Quantes línies+tindrà? - Què mostrarà
git diff --staged? - Què mostrarà
git diff HEAD? - Si confirmes ara sense cap més
git add, què contindrà el fitxer a l'historial? - Comprova les teves respostes executant-ho tot.
Exercici 2: Llegir un diff sense executar-lo
Interpreta aquesta sortida i respon les preguntes:
diff --git a/estils.css b/estils.css
index a1b2c3d..d4e5f6a 100644
--- a/estils.css
+++ b/estils.css
@@ -8,10 +8,8 @@ body {
h1 {
font-size: 1.8rem;
- color: #333;
- margin-bottom: 1rem;
+ color: #1f2937;
}
-.ocult { display: none; }
#comptador {
color: #6b7280;
}
diff --git a/config.js b/config.js
new file mode 100644
index 0000000..7a8b9c0
--- /dev/null
+++ b/config.js
@@ -0,0 +1,4 @@
+const API_URL = 'https://api.exemple.cat';
+const TEMPS_ESPERA = 5000;
+const CLAU_DEBUG = 'test-1234';
+export { API_URL, TEMPS_ESPERA, CLAU_DEBUG };- Quants fitxers afecta aquest canvi i què li passa a cadascun?
- A
estils.css, quantes línies s'han eliminat i quantes se n'han afegit? Com canvia el nombre total de línies del bloc? - Què significa exactament
--- /dev/nullal segon fitxer? - Què significa
index 0000000..7a8b9c0? - Hi ha alguna cosa en aquest diff que no hauries de confirmar? Explica per què i què faries.
- Escriu l'ordre que mostraria només el resum per fitxers d'aquest canvi.
Exercici 3: Trobar el canvi real entre el soroll
Simula la situació d'un fitxer reindentat alhora que se'n canvia una línia de lògica:
mkdir -p ~/practica/soroll && cd ~/practica/soroll
git init
cat > app.js <<'FI'
function calculaTotal(tasques) {
let total = 0;
for (const tasca of tasques) {
if (!tasca.feta) {
total = total + 1;
}
}
return total;
}
FI
git add app.js && git commit -m "Versió inicial"
# Reindentar de 4 a 2 espais I canviar la condició
cat > app.js <<'FI'
function calculaTotal(tasques) {
let total = 0;
for (const tasca of tasques) {
if (!tasca.feta && !tasca.arxivada) {
total = total + 1;
}
}
return total;
}
FI- Executa
git diffi compta quantes línies apareixen com a canviades. - Fes servir l'opció adequada per veure només el canvi de lògica.
- Explica per què el resultat de les dues ordres és tan diferent.
- Què hauria estat millor fer des del principi perquè l'historial fos llegible? Escriu la seqüència d'ordres correcta.
- Fes servir
--word-diffsobre el mateix canvi i comenta si aporta res en aquest cas.
Solucions
Solució a l'Exercici 1
1. git status -s:
Hi ha una versió preparada (canvi 1) i el disc en difereix (canvi 2).
2. git diff — el que no està preparat. Mostra únicament el canvi 2:
@@ -3,3 +3,5 @@ const tasques = [];
function afegeixTasca(text) {
tasques.push({ text: text, feta: false });
}
+
+function comptaTasques() { return tasques.length; }Dues línies +: la línia en blanc i la funció. La línia tasques.push({...}) apareix com a context, no com a afegida, perquè a l'àrea de preparació ja és així.
3. git diff --staged — el que hi entrarà. Només el canvi 1:
@@ -1,5 +1,5 @@
const tasques = [];
function afegeixTasca(text) {
- tasques.push(text);
+ tasques.push({ text: text, feta: false });
}4. git diff HEAD — el total, tots dos canvis junts:
@@ -1,5 +1,7 @@
const tasques = [];
function afegeixTasca(text) {
- tasques.push(text);
+ tasques.push({ text: text, feta: false });
}
+
+function comptaTasques() { return tasques.length; }5. En confirmar sense cap més git add hi entraria només el canvi 1. El fitxer a l'historial tindria el push amb l'objecte, però no la funció comptaTasques, que continuaria pendent al directori de treball.
Comprovació:
Solució a l'Exercici 2
1. Dos fitxers:
estils.css: modificat (existeix el parell--- a/+++ b/amb el mateix nom i mode100644).config.js: fitxer nou, com indiquennew file mode 100644i--- /dev/null.
2. A estils.css: 3 línies eliminades (color: #333;, margin-bottom: 1rem; i .ocult { display: none; }) i 1 d'afegida (color: #1f2937;).
El compte encaixa amb la capçalera @@ -8,10 +8,8 @@: el bloc passa de 10 a 8 línies, és a dir, en perd 2 (−3 +1 = −2). I es pot verificar comptant a mà les línies del fragment:
- Versió antiga = línies de context + línies
-→ 7 de context + 3 d'eliminades = 10. - Versió nova = línies de context + línies
+→ 7 de context + 1 d'afegida = 8.
Aquest exercici de comprovació és la millor manera d'assegurar-te que has entès la capçalera @@: els dos números sempre han de quadrar amb el que veus al cos.
3. --- /dev/null indica que la versió antiga del fitxer no existeix: és un fitxer nou. /dev/null és el «dispositiu nul» dels sistemes Unix, i aquí funciona com «el no-res». El cas simètric, +++ /dev/null, indicaria un esborrat.
4. index 0000000..7a8b9c0 són els hashos dels blobs antic i nou. El de l'esquerra és tot zeros perquè no hi ha blob antic: coherent amb el fet que el fitxer sigui nou.
5. Sí, hi ha un problema: CLAU_DEBUG = 'test-1234' a config.js. Encara que sigui una clau de proves, és un secret al codi, i confirmar-lo el deixa a l'historial de manera permanent per a qualsevol que cloni el repositori. Encara que avui sigui inofensiva, estableix un mal precedent i és exactament el mecanisme pel qual acaben filtrant-se claus reals.
Què faria:
# Treure la constant del fitxer i portar-la a una variable d'entorn
# o a un fitxer de configuració local ignorat.
# Després, treure config.js de l'àrea de preparació:
git restore --staged config.js
# Editar-lo, i tornar a preparar només la part legítima:
git add -p config.jsSi config.js no s'hagués de versionar en absolut:
6. El resum per fitxers:
Solució a l'Exercici 3
1. El diff complet:
@@ -1,9 +1,9 @@
function calculaTotal(tasques) {
- let total = 0;
- for (const tasca of tasques) {
- if (!tasca.feta) {
- total = total + 1;
- }
- }
- return total;
+ let total = 0;
+ for (const tasca of tasques) {
+ if (!tasca.feta && !tasca.arxivada) {
+ total = total + 1;
+ }
+ }
+ return total;
}Catorze línies apareixen com a canviades (7 d'eliminades i 7 d'afegides), quan el canvi real de comportament és un de sol.
2. Veure només la lògica:
@@ -1,7 +1,7 @@
function calculaTotal(tasques) {
let total = 0;
for (const tasca of tasques) {
- if (!tasca.feta) {
+ if (!tasca.feta && !tasca.arxivada) {
total = total + 1;
}
}Ara només hi ha un parell -/+: la condició. La indentació de les altres línies s'ignora, així que el canvi real apareix aïllat.
3. Per què la diferència és tan gran. Git compara línies completes, caràcter a caràcter. Canviar quatre espais per dos al començament d'una línia la converteix, a ulls de Git, en una línia diferent: l'antiga s'elimina i la nova s'afegeix. Com que el reformatatge afecta les set línies del cos de la funció, les set apareixen canviades i el canvi de lògica es perd entre elles. -w diu a Git que normalitzi els espais abans de comparar, amb la qual cosa només queda la diferència real.
4. El correcte des del principi: dues confirmacions separades. Barrejar reformatatge amb canvis funcionals és una mala pràctica ben coneguda, perquè fa impossible revisar el canvi i arruïna el git blame de tot el bloc.
# Confirmació 1: NOMÉS el reformatatge
# (reindentar el fitxer, sense tocar la lògica)
git add app.js
git commit -m "Reindenta calculaTotal a 2 espais"
# Confirmació 2: NOMÉS el canvi de comportament
# (afegir la condició !tasca.arxivada)
git add app.js
git commit -m "Exclou les tasques arxivades del total"El diff de la segona confirmació és de dues línies i es revisa en cinc segons. A més, si demà cal desfer el canvi de lògica, es desfà sense arrossegar el reformatatge.
Si els dos canvis ja estan barrejats al disc, git add -p amb l'opció e permet separar-los, encara que en aquest cas concret —on cada línia conté tots dos canvis alhora— seria més pràctic refer la feina en dos passos.
5. Amb --word-diff:
@@ -1,9 +1,9 @@
function calculaTotal(tasques) {
let total = 0;
for (const tasca of tasques) {
if (!tasca.feta[- -]{+ && !tasca.arxivada +}) {Aporta força: en comparar per paraules en lloc de per línies, la reindentació deixa de generar soroll gairebé del tot i el canvi de la condició queda assenyalat amb precisió quirúrgica. En canvis de text i en casos com aquest, --word-diff i -w es complementen bé:
Conclusió
Ja saps llegir els canvis abans que entrin a la història del projecte. Recapitulant:
git diffcompara sempre dues zones, i la forma que facis servir decideix quines:git diff(disc ↔ preparació),git diff --staged(preparació ↔HEAD) igit diff HEAD(disc ↔HEAD, és a dir, el total).- Una sortida buida de
git diffno significa «no he canviat res»: gairebé sempre significa que ja ho has preparat tot. - Cap
git diffmostra fitxers sense seguiment. Per a això hi hagit status. - El format unified diff té una estructura fixa: capçalera
diff --git, hashos de blob, marcadors---/+++, capçaleres de fragment@@ -a,b +c,d @@amb el seu context, i línies de context, eliminades (-) i afegides (+). Git no representa «línies modificades»: una modificació és una eliminació més una addició. - Es poden comparar dues confirmacions qualssevol (
git diff <sha1> <sha2>) i acotar per ruta amb-- <ruta>, fent servir--sempre que el nom pugui ser ambigu. - Les opcions canvien la llegibilitat radicalment:
--statper al mapa general,--name-only/--name-statusper a la llista,--word-diffper a textos i-wper separar el canvi real del soroll d'espaiat. git difftooldelega en una eina visual i accepta els mateixos arguments; compensa en canvis grans.- L'hàbit que importa:
git diff --stagedabans de cadagit commit, o directamentcommit.verbose = true.
Amb git status saps on ets, amb git diff saps què has canviat i amb git add/git commit decideixes què queda registrat. Falta l'última peça del cicle bàsic: mirar enrere.
Un historial només val el que val la capacitat de consultar-lo. Després de dues-centes confirmacions, com trobes quan es va introduir una funció? Qui va tocar estils.css la setmana passada? En quina confirmació va desaparèixer aquella línia que jures haver escrit?
A la lliçó següent, Visualitzant l'Historial de Confirmacions, veurem git log amb tots els seus formats i filtres, els formats personalitzats amb --pretty, la cerca «pickaxe» que troba quan va aparèixer o desaparèixer un text concret, git show per inspeccionar una confirmació, i les diferents formes de referir-se a un commit —HEAD, HEAD~3, HEAD^— que ja hem anat fent servir de passada i que convé entendre bé.
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ó
