In parole povere, cosa fa "git reset"?


674

Ho visto post interessanti che spiegano sottigliezze git reset.

Sfortunatamente, più ne leggo, più sembra che non lo capisca completamente. Vengo da un background SVN e Git è un paradigma completamente nuovo. Sono diventato facilmente mercuriale, ma Git è molto più tecnico.

Penso che git resetsia vicino hg revert, ma sembra che ci siano differenze.

Quindi cosa fa esattamente git reset? Si prega di includere spiegazioni dettagliate su:

  • le opzioni --hard, --softe --merge;
  • la strana notazione che usi con HEADcome HEAD^e HEAD~1;
  • casi d'uso concreti e flussi di lavoro;
  • conseguenze sulla copia di lavoro, sul HEADlivello di stress globale e.

17
Penso che A Visual Git Reference dia una buona idea di cosa succede quando si usano i comandi git comuni.

Risposte:


992

In generale, git resetla funzione è quella di prendere il ramo corrente e resettarlo per puntare altrove, e possibilmente portare l'indice e l'albero di lavoro lungo. Più concretamente, se il tuo ramo principale (attualmente estratto) è così:

- A - B - C (HEAD, master)

e ti rendi conto che vuoi che il master indichi B, non C, lo userai git reset Bper spostarlo lì:

- A - B (HEAD, master)      # - C is still here, but there's no branch pointing to it anymore

Digressione: è diversa da una cassa. Se dovessi correre git checkout B, otterresti questo:

- A - B (HEAD) - C (master)

Sei finito in uno stato HEAD distaccato. HEAD, albero di lavoro, indicizza tutte le corrispondenze B, ma il ramo principale è stato lasciato alle C. Se effettui un nuovo commit Da questo punto, otterrai questo, che probabilmente non è quello che desideri:

- A - B - C (master)
       \
        D (HEAD)

Ricorda, il reset non effettua commit, ma aggiorna solo un ramo (che è un puntatore a un commit) per puntare a un commit diverso. Il resto sono solo dettagli di ciò che accade al tuo indice e all'albero di lavoro.

Casi d'uso

Coprirò molti dei principali casi d'uso git resetall'interno delle mie descrizioni delle varie opzioni nella sezione successiva. Può davvero essere usato per una grande varietà di cose; il thread comune è che tutti implicano il ripristino del ramo, dell'indice e / o dell'albero di lavoro per puntare / abbinare un determinato commit.

Cose da fare attenzione

  • --hardpuò farti perdere davvero il lavoro. Modifica il tuo albero di lavoro.

  • git reset [options] commitpuò farti perdere (sorta di) commit. Nell'esempio di giocattolo sopra, abbiamo perso il commit C. È ancora nel repository e puoi trovarlo guardando git reflog show HEADo git reflog show master, ma in realtà non è più accessibile da nessun ramo.

  • Git elimina definitivamente tali commit dopo 30 giorni, ma fino ad allora puoi recuperare C puntando di nuovo un ramo ( git checkout C; git branch <new branch name>).

argomenti

Parafrasando la pagina man, l'utilizzo più comune è del modulo git reset [<commit>] [paths...], che reimposterà i percorsi dati al loro stato dal commit dato. Se i percorsi non vengono forniti, viene reimpostato l'intero albero e, se il commit non viene fornito, viene considerato HEAD (il commit corrente). Questo è un modello comune tra i comandi git (ad esempio checkout, diff, log, sebbene la semantica esatta vari), quindi non dovrebbe essere troppo sorprendente.

Ad esempio, git reset other-branch path/to/fooreimposta tutto nel percorso / to / foo al suo stato in altro ramo, git reset -- .reimposta la directory corrente al suo stato in HEAD e un semplice git resetreimposta tutto al suo stato in HEAD.

L'albero di lavoro principale e le opzioni di indice

Esistono quattro opzioni principali per controllare cosa succede all'albero di lavoro e all'indice durante il ripristino.

