Rimuovi commit specifico


287

Stavo lavorando con un amico su un progetto e ha modificato un mucchio di file che non avrebbero dovuto essere modificati. In qualche modo ho unito il suo lavoro al mio, sia quando l'ho estratto, sia quando ho provato a scegliere i file specifici che desideravo. Ho cercato e giocato per molto tempo, cercando di capire come rimuovere i commit che contengono le modifiche a quei file, sembra essere un gioco tra revert e rebase, e non ci sono esempi chiari, e il i documenti presumono che io sappia più di me.

Quindi, ecco una versione semplificata della domanda:

Dato il seguente scenario, come rimuovo commit 2?

$ mkdir git_revert_test && cd git_revert_test

$ git init
Initialized empty Git repository in /Users/josh/deleteme/git_revert_test/.git/

$ echo "line 1" > myfile

$ git add -A

$ git commit -m "commit 1"
[master (root-commit) 8230fa3] commit 1
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 myfile

$ echo "line 2" >> myfile

$ git commit -am "commit 2"
[master 342f9bb] commit 2
 1 files changed, 1 insertions(+), 0 deletions(-)

$ echo "line 3" >> myfile

$ git commit -am "commit 3"
[master 1bcb872] commit 3
 1 files changed, 1 insertions(+), 0 deletions(-)

Il risultato atteso è

$ cat myfile
line 1
line 3

Ecco un esempio di come ho cercato di ripristinare

$ git revert 342f9bb
Automatic revert failed.  After resolving the conflicts,
mark the corrected paths with 'git add <paths>' or 'git rm <paths>'
and commit the result.

5
Se qualcuno lo trova durante la ricerca dello stesso problema, ecco cosa ho finito per fare: Copia e incolla. Sul serio. Abbiamo trascorso più di 6 ore a cercare di far funzionare le soluzioni suggerite, senza risultati. Alla fine, ero fuori dal tempo, ho recuperato l'originale e ho appena copiato / incollato circa 20 file. Ci sono voluti meno di 5 minuti e le cose sono andate bene da allora (anche quando quei file vengono uniti a cambiamenti in altri rami che sono avvenuti prima di questo fiasco). Ti suggerisco di adottare anche questo approccio. Non solo è il più semplice, sospetto anche che sia l'unica cosa che funziona.
Joshua Cheek,

Ho affrontato un problema simile, ma forse più complesso: avevo un ramo con centinaia di commit che volevo schiacciare. Purtroppo i commit di un altro ramo sono stati riuniti nuovamente nel ramo in un punto intermedio, quindi è stato necessario un "unmerge" prima che potessi schiacciare. Ho seguito un percorso simile come suggerito da tk di seguito (raccolta ciliegia + usando la notazione di intervallo), ma ha prodotto conflitti in alcuni altri file. Alla fine, copia e incolla + alcune modifiche manuali sono state il percorso più semplice e prevedibile. Sicuramente merita una considerazione se ti ritrovi a passare troppo tempo su questo.
Federico

Risposte:


73

L'algoritmo utilizzato da Git per il calcolo del differenziale da ripristinare richiede questo

  1. le righe da ripristinare non vengono modificate da alcun commit successivo.
  2. che non ci saranno altri impegni "adiacenti" più avanti nella storia.

La definizione di "adiacente" si basa sul numero predefinito di righe da un contesto diff, che è 3. Quindi se 'myfile' è stato costruito in questo modo:

$ cat >myfile <<EOF
line 1
junk
junk
junk
junk
line 2
junk
junk
junk
junk
line 3
EOF
$ git add myfile
$ git commit -m "initial check-in"
 1 files changed, 11 insertions(+), 0 deletions(-)
 create mode 100644 myfile

$ perl -p -i -e 's/line 2/this is the second line/;' myfile
$ git commit -am "changed line 2 to second line"
[master d6cbb19] changed line 2
 1 files changed, 1 insertions(+), 1 deletions(-)

