La lliçó anterior va deixar el catàleg muntat i va resoldre els casos d'un minut. Ara toca l'ordre que apareixia a mitja dotzena de files d'aquella taula i que portem cinc mòduls prometent: git reset.
És probablement l'ordre pitjor entesa de Git. Té tres modes que fan coses diferents, dues formes d'invocar-la que es comporten de manera completament diferent, i una fama de perillosa que és merescuda només en un dels seus usos. La confusió no és casual: reset fa dues feines que en altres sistemes serien dues ordres diferents.
Aquesta lliçó el desmunta peça a peça i després el posa en context. Perquè reset no és la resposta a "vull desfer alguna cosa": és una de les respostes, i triar l'equivocada és el que converteix un canvi no desitjat en un problema per a tot l'equip. Al final tindràs una taula de decisió que respon l'única pregunta que importa: on és allò que vull desfer?
I un advertiment que val per a tota la lliçó: aquí apareix l'única ordre de Git que destrueix de debò, sense cap xarxa de seguretat possible. No és reset. És git clean.
Contingut
- Les tres zones, una altra vegada
git resetmou la branca: la idea central- Els tres modes:
--soft,--mixed,--hard - Casos ordenats pel que vols desfer
git reset <commit> -- <ruta>: l'altra formagit restore: l'ordre moderna per a la feina diàriagit clean: el que sí que destrueixORIG_HEAD: la xarxa de seguretat immediata- La taula de decisió
- Les tres zones, una altra vegada
Tot reset s'entén tan bon punt es recuperen les tres zones de la lliçó 01-03. Val la pena tenir-les al davant:
flowchart LR
WD["Directori de treball<br/>(els fitxers que edites)"]
IDX["Índex / staging<br/>(.git/index)"]
HEAD["HEAD → branca → commit<br/>(la base de dades)"]
WD -->|"git add"| IDX
IDX -->|"git commit"| HEAD
HEAD -->|"git reset --hard"| WD
HEAD -->|"git reset --mixed"| IDX
Tres fotos del projecte que poden coincidir o no:
| Zona | Què conté | On viu |
|---|---|---|
HEAD |
L'últim commit de la branca actual | .git/refs/heads/<branca> (41 bytes) |
| Índex | El que entraria al pròxim commit | .git/index (binari) |
| Directori de treball | Els fitxers reals al disc | La teva carpeta |
Quan git status diu "Changes to be committed", està comparant índex amb HEAD. Quan diu "Changes not staged for commit", compara directori de treball amb índex. Amb això clar, reset deixa de ser un misteri: cada mode decideix fins a quina d'aquestes tres zones arriba el canvi.
git reset mou la branca: la idea central
git reset mou la branca: la idea centralAbans dels modes, l'operació bàsica. Això és el que fa git reset <commit> sempre, en els tres modes:
Mou la branca actual perquè apunti a
<commit>. I com queHEADapunta a la branca,HEADes mou amb ella.
No esborra commits. No modifica commits. Reescriu un fitxer de 41 bytes (lliçó 03-01).
flowchart LR
subgraph A["Abans"]
direction LR
a1["e91d4a8"] --> a2["4f8a2e6"] --> a3["b52c9d1"] --> a4["7d3a8f4"]
ra(["GT-241"]) -.-> a4
ha(["HEAD"]) -.-> ra
end
subgraph B["git reset HEAD~2"]
direction LR
b1["e91d4a8"] --> b2["4f8a2e6"] --> b3["b52c9d1<br/>(inassolible)"] --> b4["7d3a8f4<br/>(inassolible)"]
rb(["GT-241"]) -.-> b2
hb(["HEAD"]) -.-> rb
end
A ==> B
b52c9d1 i 7d3a8f4 continuen existint íntegres a .git/objects. El que ha canviat és que GT-241 ja no hi arriba, així que git log no els mostra. Recuperar-los és trivial mentre el reflog els recordi (lliçó 09-04).
D'aquí surten les dues conseqüències que cal interioritzar:
1. reset és segur sobre feina local. Encara que t'equivoquis, els commits continuen allà.
2. reset és perillós sobre feina publicada. Perquè moure la branca enrere i tornar a avançar produeix una història diferent de la que tenen els altres. El push serà rebutjat (non-fast-forward), i forçar-lo destruiria la feina dels altres. És la regla d'or de la lliçó 05-01, i el seu tractament complet és la lliçó 09-03.
Un tercer matís que gairebé ningú no coneix i que evita molts ensurts:
Sense argument de commit, reset fa servir HEAD, és a dir, no mou res. Només actua sobre l'índex. Per això git reset a seques és completament inofensiu: buida l'àrea de preparació i deixa els teus fitxers intactes.
- Els tres modes:
--soft, --mixed, --hard
--soft, --mixed, --hardLa diferència entre els tres és fins on propaga el canvi.
| Mode | Mou HEAD/branca |
Reescriu l'índex | Reescriu el directori de treball | Destrueix feina? |
|---|---|---|---|---|
--soft |
Sí | No | No | No |
--mixed (per defecte) |
Sí | Sí | No | No |
--hard |
Sí | Sí | Sí | Sí: el que no s'ha confirmat |
--merge |
Sí | Sí | Parcialment (conserva canvis locals no conflictius) | Rarament |
--keep |
Sí | Sí | Parcialment (avorta si hi ha conflicte) | No |
Visualment, sobre les tres zones:
flowchart TD
subgraph S["--soft"]
s1["HEAD ⟵ es mou"]
s2["Índex: intacte"]
s3["Feina: intacta"]
end
subgraph M["--mixed (per defecte)"]
m1["HEAD ⟵ es mou"]
m2["Índex ⟵ es reescriu"]
m3["Feina: intacta"]
end
subgraph H["--hard"]
h1["HEAD ⟵ es mou"]
h2["Índex ⟵ es reescriu"]
h3["Feina ⟵ SE SOBREESCRIU"]
end
--soft: desfer el commit, conservar-ho tot preparat
El commit ha desaparegut de la branca, i tot el seu contingut és preparat, a punt per tornar-se a confirmar. Els M i A amb la lletra a la primera columna signifiquen "a l'índex".
És el mode de "vull refer aquest commit". De fet, és exactament el que fa git commit --amend per dins.
--mixed: desfer el commit i despreparar
Ara la lletra és a la segona columna: els canvis són al directori de treball, sense preparar. I estils.css, que era nou, ha tornat a ser un fitxer sense seguiment.
És el mode de "vull refer aquest commit i tornar a triar què hi entra". És el que vas fer servir a la lliçó 05-02 per dividir un commit en dos.
--hard: desfer el commit i el contingut
Tot net, com si aquell commit no hagués existit mai. I aquí hi ha el perill, que convé enunciar amb precisió:
--hardno destrueix els commits (continuen a la base de dades). Destrueix els canvis sense confirmar que hi hagués en aquell moment a l'índex i al directori de treball.
Això és el que no té marxa enrere, perquè mai no va ser a .git/objects (lliçó 09-01, taula de l'apartat 1).
Una excepció important i tranquil·litzadora: --hard no toca els fitxers sense seguiment. Si tenies un notes.txt que no vas afegir mai, continua allà. El que s'emporta per davant són les modificacions de fitxers seguits.
La comparació en un sol experiment
# Punt de partida: un commit, i a més un canvi sense confirmar
git log --oneline -2
git status --short| Ordre | git log -1 |
app.js (el meu canvi sense confirmar) |
Contingut del commit desfet |
|---|---|---|---|
git reset --soft HEAD~1 |
b52c9d1 |
Conservat | A l'índex |
git reset --mixed HEAD~1 |
b52c9d1 |
Conservat (barrejat) | Al directori de treball |
git reset --hard HEAD~1 |
b52c9d1 |
PERDUT | Descartat |
La fila del --hard és la que cal memoritzar: s'emporta dues coses alhora, el commit i la teva feina en curs. I només una de les dues es pot recuperar.
--merge i --keep: els modes que gairebé ningú no fa servir
Existeixen i en dues situacions són exactament el que vols:
--keep és un --hard educat: mou la branca i actualitza els fitxers que difereixen, però avorta si això trepitjaria un canvi local. És l'opció sensata quan vols retrocedir i no estàs segur de tenir-ho tot confirmat:
Aquest error, aquí, és una bona notícia: t'ha salvat.
--merge és semblant però intenta fusionar els canvis locals a la destinació. Es fa servir sobretot per avortar una fusió a mà.
Regla pràctica: quan tinguis la temptació d'escriure --hard i no n'estiguis segur, escriu --keep. Si funciona, no hi havia res a perdre; si falla, acabes d'evitar el problema.
- Casos ordenats pel que vols desfer
Aquest és el receptari. Està ordenat de menor a major abast.
4.1. Descartar canvis d'un fitxer al directori de treball
L'Ana ha estat provant coses a estils.css i vol tornar a com estava.
git restore estils.css # forma moderna (Git 2.23+)
git checkout -- estils.css # forma antiga, equivalentAixò sí que destrueix, i sense xarxa: les modificacions no confirmades no són enlloc. Git ni tan sols pregunta.
# Abans de destruir, mira què perdràs
git diff estils.css
# Tot el directori de treball
git restore .4.2. Treure alguna cosa de l'índex sense perdre el canvi
El Bruno ha fet git add . i hi ha ficat sense voler notes-personals.md.
git restore --staged notes-personals.md # forma moderna
git reset notes-personals.md # forma antiga, equivalentNo has perdut res: el fitxer continua modificat al directori de treball, només deixa d'estar preparat.
Fixa't en l'asimetria, que és la font de confusió número u:
| Treu de l'índex | Destrueix el canvi | |
|---|---|---|
git restore --staged <f> |
Sí | No |
git restore <f> |
No | Sí |
git restore --staged --worktree <f> |
Sí | Sí |
git restore sense opcions actua sobre el directori de treball. Amb --staged, sobre l'índex. És el contrari del que molta gent assumeix, i és la raó que git restore mereixi el seu propi apartat (el 6).
4.3. Desfer l'últim commit conservant els canvis
El cas més freqüent de tots. La Carla ha confirmat i s'ha adonat que el commit barreja dues coses.
No has perdut res. El commit ja no és a la branca, però tot el seu contingut és preparat. Ara el pot refer:
Si a més vol tornar a triar què entra a cada commit, --mixed i git add -p (lliçó 02-04):
git reset HEAD~1
git add -p app.js # triar tros a tros
git commit -m "GT-241 calcula el comptador sobre la llista completa"
git add .
git commit -m "GT-241 estils de l'indicador de pendents"Aquest és el flux complet de "he barrejat dues coses en un commit", i és el que es fa servir a diari.
4.4. Només vull canviar el missatge
No cal reset per a això. --amend substitueix l'últim commit (lliçó 02-04), i amb -m ni tan sols obre l'editor:
Recorda que el hash canvia: si estava publicat, això reescriu historial.
4.5. Llençar els últims N commits
Tres commits fora de la branca. Abans d'executar-ho, dos costums que costen cinc segons:
# 1. Anota on ets
git log --oneline -1
git rev-parse HEAD > /tmp/on-era.txt
# 2. O millor: posa un marcador. És gratis i no s'oblida
git branch copia-GT-241Amb git branch copia-GT-241, el --hard deixa de ser irreversible en cap sentit: els commits continuen sent assolibles des de la còpia. És el mateix patró que a la lliçó 09-01, apartat 5.2, i el que es generalitza a la 09-04.
I si ja ho vas fer sense còpia: tampoc no passa res, mentre no fossin canvis sense confirmar.
git reflog -5 # busca el hash anterior al reset
git reset --hard b52c9d1 # o millor: git branch rescat b52c9d14.6. Tornar la branca exactament a com està al remot
És el "descarta tot el meu i deixa'm com el servidor". Útil quan la teva còpia local s'ha embolicat sense remei i saps que no hi ha res teu a salvar.
Comprova abans què llençaràs:
git log --oneline origin/main..HEAD # commits meus que no són al remot
git status --short # canvis sense confirmarSi la primera llista no és buida, no executis el --hard sense crear abans una branca de còpia.
4.7. Desfer alguna cosa que ja està publicada
Aquí reset no és la resposta. Reescriure una branca publicada obliga a forçar el push i destrueix la feina de qui tingui la versió anterior.
La resposta és git revert (lliçó 05-06): crea un commit nou amb el canvi invers, no reescriu res, no cal forçar, i en queda constància.
L'única excepció: una branca publicada que és exclusivament teva (la teva branca de treball GT-241, que ningú més no fa servir). Aquí sí que pots reescriure i forçar amb --force-with-lease, i això és la lliçó 09-03.
git reset <commit> -- <ruta>: l'altra forma
git reset <commit> -- <ruta>: l'altra formaAquí hi ha la font principal de confusió amb reset, i mereix un apartat propi.
Quan hi afegeixes una ruta, git reset fa una cosa completament diferent:
git reset <commit> -- <ruta>NO mou la branca. Copia l'estat de<ruta>a<commit>a l'índex, i deixa el directori de treball intacte.
| Forma | Mou la branca? | Toca l'índex? | Toca el directori de treball? |
|---|---|---|---|
git reset <commit> |
Sí | Segons el mode | Segons el mode |
git reset <commit> -- <ruta> |
No, mai | Sí, només aquesta ruta | No, mai |
Per què no pot moure la branca: moure HEAD a un altre commit és una operació sobre el repositori sencer. No existeix "moure la branca només per a un fitxer". Així que quan dones una ruta, Git entén que vols l'altra operació: omplir l'índex des d'un commit.
Conseqüència directa: git reset <commit> -- <ruta> no accepta --hard.
Precisament perquè el mode --hard implica tocar el directori de treball, i aquesta forma no ho fa mai.
Per a què serveix realment
És la manera de preparar el contingut antic d'un fitxer, típicament per desfer només una part d'un commit:
# El commit 4f8a2e6 va tocar tres fitxers; vull desfer només el d'estils.css
git reset 4f8a2e6~1 -- estils.css
git status --shortL'índex té ara la versió anterior d'estils.css, preparada. Un git commit crea un commit que desfà només aquesta part.
I si a més vols que el fitxer al disc canviï, la forma moderna i molt més clara és git restore:
El cas "aquest fitxer no hauria de ser al repositori"
Reprenent la lliçó 08-03:
git rm --cached treu el fitxer de l'índex (deixa de tenir seguiment) però el conserva al disc. És l'operació que cal fer quan alguna cosa va entrar al repositori i després la vas afegir al .gitignore. És una cosina germana de git reset -- <ruta>, però per deixar de seguir el fitxer en lloc de restaurar-ne el contingut.
git restore: l'ordre moderna per a la feina diària
git restore: l'ordre moderna per a la feina diàriaGit 2.23 va partir el vell git checkout en dues ordres amb noms honestos: git switch per a les branques (lliçó 03-02) i git restore per als fitxers. És l'eina que hauries de fer servir a diari, deixant reset per al que de debò implica moure la branca.
# Descartar canvis del directori de treball
git restore app.js
git restore .
# Treure de l'índex (conservant el canvi al disc)
git restore --staged app.js
# Totes dues coses: deixar el fitxer com a HEAD, sense rastre del canvi
git restore --staged --worktree app.js
# Portar un fitxer des d'un altre commit o branca
git restore --source=HEAD~3 app.js
git restore --source=origin/main estils.css
# Interactiu: triar tros a tros què descartar
git restore -p app.jsLa taula d'equivalències amb el que és antic, perquè veuràs les dues formes a la documentació i a les respostes de fòrum:
| Objectiu | Modern | Antic |
|---|---|---|
| Descartar canvis al disc | git restore <f> |
git checkout -- <f> |
| Treure de l'índex | git restore --staged <f> |
git reset HEAD <f> |
| Portar un fitxer d'un altre commit | git restore --source=<c> <f> |
git checkout <c> -- <f> |
| Canviar de branca | git switch <branca> |
git checkout <branca> |
| Crear i canviar | git switch -c <branca> |
git checkout -b <branca> |
git restore -p mereix una menció a part. És el germà de git add -p: t'ensenya cada fragment del diff i et pregunta si el vols descartar. És la manera quirúrgica de desfer part d'un canvi sense perdre la resta, i evita el "descartaré tot i ho refaré".
@@ -12,6 +12,9 @@ function renderitzaTasques() {
llista.innerHTML = '';
+ console.log('DEBUG tasques', tasques);
tasques.forEach(tasca => {
Discard this hunk from worktree [y,n,q,a,d,e,?]?Perfecte per treure els console.log de depuració conservant la feina real.
git clean: el que sí que destrueix
git clean: el que sí que destrueixTot l'anterior té xarxa de seguretat en algun grau. Aquesta ordre no en té cap, i per això mereix el to més seriós de la lliçó.
git clean esborra fitxers sense seguiment. I un fitxer sense seguiment no ha passat mai per .git/objects: no hi ha commit, no hi ha blob, no hi ha reflog, no hi ha fsck que valgui. Quan git clean l'esborra, està esborrat, igual que amb rm.
La regla absoluta: -n primer, sempre
-n (o --dry-run) no esborra res: només llista. Llegeix-ho sencer. Si en aquesta llista apareix alguna cosa que t'importa, no executis l'ordre real.
Aquest costum no és opcional. És la diferència entre netejar el projecte i perdre el fitxer de configuració local que portes sis mesos mantenint a mà.
Les opcions
| Opció | Què fa | Perill |
|---|---|---|
-n / --dry-run |
Només llista el que esborraria | Cap |
-f / --force |
Esborra de debò (obligatòria: sense ella no fa res) | Alt |
-d |
Inclou directoris sense seguiment | Alt |
-x |
Inclou també el que està ignorat pel .gitignore |
Molt alt |
-X |
Esborra només el que està ignorat, conservant la resta | Mitjà |
-i / --interactive |
Pregunta fitxer a fitxer | Baix |
-e <patró> |
Exclou el que coincideixi | — |
Els usos habituals:
# El que gairebé sempre vols: veure, i després esborrar fitxers i directoris nous
git clean -nd
git clean -fd
# Deixar el projecte com acabat de clonar (també esborra node_modules, .env, builds!)
git clean -ndx
git clean -fdx
# Esborrar només el que està ignorat: netejar artefactes de compilació conservant la resta
git clean -fdX
# El mode segur quan dubtes
git clean -id-x és la que cal mirar dues vegades. Esborra el que el .gitignore protegeix, i allà sol haver-hi precisament el que no és al repositori però sí que t'importa: .env amb la configuració local, credencials de desenvolupament, bases de dades de prova.
El mode interactiu
Would remove the following items:
captures/ notes-personals.md bolcat.sql
*** Commands ***
1: clean 2: filter by pattern 3: select by numbers
4: ask each 5: quit 6: help
What now> 4L'opció 4 (ask each) pregunta un per un. Quan hi ha molts fitxers i només en sobren alguns, és la manera correcta.
El "reset total" i el seu cost
La combinació que circula per internet com a "deixar-ho tot net":
Això deixa el directori exactament com un clon acabat de fer d'aquell commit. I s'emporta per davant, sense preguntar i sense recuperació possible:
- Tots els canvis sense confirmar de fitxers seguits.
- Tots els fitxers nous que no havies afegit.
- Tot el que està ignorat:
.env,node_modules/, configuracions locals, bases de dades de prova.
És una ordre legítima —en CI, en un contenidor, en un repositori d'usar i llençar— i una catàstrofe a la màquina d'algú que portava dues hores treballant. No l'enganxis mai sense haver executat abans git clean -ndx i llegit la llista.
L'única prevenció que funciona
git stash -u (lliçó 05-04) desa també els fitxers sense seguiment, i els desa com a objectes de Git. Amb això, el que anaves a esborrar passa a ser a la base de dades i, per tant, deixa de ser irrecuperable. Costa un segon.
ORIG_HEAD: la xarxa de seguretat immediata
ORIG_HEAD: la xarxa de seguretat immediataAbans d'una operació que mou HEAD de manera substancial —reset, merge, rebase, pull—, Git desa la posició anterior en una referència especial:
Així que el "desfer" immediat d'un reset és:
O, seguint el patró segur:
Dos advertiments sobre ORIG_HEAD:
- Només desa una posició, la de l'última operació. Si fas dos
resetseguits, la primera es perd. Per a això hi ha el reflog, que desa tot l'historial de moviments (lliçó 09-04). - No recupera canvis sense confirmar.
ORIG_HEADés un hash de commit: torna la branca al seu lloc, no la teva feina en curs.
És la xarxa de seguretat dels trenta segons següents. Per a tota la resta, la 09-04.
- La taula de decisió
La pregunta correcta no és "quina ordre faig servir per desfer?", sinó "on és allò que vull desfer?". Amb aquesta resposta, l'ordre és immediata.
flowchart TD
Q1{"Està confirmat?"}
Q1 -->|No| Q2{"Està preparat<br/>(git add)?"}
Q1 -->|Sí| Q3{"Està publicat<br/>al remot?"}
Q2 -->|No| R1["git restore <f><br/>(o git clean si no té seguiment)"]
Q2 -->|Sí| R2["git restore --staged <f>"]
Q3 -->|No| Q4{"Només l'últim<br/>commit?"}
Q3 -->|Sí| R5["git revert (05-06)<br/>o --force-with-lease si la branca és teva (09-03)"]
Q4 -->|Sí| R3["git commit --amend<br/>o git reset --soft HEAD~1"]
Q4 -->|No| R4["git reset HEAD~N<br/>o git rebase -i (05-02)"]
I en forma de taula, que és com es consulta en el moment:
| Vull desfer... | Estat | Ordre | Recuperable? |
|---|---|---|---|
| Canvis en un fitxer editat | Sense confirmar | git restore <f> |
No |
| Part dels canvis d'un fitxer | Sense confirmar | git restore -p <f> |
No |
Un git add |
Preparat | git restore --staged <f> |
Sí (no es perd res) |
| Un fitxer nou que sobra | Sense seguiment | git clean -n i després -f |
No |
| L'últim commit, conservant la feina | Sense publicar | git reset --soft HEAD~1 |
Sí (reflog) |
| El missatge de l'últim commit | Sense publicar | git commit --amend |
Sí (reflog) |
| Un fitxer oblidat a l'últim commit | Sense publicar | git add <f> && git commit --amend --no-edit |
Sí (reflog) |
| Els últims N commits i el seu contingut | Sense publicar | git reset --hard HEAD~N |
Sí els commits; no el que no s'ha confirmat |
| Un commit del mig de la branca | Sense publicar | git rebase -i amb drop (05-02) |
Sí (reflog) |
| Només un fitxer d'un commit | Sense publicar | git reset <c>~1 -- <f> i confirmar |
Sí |
| Un commit ja publicat | Publicat | git revert <sha> (05-06) |
Sí (és additiu) |
| Una fusió ja publicada | Publicat | git revert -m 1 <sha> (05-06) |
Sí |
| Un commit a la meva branca personal publicada | Publicat, branca pròpia | reset + push --force-with-lease (09-03) |
Sí, avisant |
| Tot: tornar a l'estat del remot | Qualsevol | git fetch && git reset --hard origin/<branca> |
Els commits sí; el que no s'ha confirmat no |
| Tot, inclosos fitxers nous | Qualsevol | git reset --hard && git clean -fdx |
No el que no s'ha confirmat |
La fila que governa totes les altres és la de "està publicat?". Si la resposta és sí i la branca és compartida, reset surt de la llista i l'única eina correcta és revert.
Errors Habituals i Consells
Error 1: creure que reset --hard esborra commits. No els esborra: mou la branca. Els commits continuen a .git/objects i el reflog els troba (09-04). El que sí que destrueix són els canvis sense confirmar.
Error 2: fer servir --hard quan n'hi havia prou amb --soft. Si només volies refer el commit, --soft ho conserva tot preparat. --hard s'emporta a més la teva feina en curs.
Error 3: confondre git restore <f> amb git restore --staged <f>. El primer destrueix el canvi al disc; el segon només el treu de l'índex i no perd res. Són gairebé oposats.
Error 4: esperar que git reset <commit> -- <ruta> mogui la branca. No ho fa mai, i per això --hard amb rutes dona error. És una altra operació amb el mateix nom.
Error 5: executar git clean -fdx copiat d'internet. Esborra .env, configuracions locals i tot el que està ignorat, sense recuperació possible. -n primer, sempre.
Error 6: fer reset en una branca compartida i forçar el push. Destrueix la feina de qui tingués els commits. En el que està publicat, revert (05-06).
Error 7: fer servir git reset --hard origin/main sense comprovar què hi ha de teu al davant. git log --oneline origin/main..HEAD costa un segon i et diu exactament què llençaràs.
Error 8: oblidar que --hard respecta els fitxers sense seguiment. No és un error greu, però explica el "he fet reset --hard i això continua aquí": no tenia seguiment.
Consell 1: quan dubtis entre --hard i no fer res, fes servir --keep. Fa el mateix, però avorta en lloc de trepitjar canvis locals.
Consell 2: git branch copia-<alguna-cosa> abans de qualsevol reset --hard. Costa dos segons i converteix l'operació en trivialment reversible.
Consell 3: fes servir git restore i git switch en lloc de git checkout. Els noms diuen el que fan i eviten errors per confusió.
Consell 4: git restore -p per treure els console.log. Quirúrgic, i conserva la resta de la feina.
Consell 5: git stash push -u abans de qualsevol neteja. Converteix l'irrecuperable en recuperable per un segon d'esforç.
Consell 6: recorda ORIG_HEAD. El desfer immediat d'un reset, merge, rebase o pull que acabes de lamentar.
Exercicis
Exercici 1: els tres modes, al mateix punt de partida
- Crea un repositori amb
app.jsi tres commits. Al tercer, afegeix-hi tambéestils.css. - Modifica
app.jssense confirmar i crea un fitxer nounotes.txtsense afegir-lo. - Executa
git reset --soft HEAD~1i anota la sortida degit status --shortigit log --oneline -1. - Desfés-ho amb
git reset --hard ORIG_HEAD... i observa què ha passat amb la teva modificació d'app.js. Explica-ho. - Repeteix l'experiment complet amb
--mixedi amb--hard, i omple una taula amb tres columnes:log -1, canvi sense confirmar aapp.js, inotes.txt. - Explica per què
notes.txtsobreviu fins i tot a--hard.
Exercici 2: reset amb ruta contra reset sense ruta
- Sobre un repositori amb quatre commits, on el tercer va modificar
app.js,estils.cssiindex.html, provagit reset --hard HEAD~1 -- app.jsi explica l'error. - Fes servir
git reset HEAD~2 -- estils.cssi comprova ambgit status,git diffigit diff --cacheden quina zona ha quedat el canvi. - Confirma. Comprova que has desfet només la part d'
estils.cssdel tercer commit i que la resta continua al projecte. - Aconsegueix el mateix resultat amb
git restore --source=... --staged --worktreei compara l'estat del directori de treball en tots dos casos.
Exercici 3: el límit real de la recuperació
- En un repositori de proves, crea tres situacions alhora: un commit sense publicar, un canvi preparat amb
git add, i un canvi només al disc. - Executa
git reset --hard HEAD~1. - Recupera el commit fent servir el reflog.
- Intenta recuperar el canvi preparat amb
git fsck --lost-foundigit cat-file -p. - Intenta recuperar el canvi que era només al disc. Documenta per què no és possible.
- Repeteix tot l'exercici executant abans
git stash push -ui explica què canvia.
Solucions
Solució 1:
mkdir /tmp/p9-02 && cd /tmp/p9-02 && git init -q -b main
git config user.name "Ana Ferrer"; git config user.email "[email protected]"
echo "// gestor-tasques" > app.js && git add . && git commit -q -m "chore: inici"
echo "// llistat" >> app.js && git commit -q -am "feat: llistat"
echo "// filtre" >> app.js && echo "body{margin:0}" > estils.css
git add . && git commit -q -m "feat: filtre i estils"
# 2. Estat brut abans del reset
echo "// FEINA SENSE CONFIRMAR" >> app.js
echo "notes privades" > notes.txt
git status --shortEl commit ha sortit de la branca i el seu contingut és preparat (M i A a la primera columna). I aquí hi ha un detall fi: app.js apareix una sola vegada perquè l'índex conté tant el del commit desfet com la teva modificació sense confirmar encara fora... en realitat la modificació d'app.js continua al directori de treball, i l'índex té la versió del commit desfet. Comprova-ho:
git diff --cached --stat # el del commit desfet, preparat
git diff --stat # la teva modificació sense confirmar, encara pendentRes no s'ha perdut: les dues capes són intactes i separades.
# 4. Desfer amb ORIG_HEAD
git reset --hard ORIG_HEAD
git log --oneline -1
git status --short
grep -c "FEINA SENSE CONFIRMAR" app.jsLa branca ha tornat al seu lloc, però la modificació d'app.js ha desaparegut: --hard l'ha sobreescrita. ORIG_HEAD torna la posició de la branca, no la teva feina en curs. És exactament l'advertiment de l'apartat 8.
| Ordre | git log -1 |
Modificació d'app.js |
notes.txt |
|---|---|---|---|
git reset --soft HEAD~1 |
b52c9d1 |
Conservada (al disc) | Intacte |
git reset --mixed HEAD~1 |
b52c9d1 |
Conservada (barrejada amb la del commit) | Intacte |
git reset --hard HEAD~1 |
b52c9d1 |
PERDUDA | Intacte |
notes.txt no és a l'índex: Git no el segueix. reset opera sobre HEAD, l'índex i els fitxers que Git coneix; un fitxer sense seguiment li és invisible. Per esborrar-lo cal git clean (apartat 7).
Solució 2:
mkdir /tmp/p9-02-b && cd /tmp/p9-02-b && git init -q -b main
echo "v1" > app.js; echo "v1" > estils.css; echo "v1" > index.html
git add . && git commit -q -m "c1"
echo "v2" > app.js && git commit -q -am "c2"
echo "v3" > app.js; echo "v3" > estils.css; echo "v3" > index.html
git add . && git commit -q -m "c3: toca els tres fitxers"
echo "v4" > app.js && git commit -q -am "c4"Amb una ruta, reset mai no toca el directori de treball ni mou la branca; --hard implica totes dues coses, així que és contradictori. Git ho rebutja en lloc de fer alguna cosa a mitges.
# 2. La forma correcta
git reset HEAD~2 -- estils.css # HEAD~2 = c2, és a dir, abans de c3
git status --shortgit diff --cached estils.css # índex vs HEAD: sí que hi ha canvi
git diff estils.css # treball vs índex: també, en sentit invers
cat estils.cssClau: el fitxer al disc continua sent v3. Només l'índex té v1. Per això git diff (treball vs índex) mostra el canvi invers. I la branca no s'ha mogut:
# 3. Confirmar només aquesta part
git commit -q -m "revert parcial: torna estils.css a l'estat previ a c3"
cat app.js estils.css index.htmlapp.js conserva v4, index.html conserva v3 (el que va aportar c3), i només estils.css ha tornat enrere. S'ha desfet una tercera part d'un commit.
Però atenció: el fitxer al disc continuava sent v3 en confirmar. El commit diu v1 i el disc diu v3, així que ara hi ha una modificació pendent. Aquesta asimetria és justament el que evita restore:
# 4. La forma moderna, que deixa disc i índex coherents
git restore --source=HEAD~3 --staged --worktree estils.css
git status --short
cat estils.cssÍndex i directori de treball coincideixen. Aquesta és la diferència pràctica: reset -- <ruta> només prepara; restore --staged --worktree prepara i escriu al disc. Per a l'ús diari, restore és més predictible.
Solució 3:
mkdir /tmp/p9-02-c && cd /tmp/p9-02-c && git init -q -b main
echo "base" > app.js && git add . && git commit -q -m "c1"
# 1. Les tres situacions
echo "COMMIT SENSE PUBLICAR" >> app.js && git commit -q -am "c2: feina confirmada"
echo "CANVI PREPARAT" >> estils.css && git add estils.css
echo "CANVI NOMES AL DISC" >> app.js
git status --shortTot fora: el commit, el fitxer preparat i la modificació al disc.
3f8a1d6 HEAD@{0}: reset: moving to HEAD~1
8f4c2a9 HEAD@{1}: commit: c2: feina confirmada
3f8a1d6 HEAD@{2}: commit (initial): c1Recuperat íntegre.
El git add el va salvar. En preparar el fitxer, Git va crear el blob i el va escriure a .git/objects. El reset --hard va treure la referència des de l'índex, però l'objecte s'hi va quedar.
# 5. El canvi que era només al disc
git fsck --lost-found | wc -l
grep -c "NOMES AL DISC" $(git rev-list --objects --all | awk '{print $1}' > /dev/null; echo app.js)Només hi ha un objecte penjant, el de l'add. La línia CANVI NOMES AL DISC no existeix enlloc de .git. Mai no se'n va calcular el hash, mai no es va comprimir, mai no es va escriure. Git no pot recuperar una cosa de la qual no va tenir mai notícia. Les úniques vies reals serien l'historial local del teu editor (VS Code, IntelliJ i Vim amb undofile el desen) o una còpia de seguretat del sistema de fitxers.
# 6. Amb stash previ, tot canvia
cd /tmp && rm -rf p9-02-d && mkdir p9-02-d && cd p9-02-d && git init -q -b main
echo "base" > app.js && git add . && git commit -q -m "c1"
echo "COMMIT SENSE PUBLICAR" >> app.js && git commit -q -am "c2"
echo "CANVI PREPARAT" >> estils.css && git add estils.css
echo "CANVI NOMES AL DISC" >> app.js
echo "fitxer nou sense afegir" > esborrany.txt
git stash push -u -m "per si de cas"
git reset --hard HEAD~1
git stash listTot intacte, inclòs el fitxer sense seguiment gràcies a -u. El desament temporal va convertir les tres capes en commits ocults dins de la base de dades i, per tant, en una cosa recuperable fins i tot si s'hagués esborrat el desament (lliçó 09-04).
Un segon de git stash push -u compra la diferència entre "he perdut dues hores" i "no ha passat res".
Conclusió
reset deixa de fer por tan bon punt s'entén que fa dues coses amb el mateix nom.
git reset <commit>mou la branca. No esborra commits: reescriu un fitxer de 41 bytes. Els commits que deixen de ser assolibles continuen íntegres a la base de dades.- Els tres modes es distingeixen per fins on arriben:
--softnomés mouHEAD;--mixed(per defecte) a més reescriu l'índex;--harda més sobreescriu el directori de treball. Només--harddestrueix alguna cosa, i el que destrueix són els canvis sense confirmar, no els commits. Els fitxers sense seguiment no els toca. --keepés el--hardprudent: avorta en lloc de trepitjar la teva feina local.git reset <commit> -- <ruta>és una altra operació: mai no mou la branca, mai no toca el disc, només omple l'índex. Per això--hardamb rutes és un error.- Per a la feina diària,
git restore:--stagedper despreparar (no perd res), sense opcions per descartar al disc (destrueix),--sourceper portar d'un altre commit i-pper fer-ho tros a tros. git cleanés l'única ordre d'aquest mòdul sense xarxa de seguretat, perquè el que mai no es va confirmar no és a.git/objects.-nabans que-f, sempre;-xmereix llegir-se dues vegades;git stash push -uho converteix tot en recuperable.ORIG_HEADdesfà l'últimreset,merge,rebaseopull. Només desa una posició.- I la decisió, en una pregunta: està publicat? Si no,
reset,--amendorebase -i. Si sí i la branca és compartida,revert(lliçó 05-06), sense excepcions.
Queda pendent el cas que la taula de decisió despatxa amb un "veure 09-03": què fer quan allò que s'ha embolicat no és dins del teu repositori, sinó entre el teu repositori i el servidor. El push rebutjat, el missatge de branques divergents, el company que va reescriure l'historial publicat i et va deixar amb feina penjant d'una base que ja no existeix.
Tot això, i el tancament de la promesa que vam deixar oberta a la lliçó 04-05, a la lliçó 09-03: Resolent Divergències amb el Remot.
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ó