Ricorda, l'indice è "area di gestione temporanea" di git - è dove vanno le cose quando dici git addin preparazione di impegnarti.

  • --hardrende tutto corrispondente al commit a cui hai reimpostato. Questo è probabilmente il più facile da capire. Tutte le modifiche locali vengono bloccate. Un uso primario è spazzare via il tuo lavoro ma non commutare i commit: git reset --hardsignifica git reset --hard HEAD, cioè non cambiare il ramo ma sbarazzarsi di tutti i cambiamenti locali. L'altro è semplicemente spostare un ramo da un posto all'altro e mantenere sincronizzati l'indice / l'albero di lavoro. Questo è quello che può davvero farti perdere il lavoro, perché modifica il tuo albero di lavoro. Assicurati di voler eliminare il lavoro locale prima di eseguirne uno reset --hard.

  • --mixedè il valore predefinito, ovvero git resetsignifica git reset --mixed. Reimposta l'indice, ma non l'albero di lavoro. Questo significa che tutti i tuoi file sono intatti, ma qualsiasi differenza tra il commit originale e quello a cui resetti apparirà come modifiche locali (o file non tracciati) con stato git. Usalo quando ti rendi conto di aver commesso degli errori, ma vuoi mantenere tutto il lavoro che hai fatto in modo da poterlo riparare e ricominciare. Per eseguire il commit, dovrai aggiungere nuovamente i file all'indice ( git add ...).

  • --softnon tocca l'indice o l' albero di lavoro. Tutti i tuoi file sono intatti come con --mixed, ma tutte le modifiche vengono visualizzate come changes to be committedcon lo stato git (ovvero il check-in in preparazione per il commit). Usalo quando ti rendi conto di aver commesso degli errori, ma tutto va bene - tutto ciò che devi fare è ricominciarlo in modo diverso. L'indice non è stato toccato, quindi puoi eseguire immediatamente il commit, se lo desideri: il commit risultante avrà lo stesso contenuto di quello che eri prima del ripristino.

  • --mergeè stato aggiunto di recente e ha lo scopo di aiutarti a interrompere una fusione fallita. Ciò è necessario perché git mergein realtà ti consentirà di eseguire l'unione con un albero di lavoro sporco (uno con modifiche locali) purché tali modifiche si trovino in file non interessati dall'unione. git reset --mergeripristina l'indice (come --mixed- tutte le modifiche vengono visualizzate come modifiche locali) e ripristina i file interessati dall'unione, ma lascia gli altri soli. Si spera che questo ripristini tutto come prima della brutta fusione. Di solito lo userai come git reset --merge(significato git reset --merge HEAD) perché vuoi solo resettare l'unione, non effettivamente spostare il ramo. ( HEADnon è stato ancora aggiornato, poiché l'unione non è riuscita)

    Per essere più concreti, supponiamo di aver modificato i file A e B e di tentare di unire in un ramo che ha modificato i file C e D. L'unione non riesce per qualche motivo e si decide di interromperlo. Tu usi git reset --merge. Riporta C e D al modo in cui erano HEAD, ma lascia le tue modifiche ad A e B da sole, poiché non facevano parte del tentativo di fusione.

Voglio sapere di più?

Penso che man git resetsia davvero abbastanza buono per questo - forse hai bisogno di un po 'di senso del modo in cui git funziona perché possano davvero affondare. In particolare, se si prende il tempo di leggerli attentamente, quelle tabelle che descrivono in dettaglio gli stati dei file nell'indice e l'albero di lavoro per tutte le varie opzioni e casi sono molto utili. (Ma sì, sono molto densi - stanno trasmettendo moltissime informazioni di cui sopra in una forma molto concisa.)

Notazione strana

La "strana notazione" ( HEAD^e HEAD~1) che menzioni è semplicemente una scorciatoia per specificare i commit, senza dover usare un nome di hash come 3ebe3f6. È completamente documentato nella sezione "specifica delle revisioni" della pagina man per git-rev-parse, con molti esempi e sintassi correlata. Il cursore e la tilde in realtà significano cose diverse :

  • HEAD~è l'abbreviazione di HEAD~1e indica il primo genitore del commit. HEAD~2indica il primo genitore del primo genitore del commit. Pensa HEAD~na "n si impegna prima di HEAD" o "l'antenata generazione di HEAD".
  • HEAD^(o HEAD^1) indica anche il primo genitore del commit. HEAD^2indica il secondo genitore del commit . Ricorda, un normale commit di unione ha due genitori: il primo genitore è il commit unito e il secondo genitore è il commit che è stato unito. In generale, le fusioni possono effettivamente avere arbitrariamente molti genitori (fusioni di polpo).
  • I ^e ~operatori possono essere messe insieme, come nella HEAD~3^2, il secondo genitore del antenato di terza generazione HEAD, HEAD^^2il secondo genitore del primo genitore HEAD, o anche HEAD^^^, che è equivalente a HEAD~3.