$ perl -p -i -e 's/line 3/this is the third line/;' myfile
$ git commit -am "changed line 3 to third line"
[master dd054fe] changed line 3
 1 files changed, 1 insertions(+), 1 deletions(-)

$ git revert d6cbb19
Finished one revert.
[master 2db5c47] Revert "changed line 2"
 1 files changed, 1 insertions(+), 1 deletions(-)

Quindi funziona tutto come previsto.

La seconda risposta è stata molto interessante. C'è una funzione che non è stata ancora ufficialmente rilasciata (sebbene sia disponibile in Git v1.7.2-rc2) chiamata Revert Strategy. Puoi invocare git in questo modo:

git ripristina --strategy resolve <commit>

e dovrebbe fare un lavoro migliore per capire cosa intendevi. Non so quale sia l'elenco delle strategie disponibili, né conosco la definizione di alcuna strategia.


1
perl -pè utile per scrivere programmi molto brevi (una riga) che eseguono il loop e passano il loro input all'output, in modo simile a sed. perl -iviene utilizzato per la modifica dei file in atto . perl -eè come inviare il codice da valutare .
Hal Eisen,

281

Esistono quattro modi per farlo:

  • Modo pulito, ripristino ma mantenere nel registro il ripristino:

    git revert --strategy resolve <commit>
    
  • In modo duro, rimuovi del tutto solo l'ultimo commit:

    git reset --soft "HEAD^"
    

Nota: evitare git reset --hardin quanto eliminerà anche tutte le modifiche ai file dall'ultimo commit. Se --softnon funziona, piuttosto provare --mixedo --keep.

  • Rebase (mostra il registro degli ultimi 5 commit ed elimina le righe che non desideri, oppure riordina, oppure annulla più commit in uno o esegui qualsiasi altra cosa desideri, questo è uno strumento molto versatile):

    git rebase -i HEAD~5
    

E se viene commesso un errore:

git rebase --abort
  • Quick rebase: rimuovi solo un commit specifico usando il suo id:

    git rebase --onto commit-id^ commit-id
    
  • Alternative: puoi anche provare:

    git cherry-pick commit-id
    
  • Ancora un'altra alternativa:

    git revert --no-commit
    
  • Come ultima risorsa, se hai bisogno di una completa libertà di modifica della cronologia (ad esempio, perché git non ti consente di modificare ciò che desideri), puoi utilizzare questa applicazione open source molto veloce : reposurgeon .

Nota: ovviamente, tutte queste modifiche vengono eseguite localmente, è necessario git pushsuccessivamente applicare le modifiche al telecomando. E nel caso in cui il repository non desideri rimuovere il commit ("nessun avanzamento rapido consentito", che si verifica quando si desidera rimuovere un commit già inviato), è possibile utilizzare git push -fper forzare il push delle modifiche.

Nota 2: se lavori su un ramo e devi forzare la spinta, dovresti assolutamente evitarlo git push --forceperché questo potrebbe sovrascrivere altri rami (se hai apportato modifiche in essi, anche se il tuo checkout corrente è su un altro ramo). Preferiscono specificare sempre il ramo remoto quando si forza di spinta : git push --force origin your_branch.


Sulla base dei suggerimenti di @gaborous: eseguire un "git rebase -i HEAD ~ 2". Ora hai diverse opzioni. Nel video puoi vedere alcune righe commentate: una ti dice che puoi semplicemente eliminare una linea (che dovrebbe essere il commit di cui vuoi sbarazzarti) e questo commit verrà rimosso insieme al suo log nella tua cronologia.
Ilker Cat

git revert --strategy solving <commit>. Questo comando ha funzionato per me. grazie :)
Swathin,

2
git rebase -i HEAD~5ha funzionato per me. Quindi ho rimosso i commit di cui non avevo bisogno e sono stato in grado di risolvere il problema in meno di 30 secondi. Grazie.
lv10

117

Ecco una soluzione semplice:

git rebase -i HEAD~x

(Nota: xè il numero di commit)