custode e tilde


"userete git reset per spostarlo lì." perché non usi git checkout per farlo?
e-soddisfa il

5
@ e-satis: il checkout di git sposta HEAD, ma lascia il ramo dov'era. Questo è quando vuoi spostare il ramo.
Cascabel,

Quindi, se ho capito bene, resettare B farebbe: - A - B - C - B (master) mentre il checkout B farebbe - A - B (master)?
e-soddisf

32
i documenti sono buoni anche se ci vuole un'eternità a leggerli e sono molto densi e ci vuole un'eternità per verificare che dicono che funziona come se già sapessi come funziona. Non sembra che i documenti siano buoni per me ...
Kirby,

4
Una spiegazione molto semplice e comprensibile è data da questa risposta SO: stackoverflow.com/questions/3528245/whats-the-difference-b
Between-git-reset-mixed-soft-

80

Ricorda che in gitte hai:

  • il HEADpuntatore , che ti dice su quale commit stai lavorando
  • l' albero di lavoro , che rappresenta lo stato dei file sul sistema
  • l' area di stadiazione (chiamata anche indice ), che "mette in scena" cambia in modo da poter essere successivamente impegnata insieme

Si prega di includere spiegazioni dettagliate su:

--hard, --softE --merge;

In ordine crescente di pericolosità:

  • --softsi sposta HEADma non tocca l'area di stadiazione o l'albero di lavoro.
  • --mixedsposta HEADe aggiorna l'area di gestione temporanea, ma non l'albero di lavoro.
  • --mergesi sposta HEAD, ripristina l'area di gestione temporanea e tenta di spostare tutte le modifiche dell'albero di lavoro nel nuovo albero di lavoro.
  • --hardsposta HEAD e regola l'area di stadiazione e l'albero di lavoro sul nuovo HEAD, gettando via tutto.

casi d'uso concreti e flussi di lavoro;

  • Utilizzare --softquando si desidera passare a un altro commit e correggere le cose senza "perdere il posto". È abbastanza raro che tu abbia bisogno di questo.

-

# git reset --soft example
touch foo                            // Add a file, make some changes.
git add foo                          // 
git commit -m "bad commit message"   // Commit... D'oh, that was a mistake!
git reset --soft HEAD^               // Go back one commit and fix things.
git commit -m "good commit"          // There, now it's right.