al momento dell'esecuzione si aprirà il file del blocco note. Inserisci dropoltre al tuo impegno.
Se non conosci Vim, fai clic su ogni parola selezionata che desideri modificare e quindi premi il tasto "I" (per la modalità di inserimento). Una volta terminata la digitazione, premi il tasto "esc" per uscire dalla modalità di inserimento.



inserisci qui la descrizione dell'immagine

e il gioco è fatto, hai appena sincronizzato la dashboard git e le modifiche verranno trasferite sul telecomando.

Se il commit che hai lasciato era già sul telecomando, dovrai forzare push. Poiché --force è considerato dannoso , utilizzare git push --force-with-lease.


C'è un buon modo per scoprire quale 'x' dovrebbe essere se conosci solo l'hash del commit in questione?
jlewkovich,

1
Per le persone con mentalità cpp: x (numero di commit) è inclusivo. ad es. HEAD ~ 4 include gli ultimi 4 commit.
Herpes Free Engineer

suggerimento perfetto. voglio solo aggiungere, questo elimina anche le modifiche dal commit eliminato.
Celerno

ha tentato di ripristinare 3 commit: git rebase -i HEAD-3 ha avuto un errore fatale: era necessaria una sola revisione non valida a monte 'HEAD-3'
Ustin

1
@Ustin sono ~ 3 non -3
JD-V

37

La tua scelta è tra

  1. mantenendo l'errore e introducendo una correzione e
  2. rimuovendo l'errore e cambiando la cronologia.

È necessario scegliere (1) se la modifica errata è stata rilevata da qualcun altro e (2) se l'errore è limitato a un ramo privato senza push.

Git Revert è uno strumento automatizzato da eseguire (1), crea un nuovo commit annullando alcuni commit precedenti. Vedrai l'errore e la rimozione nella cronologia del progetto, ma le persone che estraggono dal tuo repository non incontreranno problemi durante l'aggiornamento. Nel tuo esempio non funziona in modo automatizzato, quindi devi modificare "myfile" (per rimuovere la riga 2), fare git add myfilee git commitgestire il conflitto. Quindi finirai con quattro commit nella tua cronologia, con commit 4 ripristinando commit 2.

Se a nessuno importa che la tua cronologia cambi, puoi riscriverla e rimuovere il commit 2 (scelta 2). Il modo più semplice per farlo è usare git rebase -i 8230fa3. Questo ti farà cadere in un editor e puoi scegliere di non includere il commit errato rimuovendo il commit (e mantenendo "pick" accanto agli altri messaggi di commit. Leggi le conseguenze di ciò .


Rebase può essere complicato, dal momento che sembra che ci sia stata un'unione.
Cascabel,

3
git rebase -i 8230fa3 ed elimina la riga di commit ha funzionato perfettamente per me con le mie modifiche solo locali. Grazie!
Samuel,

26

Circa 1

Per prima cosa ottieni l'hash di commit (es: 1406cd61) che devi ripristinare. la soluzione semplice sarà sotto il comando,

$ git revert 1406cd61

se dopo il commit di 1406cd61 sono state apportate ulteriori modifiche relative ai file 1406cd61, il comando Above simple non funzionerà. Quindi devi fare i seguenti passaggi, che è la raccolta delle ciliegie.

Circa 2

Per favore segui la seguente serie di azioni, dal momento che stiamo usando --force, per farlo devi avere i diritti di amministratore sul repository git.

Passaggio 1: trova il commit prima del commit che desideri rimuoveregit log

Passaggio 2: verifica quel commitgit checkout <commit hash>

Passaggio 3: crea una nuova filiale utilizzando il commit del checkout correntegit checkout -b <new branch>

Passaggio 4: ora è necessario aggiungere il commit dopo il commit rimossogit cherry-pick <commit hash>

Passaggio 5: ora ripetere il passaggio 4 per tutti gli altri commit che si desidera mantenere.

Passaggio 6: una volta che tutti gli commit sono stati aggiunti alla nuova filiale e sono stati impegnati. Controlla che tutto sia nello stato corretto e funzioni come previsto. Ricontrolla tutto è stato eseguito il commit:git status

Passaggio 7: passa al ramo spezzatogit checkout <broken branch>

Passaggio 8: ora eseguire un hard reset sul ramo interrotto per il commit prima di quello che si desidera rimuoveregit reset --hard <commit hash>

Passaggio 9: unire il ramo fisso in questo ramogit merge <branch name>

Passaggio 10: reindirizzare le modifiche unite all'origine. ATTENZIONE: questo sovrascriverà il repository remoto!git push --force origin <branch name>

È possibile eseguire il processo senza creare un nuovo ramo sostituendo i passaggi 2 e 3 con i passaggi 8 e non eseguire i passaggi 7 e 9.


1
Il primo approccio ha funzionato come un fascino. Include un commit che specifica che il commit desiderato è stato ripristinato, il che è davvero utile ai fini del monitoraggio.
suarsenegger,

18

Puoi rimuovere commit indesiderati con git rebase. Supponi di aver incluso alcuni commit dal ramo tematico di un collega nel ramo tematico, ma in seguito decidi di non volerlo.

git checkout -b tmp-branch my-topic-branch  # Use a temporary branch to be safe.
git rebase -i master  # Interactively rebase against master branch.

A questo punto il tuo editor di testo aprirà la vista rebase interattiva. Per esempio

git-rebase-todo

  1. Rimuovi i commit che non desideri eliminando le loro righe
  2. Salva ed esci

Se il rebase non ha avuto esito positivo, elimina il ramo temporaneo e prova un'altra strategia. Altrimenti continua con le seguenti istruzioni.

git checkout my-topic-branch
git reset --hard tmp-branch  # Overwrite your topic branch with the temp branch.
git branch -d tmp-branch  # Delete the temporary branch.

Se stai spingendo il tuo argomento su un telecomando, potrebbe essere necessario forzare il push poiché la cronologia del commit è cambiata. Se altri stanno lavorando sullo stesso ramo, dai loro un avvertimento.


Potresti per favore dare un esempio di "provare un'altra strategia"?
Pfabri,

@pfabri, ad esempio, potresti scegliere due intervalli di commit in cui tralasci il commit errato. È possibile ripristinare il commit errato. Potresti evitare di usare una soluzione git annullando anche manualmente le modifiche o partendo da un nuovo ramo senza il commit errato e rifando manualmente le modifiche buone. Se il commit errato
Dennis

8

Da altre risposte qui, ero un po 'confuso con come git rebase -i potesse essere usato per rimuovere un commit, quindi spero che sia OK annotare il mio caso di test qui (molto simile al PO).

Ecco uno bashscript che puoi incollare per creare un repository di prova nella /tmpcartella:

set -x

rm -rf /tmp/myrepo*
cd /tmp

mkdir myrepo_git
cd myrepo_git
git init
git config user.name me
git config user.email me@myself.com

mkdir folder
echo aaaa >> folder/file.txt
git add folder/file.txt
git commit -m "1st git commit"

echo bbbb >> folder/file.txt
git add folder/file.txt
git commit -m "2nd git commit"

echo cccc >> folder/file.txt
git add folder/file.txt
git commit -m "3rd git commit"

echo dddd >> folder/file.txt
git add folder/file.txt
git commit -m "4th git commit"

echo eeee >> folder/file.txt
git add folder/file.txt
git commit -m "5th git commit"

A questo punto, abbiamo un file.txtcon questi contenuti:

aaaa
bbbb
cccc
dddd
eeee

A questo punto, HEAD è al 5 ° commit, HEAD ~ 1 sarebbe il 4 ° e HEAD ~ 4 sarebbe il 1 ° commit (quindi HEAD ~ 5 non esisterebbe). Diciamo che vogliamo rimuovere il 3o commit - possiamo emettere questo comando nella myrepo_gitdirectory:

git rebase -i HEAD~4

( Si noti che i git rebase -i HEAD~5risultati con "fatale: necessaria una singola revisione; HEAD upstream non valido ~ 5". ) Un editor di testo (vedi screenshot nella risposta di @Dennis ) si aprirà con questi contenuti:

pick 5978582 2nd git commit
pick 448c212 3rd git commit
pick b50213c 4th git commit
pick a9c8fa1 5th git commit

# Rebase b916e7f..a9c8fa1 onto b916e7f
# ...

Quindi riceviamo tutti i commit dal (ma non incluso ) il nostro HEAD richiesto ~ 4. Elimina la riga pick 448c212 3rd git commite salva il file; otterrai questa risposta da git rebase:

error: could not apply b50213c... 4th git commit

When you have resolved this problem run "git rebase --continue".
If you would prefer to skip this patch, instead run "git rebase --skip".
To check out the original branch and stop rebasing run "git rebase --abort".
Could not apply b50213c... 4th git commit

A questo punto apri myrepo_git / folder/file.txtin un editor di testo; vedrai che è stato modificato:

aaaa
bbbb
<<<<<<< HEAD
=======
cccc
dddd
>>>>>>> b50213c... 4th git commit

Fondamentalmente, gitvede che quando HEAD è arrivato al 2 ° commit, c'era contenuto di aaaa+ bbbb; e poi ha una patch di cccc+ aggiunto ddddche non sa come aggiungere al contenuto esistente.

Quindi qui gitnon puoi decidere per te - sei tu che devi prendere una decisione: rimuovendo il 3o commit, o mantieni le modifiche introdotte da esso (qui, la linea cccc) - oppure no. Se non lo fai, rimuovi semplicemente le righe extra - incluso il cccc- folder/file.txtusando un editor di testo, quindi è così:

aaaa
bbbb
dddd

... e poi salva folder/file.txt. Ora puoi emettere i seguenti comandi nella myrepo_gitdirectory:

$ nano folder/file.txt  # text editor - edit, save
$ git rebase --continue
folder/file.txt: needs merge
You must edit all merge conflicts and then
mark them as resolved using git add

Ah - così al fine di boa che abbiamo risolto il conflitto, noi dobbiamo git add la folder/file.txt, prima di fare git rebase --continue:

$ git add folder/file.txt
$ git rebase --continue

Qui si apre di nuovo un editor di testo che mostra la riga 4th git commit: qui abbiamo la possibilità di modificare il messaggio di commit (che in questo caso potrebbe essere significativamente modificato 4th (and removed 3rd) commito simile). Diciamo che non vuoi - quindi basta uscire dall'editor di testo senza salvare; una volta fatto, otterrai:

$ git rebase --continue
[detached HEAD b8275fc] 4th git commit
 1 file changed, 1 insertion(+)
Successfully rebased and updated refs/heads/master.

A questo punto, ora hai una storia come questa (che potresti anche esaminare con voce gitk .o altri strumenti) dei contenuti di folder/file.txt(con, apparentemente, timestamp invariati degli commit originali):

1st git commit  |  +aaaa
----------------------------------------------
2nd git commit  |   aaaa
                |  +bbbb
----------------------------------------------
4th git commit  |   aaaa
                |   bbbb
                |  +dddd
----------------------------------------------
5th git commit  |   aaaa
                |   bbbb
                |   dddd
                |  +eeee

E se in precedenza, avessimo deciso di mantenere la linea cccc(i contenuti del terzo commit git che abbiamo rimosso), avremmo avuto:

1st git commit  |  +aaaa
----------------------------------------------
2nd git commit  |   aaaa
                |  +bbbb
----------------------------------------------
4th git commit  |   aaaa
                |   bbbb
                |  +cccc
                |  +dddd
----------------------------------------------
5th git commit  |   aaaa
                |   bbbb
                |   cccc
                |   dddd
                |  +eeee

Bene, questo era il tipo di lettura che speravo di aver trovato, per iniziare a capire come git rebasefunziona in termini di cancellazione di commit / revisioni; quindi spero che possa aiutare anche gli altri ...


Che inestimabile passaggio - l'esatto caso d'uso con cui stavo lottando, questo mi ha aiutato immensamente.
Pfabri,

2

Quindi sembra che il cattivo commit sia stato incorporato in un commit di merge ad un certo punto. Il tuo commit di merge è stato ancora rimosso? Se sì, allora ti consigliamo di utilizzare git revert; dovrai stringere i denti e superare i conflitti. In caso contrario, è possibile ipotizzare un rebase o un ripristino, ma è possibile farlo prima del commit della fusione, quindi ripetere la fusione.

Non c'è molto aiuto che possiamo darti per il primo caso, davvero. Dopo aver provato il ripristino e aver scoperto che quello automatico non è riuscito, è necessario esaminare i conflitti e risolverli in modo appropriato. Questo è esattamente lo stesso processo di correzione dei conflitti di unione; puoi usare git statusper vedere dove si trovano i conflitti, modificare i file non uniti, trovare gli hunk in conflitto, capire come risolverli, aggiungere i file in conflitto e infine eseguire il commit. Se usi git commitda solo (no -m <message>), il messaggio che appare nel tuo editor dovrebbe essere il messaggio modello creato dagit revert ; puoi aggiungere una nota su come hai risolto i conflitti, quindi salvare ed uscire per eseguire il commit.

Nel secondo caso, risolvendo il problema prima dell'unione, ci sono due sottotitoli, a seconda che tu abbia fatto più lavoro dall'unione. In caso contrario, puoi semplicemente git reset --hard HEAD^annullare l'unione, eseguire il ripristino, quindi ripetere l'unione. Ma suppongo tu l'abbia fatto. Quindi, finirai per fare qualcosa del genere:

  • creare un ramo temporaneo appena prima dell'unione e verificarlo
  • eseguire il ripristino (o utilizzare git rebase -i <something before the bad commit> <temporary branch>per rimuovere il commit errato)
  • ripetere l'unione
  • rifare il lavoro successivo su: git rebase --onto <temporary branch> <old merge commit> <real branch>
  • rimuovere il ramo temporaneo

1

Quindi hai fatto un po 'di lavoro e lo hai spinto, chiamiamoli commette A e B. Anche il tuo collega ha fatto un po' di lavoro, commette C e D. Hai unito il lavoro dei tuoi colleghi al tuo (unisci l'impegno E), quindi hai continuato a lavorare, impegnato, anche (commetti F), e ho scoperto che il tuo collega ha cambiato alcune cose che non avrebbe dovuto avere.

Quindi la cronologia del commit è simile alla seguente:

A -- B -- C -- D -- D' -- E -- F

Vuoi davvero sbarazzarti di C, D e D '. Dato che dici di aver unito i tuoi colleghi di lavoro con i tuoi, questi si impegnano già "là fuori", quindi rimuovere i commit usando ad esempio git rebase è un no-no. Credimi, ci ho provato.

Ora vedo due vie d'uscita:

  • se non hai ancora inviato E e F al tuo collega o ad altri (in genere il tuo server "origine"), potresti comunque rimuoverli dalla cronologia per il momento. Questo è il tuo lavoro che vuoi salvare. Questo può essere fatto con a

    git reset D'
    

    (sostituisci D 'con l'hash di commit effettivo che puoi ottenere da a git log

    A questo punto, i commit E e F sono spariti e le modifiche sono di nuovo senza modifiche nell'area di lavoro locale. A questo punto li sposterei su un ramo o li trasformerei in una patch e li conserverei per dopo. Ora ripristina il lavoro del tuo collega, automaticamente con git reverto manualmente. Al termine, ripeti il ​​tuo lavoro per di più. Potresti avere conflitti di unione, ma almeno saranno nel codice che hai scritto, anziché nel codice del tuo collega.

  • Se hai già spinto il lavoro che hai fatto dopo il commit del tuo collega, puoi ancora provare a ottenere una "patch inversa" manualmente o utilizzando git revert, ma poiché il tuo lavoro è "sulla strada", quindi per dirlo probabilmente otterrai conflitti più uniti e più confusi. Sembra che sia quello in cui sei finito ...


0

git revert --strategy risolve se commit è un'unione: usa git revert --strategy decide -m 1

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.