-

  • Usa --mixed(che è l'impostazione predefinita) quando vuoi vedere come appaiono le cose in un altro commit, ma non vuoi perdere le modifiche che hai già.

  • Utilizzare --mergequando si desidera spostarsi in un nuovo punto ma incorporare le modifiche che già si hanno in quell'albero di lavoro.

  • Utilizzare --hardper cancellare tutto e avviare una nuova lavagna al nuovo commit.


2
Questo non è il caso d'uso previsto per reset --merge. Non esegue un'unione a tre vie. È davvero solo per ripristinare le fusioni in conflitto, come descritto nei documenti. Ti consigliamo checkout --mergedi fare ciò di cui stai parlando. Se vuoi spostare anche il ramo, penso che l'unico modo sia seguire un po 'di checkout / reset per trascinarlo.
Cascabel,

@Jefromi »Sì, non l'ho espresso molto bene. Per "un nuovo posto" intendevo "un nuovo posto in cui non hai l'unione in conflitto".
John Feminella,

1
Ah, capisco. Penso che la cosa importante qui sia che, a meno che tu non sappia davvero cosa stai facendo, probabilmente non vorrai mai usarlo reset --mergecon nessun bersaglio oltre (il default) HEAD, perché in casi oltre a interrompere una fusione in conflitto, andrà via informazioni che potresti altrimenti salvare.
Cascabel,

2
Ho trovato questa risposta la più semplice e utile
Jazzepi

Si prega di aggiungere informazioni su questi comandi: git resete git reset -- ..
Flimm,

35

Il post Reset Demysified nel blog Pro Git fornisce una spiegazione semplicissima su git resete git checkout.

Dopo tutta la discussione utile nella parte superiore di quel post, l'autore riduce le regole ai seguenti tre semplici passaggi:

Questo è fondamentalmente. Il resetcomando sovrascrive questi tre alberi in un ordine specifico, fermandosi quando glielo dici.

  1. Sposta il ramo su cui punta HEAD (ferma se --soft)
  2. ALLORA, fai in modo che l'indice assomigli a quello (fermati qui a meno che --hard)
  3. ALLORA, fai sembrare la Directory di lavoro così

Ci sono anche --mergee --keepopzioni, ma preferirei semplificare le cose per ora - sarà per un altro articolo.


25

Quando commetti qualcosa per git devi prima mettere in scena (aggiungi all'indice) le tue modifiche. Questo significa che devi aggiungere git a tutti i file che vuoi includere in questo commit prima che git li consideri parte del commit. Diamo prima un'occhiata all'immagine di un repository git: inserisci qui la descrizione dell'immagine

quindi è semplice ora. Dobbiamo lavorare nella directory di lavoro, creando file, directory e tutto il resto. Queste modifiche sono modifiche non tracciate. Per tenerli tracciati, dobbiamo aggiungerli all'indice git usando il comando git add . Una volta aggiunti all'indice git. Ora possiamo eseguire il commit di queste modifiche, se vogliamo inviarlo al repository git.

Ma all'improvviso siamo venuti a conoscenza mentre ci impegniamo a disporre di un file aggiuntivo che è stato aggiunto nell'indice per non inviare il repository git. Significa che non vogliamo quel file nell'indice. Ora la domanda è come rimuovere quel file dall'indice git, dato che abbiamo usato git add per metterli nell'indice sarebbe logico usare git rm ? Sbagliato! git rm eliminerà semplicemente il file e aggiungerà la cancellazione all'indice. Quindi cosa si fa adesso:

Uso:-

git reset

Cancella il tuo indice, lascia intatta la tua directory di lavoro. (semplicemente rimettendo in scena tutto).

Può essere utilizzato con il numero di opzioni con esso. Esistono tre opzioni principali da utilizzare con git reset: --hard, --soft e --mixed . Questi influiscono su ciò che viene ripristinato oltre al puntatore HEAD quando si ripristina.

Innanzitutto --hard ripristina tutto. La tua directory attuale sarebbe esattamente come se avessi seguito quel ramo da sempre. La directory di lavoro e l'indice vengono modificati in tale commit. Questa è la versione che utilizzo più spesso. git reset --hard è qualcosa come Svn Revert .

Successivamente, l'esatto contrario, —soft , non ripristina l'albero di lavoro né l'indice. Sposta solo il puntatore HEAD. Questo lascia il tuo stato attuale con qualsiasi modifica diversa dal commit a cui stai passando in atto nella tua directory e “messo in scena” per il commit. Se si effettua un commit a livello locale ma non è stato inviato il commit al server git, è possibile ripristinare il commit precedente e ricominciare con un buon messaggio di commit.

Infine, --mixed reimposta l'indice, ma non l'albero di lavoro. Quindi i cambiamenti sono ancora tutti lì, ma sono "non messi in scena" e dovrebbero essere aggiunti o aggiunti git commit -a . lo usiamo a volte se ci impegniamo più di quanto intendessimo con git commit -a, possiamo annullare il commit con git reset --mix, aggiungere le cose che vogliamo impegnare e semplicemente impegnarle.

Differenza tra git revert e git reset : -


In parole semplici, git reset è un comando per "correggere errori non commessi" e git revert è un comando per "errore commesso a correzione" .

Significa che se abbiamo commesso un errore in qualche modifica e commesso e spinto lo stesso per git repo, allora git revert è la soluzione. E se nel caso in cui abbiamo identificato lo stesso errore prima di premere / confermare, possiamo usare git reset per risolvere il problema.

Spero che ti aiuterà a sbarazzarti della tua confusione.


2
Questa è una bella risposta in inglese semplice, come richiesto dall'OP.
Episodex,

1
Anche se potrei perdere questo nella tua risposta. Cosa è git reset HEADdi default? --hard, --softO --mixed? Ottima risposta tra l'altro.
giannis christofakis,

1
Ottima risposta, ma vorrei chiarire che git reset --hardti farà perdere alcuni dati. E c'è un punto che potrebbe essere sbagliato (anche se non sono sicuro al 100% ... Sto ancora imparando!): Parlando di --mixedte dici che "usiamo questo a volte se ci impegniamo più di quanto intendessimo con git commit -a". Intendevi forse "se mettessimo in scena più di quanto intendessimo con git stage ."? Se l'hai davvero commesso, penso che sia troppo tardi (come dici alla fine, git reset è un comando per "correggere errori non commessi")
Fabio dice Reinstate Monica il

6

TL; DR

git resetripristina la gestione temporanea sull'ultimo commit. Utilizzare --hardper ripristinare anche i file nella directory di lavoro all'ultimo commit.

VERSIONE PIÙ LUNGA

Ma questo è ovviamente semplicistico, quindi le molte risposte piuttosto dettagliate. Per me aveva più senso leggere git resetnel contesto delle modifiche annullate. Ad esempio vedere questo:

Se git revert è un modo "sicuro" per annullare le modifiche, puoi pensare a git reset come al metodo pericoloso. Quando si annulla con git reset (e le commit non fanno più riferimento a nessun ref o reflog), non c'è modo di recuperare la copia originale: si tratta di un annullamento permanente. Bisogna fare attenzione quando si utilizza questo strumento, in quanto è uno dei soli comandi Git che ha il potenziale per perdere il lavoro.

Da https://www.atlassian.com/git/tutorials/undoing-changes/git-reset

e questo

A livello di commit, il ripristino è un modo per spostare la punta di un ramo su un altro commit. Questo può essere usato per rimuovere commit dal ramo corrente.

Da https://www.atlassian.com/git/tutorials/resetting-checking-out-and-reverting/commit-level-operations


2

Si noti che questa è una spiegazione semplificata intesa come primo passo nel cercare di comprendere questa complessa funzionalità.

Può essere utile per gli studenti visivi che desiderano visualizzare l'aspetto del loro progetto dopo ciascuno di questi comandi:


Per coloro che usano Terminal con il colore acceso (git config --global color.ui auto):

git reset --soft A e vedrai la roba di B e C in verde (messa in scena e pronta per il commit)

git reset --mixed A(o git reset A) e vedrai le cose di B e C in rosso (non messe in scena e pronte per essere messe in scena (verde) e poi impegnate)

git reset --hard A e non vedrai più le modifiche di B e C da nessuna parte (sarà come se non fossero mai esistite)


O per coloro che usano un programma GUI come 'Tower' o 'SourceTree'

git reset --soft A e vedrai le cose di B e C nell'area 'file in scena' pronte per il commit

git reset --mixed A(o git reset A) e vedrai i contenuti di B e C nell'area "file non in scena" pronti per essere spostati in scena e poi impegnati

git reset --hard A e non vedrai più le modifiche di B e C da nessuna parte (sarà come se non fossero mai esistite)


1

Il checkout punta la testa a un commit specifico.

Il ripristino punta un ramo su un commit specifico. (Un ramo è un puntatore a un commit.)

Per inciso, se la tua testa non punta a un commit che è anche indicato da un ramo, allora hai una testa staccata. (si è rivelato essere sbagliato. Vedi commenti ...)


1
Non per nitpick, ma (sì, in realtà è nitpicking ma aggiungiamolo per il completamento) la tua terza frase è tecnicamente falsa. Supponiamo che HEAD stia puntando sul ramo B che a sua volta sta puntando a commettere abc123. Se ora esegui il checkout di commit abc123, HEAD e il ramo B puntano entrambi a commit abc123 E HEAD viene rimosso. Commettere a questo punto non aggiorna la posizione della diramazione B. Avresti potuto dire "se la tua testa non punta a un ramo, allora hai una testa staccata"
RomainValeri

@RomainValeri Cosa farà l'impegno in quella circostanza?
Ian Warburton,

1
Il commit creerebbe commit che non fanno riferimento a nessun ramo, e il ramo B continuerebbe a puntare allo stesso commit abc123 anche dopo averlo commesso più volte. Implica che questi commit diventerebbero candidati per la raccolta dei rifiuti quando HEAD smetterà di indicare l'ultimo commit in questa serie "selvaggia" di commit.
Romain Valeri
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.