Come ripristinare un commit di unione che è già stato inviato al ramo remoto?


962

git revert <commit_hash>da solo non funzionerà. -mdeve essere specificato e sono piuttosto confuso al riguardo.

Qualcuno l'ha già sperimentato?


3
Dai un'occhiata alla risposta a questa domanda: stackoverflow.com/questions/2318777/…
eugen


Il link qui è il miglior esempio che illustra il ripristino del commit unito: christianengvall.se/undo-pushed-merge-git
SK Venkat,

Questo è un esempio di dove il design di gitnon corrisponde al git-flowflusso di lavoro -ish che tutti usano. Se hai developeffettuato il check-out, ovviamente desideri ripristinare il ramo della funzionalità 2-commit che ha introdotto un bug e non il ramo di sviluppo condiviso da anni. Sembra ridicolo doverlo scegliere -m 1.
pkamb,

2
Solo un altro suggerimento che non mi è mai venuto in mente prima: se uno degli elenchi di commit delle filiali è piccolo, potresti sentirti più a tuo agio nel ripristinare i singoli commit anziché un intero ramo di commit.
Sridhar Sarnobat,

Risposte:


1153

L' -mopzione specifica il numero principale . Questo perché un commit di unione ha più di un genitore e Git non conosce automaticamente quale genitore era la linea principale e quale genitore era il ramo che si desidera annullare l'unione.

Quando visualizzi un commit di unione nell'output di git log, vedrai i suoi genitori elencati sulla riga che inizia con Merge:

commit 8f937c683929b08379097828c8a04350b9b8e183
Merge: 8989ee0 7c6b236
Author: Ben James <ben@example.com>
Date:   Wed Aug 17 22:49:41 2011 +0100

Merge branch 'gh-pages'

Conflicts:
    README

In questa situazione, git revert 8f937c6 -m 1ti porterà l'albero così com'era 8989ee0e git revert -m 2ripristinerà l'albero com'era 7c6b236.

Per comprendere meglio gli ID principali, è possibile eseguire:

git log 8989ee0 

e

git log 7c6b236

127
da due numeri 8989ee0, 7c6b236quale scegliere. Come avrei capito?
Arup Rakshit,

12
Dopo il ripristino, non credo che uno sarà in grado di correggere facilmente il codice nel ramo di origine e unire nuovamente? kernel.org/pub/software/scm/git/docs/howto/…
IsmailS

10
Mentre cercavo una spiegazione migliore su Google, ho trovato questo articolo che pensavo avesse fatto un ottimo lavoro nel leggere i dettagli. Ho scoperto dopo aver letto che quello che stavo davvero cercando era il comando RESET, seguito da una spinta forzata. Forse aiuterà qualcun altro. atlassian.com/git/tutorials/…
Funktr0n

46
@ArupRakshit se corri git log 8989ee0e git log 7c6b236dovresti conoscere la risposta.
BMW,

4
git log - si fonde per vedere tutte le fusioni e git log --no-si fonde per vedere la cronologia senza fusioni. La fusione di un ramo porta la storia del ramo unito nel bersaglio e rende difficile distinguerlo usando il semplice registro git
Alex Punnen,

370

Ecco un esempio completo nella speranza che aiuti qualcuno:

git revert -m 1 <commit-hash> 
git push -u origin master

Dov'è <commit-hash>l'hash di commit della fusione che desideri ripristinare e, come indicato nella spiegazione di questa risposta , -m 1indica che desideri tornare all'albero del primo genitore prima della fusione.

La git revert ...linea essenzialmente commette le modifiche mentre la seconda riga rende pubbliche le modifiche spingendole al ramo remoto.


21
Credevo che il git revertcomando avesse già eseguito il commit dell'oggetto commit creato. Perché ciò non accada, dovrai inserire la --no-commitbandiera
Delfic,

2
Come accennato da @Delfic, il commit è già gestito dalla prima riga (avevo bisogno di un: wq per convalidarlo), quindi la seconda riga non è necessaria.
eka808,

1
questo è confusionario. ci sono solo 2 righe e nessun commit git .. qualcuno può modificare.
Jay Random,

176

Ben ti ha detto come ripristinare un commit di unione, ma è molto importante che tu lo capisca

"... dichiara che non vorrai mai le modifiche dell'albero apportate dalla fusione. Di conseguenza, le fusioni successive porteranno solo alle modifiche dell'albero introdotte dai commit che non sono antenati della fusione precedentemente annullata. Ciò può essere o meno quello che vuoi. " (pagina man di git-merge) .

Un messaggio di articolo / mailing list collegato dalla pagina man descrive i meccanismi e le considerazioni che sono coinvolti. Assicurati solo di capire che se ripristini il commit di unione, non puoi semplicemente unire nuovamente il ramo in seguito e aspettarti che le stesse modifiche ritornino.


81
Ma puoi ripristinare il ripristino per ripristinarlo se necessario.
dalore,

5
Grazie. Molto utile sapere come caso d'uso per annullare un'unione - a causa di un bug, diciamo - e quindi ricongiungere l'intero ramo una volta che il bug è stato risolto, è comune.
Componente 10

3
Se sei come me e in seguito volevi l'unione, puoi ripristinare il ripristino o scegliere la modifica che hai ripristinato.
UnitasBrooks,

Nella mia situazione, ho colto il dovere di "ripristinare il ripristino" per ottenere il mio problema con le modifiche. La raccolta delle ciliegie potrebbe essere più ordinata? Ci proverò la prossima volta ...
Steven Anderson il

79

È possibile seguire questi passaggi per ripristinare i commit errati o ripristinare il ramo remoto per correggere lo stato / HEAD.

  1. controlla la filiale remota al repository locale.
    git checkout development
  2. copia l'hash di commit (cioè l'id del commit immediatamente prima del commit errato) dal registro di git git log -n5

    produzione:

    commit 7cd42475d6f95f5896b6f02e902efab0b70e8038 "Unisci ramo 'errato-commit' in 'sviluppo'"
    commit f9a734f8f44b0b37ccea769b9a2fd774c0f0c012 "questo è un commit errato"
    commit 3779ab50e72908da92dc

  3. reimpostare il ramo sull'hash di commit copiato nel passaggio precedente
    git reset <commit-hash> (i.e. 3779ab50e72908da92d2cfcd72256d7a09f446ba)

  4. eseguire il git statusper mostrare tutte le modifiche che facevano parte del commit errato.
  5. corri semplicemente git reset --hardper ripristinare tutte quelle modifiche.
  6. spingere forzatamente la filiale locale in remoto e notare che la cronologia di commit è pulita com'era prima che fosse inquinata.
    git push -f origin development

4
cosa succederebbe se nel frattempo 20 sviluppatori tirassero l'ultimo dev merge?
Ewoks,

2
Non forzerei il push di un ramo di sviluppo quando ci sono 20 sviluppatori nel team che usano quel ramo. :) In tal caso, è consigliabile eseguire solo un commit di ripristino.
ssasi,

4
Questa è un'ottima soluzione quando lavori da solo o sei sicuro che nessun altro sviluppatore abbia fatto il commit che hai rovinato
Kyle B


30

Per mantenere pulito il registro in quanto non è successo nulla (con alcuni aspetti negativi di questo approccio (a causa di push -f)):

git checkout <branch>
git reset --hard <commit-hash-before-merge>
git push -f origin HEAD:<remote-branch>

'commit-hash-before-merge' viene dal registro (log git) dopo l'unione.


Suggerimento: se lo stai facendo nella tua azienda, potresti non avere l'autorizzazione.
eneski,

3
non fare mai un push -frepository condiviso
Baptiste Mille-Mathias

17

A volte il modo più efficace per eseguire il rollback è fare un passo indietro e sostituirlo.

git log

Usa il secondo hash di commit (hash completo, quello a cui desideri ripristinare, prima dell'errore elencato) e quindi rebranch da lì.

git checkout -b newbranch <HASH>

Quindi elimina il vecchio ramo, copia il newbranch al suo posto e riavvia da lì.

git branch -D oldbranch
git checkout -b oldbranch newbranch

Se è stato trasmesso, quindi eliminare il vecchio ramo da tutti i repository, spingere il ramo rifatto nella posizione più centrale e riportarlo a tutti.


4
L'avvertimento sulla trasmissione dovrebbe essere davvero più esplicito su quanto sia orribile un'idea. Questo danneggerà la versione di tutti di quel ramo ed è davvero utile solo se stai lavorando con un repository remoto (github / bitbucket) a cui solo tu hai accesso.
RobbyD,

Non male come spingere i file di configurazione modificati nel flusso verso la produzione. Non corromperà, è solo un rebranching di un commit precedente, quindi è un modo per spostare il puntatore sui rami a una versione precedente. Spero che abbia un impatto solo sul repository locale
ppostma1,

5

Se vuoi ripristinare un mergecommit, ecco cosa devi fare.

  1. Innanzitutto, controlla git logper trovare l'ID del tuo commit di unione. Troverai anche più ID padre associati all'unione (vedi immagine sotto).

inserisci qui la descrizione dell'immagine

Annotare l'ID commit di unione mostrato in giallo. Gli ID padre sono quelli scritti nella riga successiva come Merge: parent1 parent2. Adesso...

Storia breve:

  1. Passa al ramo su cui è stata effettuata l'unione. Quindi basta fare git revert <merge commit id> -m 1ciò che aprirà una viconsole per l'immissione del messaggio di commit. Scrivi, salva, esci, fatto!

Lunga storia:

  1. Passa al ramo su cui è stata effettuata l'unione. Nel mio caso, è il testramo e sto cercando di rimuovere il feature/analytics-v3ramo da esso.

  2. git revertè il comando che ripristina qualsiasi commit. Ma c'è un brutto trucco quando si ripristina un mergecommit. Devi inserire la -mbandiera, altrimenti fallirà. Da qui in poi, devi decidere se vuoi ripristinare il tuo ramo e farlo sembrare esattamente come era su parent1o parent2tramite:

git revert <merge commit id> -m 1(ripristina a parent2)

git revert <merge commit id> -m 2(ripristina a parent1)

Puoi fare il log di questi genitori per capire in che direzione vuoi andare e questa è la radice di tutta la confusione.


5

Tutte le risposte hanno già coperto la maggior parte delle cose, ma aggiungerò i miei 5 centesimi. In breve, ripristinare un commit di unione è abbastanza semplice:

git revert -m 1 <commit-hash>

Se si dispone dell'autorizzazione, è possibile inviarlo direttamente al ramo "master", altrimenti è sufficiente inviarlo al ramo "ripristina" e creare una richiesta pull.

Puoi trovare maggiori informazioni utili su questo argomento qui: https://itcodehub.blogspot.com/2019/06/how-to-revert-merge-in-git.html


1

Ho scoperto che la creazione di una patch inversa tra due endpoint noti e l'applicazione di quella patch avrebbe funzionato. Ciò presume che tu abbia creato snapshot (tag) al di fuori del tuo ramo principale o anche un backup del tuo ramo principale, ad esempio master_bk_01012017.

Supponiamo che il ramo di codice che hai unito in master sia mycodebranch.

  1. Checkout master.
  2. Crea una patch inversa binaria completa tra master e backup. git diff --binary master..master_bk_01012017 > ~/myrevert.patch
  3. Controlla la tua patch git apply --check myrevert.patch
  4. Applica patch con firma git am --signoff < myrevert.patch
  5. Se sarà necessario inserire nuovamente questo codice una volta che è stato risolto, sarà necessario ramificare il master ripristinato e verificare il ramo di correzione git branch mycodebranch_fix git checkout mycodebranch_fix
  6. Qui è necessario trovare il tasto SHA per il ripristino e ripristinare il ripristino git revert [SHA]
  7. Ora puoi utilizzare mycodebranch_fix per risolvere i problemi, eseguire il commit e ricollegarti al master una volta terminato.

1

La risposta correttamente contrassegnata ha funzionato per me, ma ho dovuto dedicare un po 'di tempo per determinare cosa stava succedendo .. Quindi ho deciso di aggiungere una risposta con semplici passaggi per casi come il mio ..

Diciamo che abbiamo i rami A e B .. Hai unito il ramo A al ramo B e hai spinto il ramo B su se stesso, quindi ora l'unione fa parte di esso .. Ma vuoi tornare all'ultimo commit prima dell'unione .. Cosa fare tu fai?

  1. Vai alla tua cartella git root (di solito la cartella del progetto) e usa git log
  2. Vedrai la cronologia dei recenti commit - i commit hanno proprietà commit / author / date mentre le fusioni hanno anche una proprietà merge - quindi le vedi in questo modo:

    commit: <commitHash> Merge: <parentHashA> <parentHashB> Author: <author> Date: <date>

  3. Usa git log <parentHashA>e git log <parentHashB>- vedrai le cronologie dei commit di quei rami principali - i primi commit nella lista sono quelli più recenti

  4. Prendi il <commitHash>commit che vuoi, vai nella cartella git root e usa git checkout -b <newBranchName> <commitHash>- che creerà un nuovo ramo a partire dall'ultimo commit che hai scelto prima dell'unione .. Voila, pronto!


0

Ho anche affrontato questo problema su un PR che è stato unito al ramo principale di un repository GitHub.

Dal momento che volevo solo modificare alcuni file modificati ma non tutte le modifiche apportate dal PR, ho dovuto fare amendi merge commitconti con git commit --am.

passi:

  1. Vai al ramo in cui vuoi cambiare / ripristinare alcuni file modificati
  2. Apporta le modifiche desiderate in base ai file modificati
  3. corri git add *ogit add <file>
  4. esegui git commit --ame convalida
  5. correre git push -f

Perché è interessante:

  • Mantiene invariato l'autore del PR
  • Non rompe l'albero git
  • Verrai contrassegnato come committer (unisci autore autore rimarrà invariato)
  • Git agirà come se avessi risolto i conflitti, rimuoverà / cambierà il codice nei file modificati come se dicessi manualmente a GitHub di non unirlo così com'è

0

Ho trovato una buona spiegazione per Come ripristinare l'unione da questo link e copio incollato la spiegazione di seguito e sarebbe utile nel caso in cui il link di seguito non funzioni.

Come ripristinare una fusione errata Alan (alan@clueserver.org) ha detto:

Ho un ramo principale. Abbiamo una branca di ciò su cui alcuni sviluppatori stanno lavorando. Dicono che è pronto. Lo uniamo nel ramo principale. Si rompe qualcosa in modo da ripristinare l'unione. Apportano modifiche al codice. arrivano a un punto in cui dicono che va bene e ci uniamo di nuovo. Quando esaminato, troviamo che le modifiche al codice effettuate prima del ripristino non si trovano nel ramo principale, ma le modifiche al codice successive si trovano nel ramo principale. e ha chiesto aiuto per riprendersi da questa situazione.

La storia immediatamente dopo il "ripristino della fusione" sarebbe simile a questa:

---o---o---o---M---x---x---W
              /
      ---A---B

dove A e B sono sullo sviluppo laterale che non era così buono, M è la fusione che porta questi cambiamenti prematuri nella linea principale, x sono cambiamenti non correlati a ciò che il ramo laterale ha fatto e già fatto sulla linea principale, e W è il " ripristina l'unione M "(W non guarda M sottosopra?). IOW, "diff W ^ .. W" è simile a "diff -RM ^ .. M".

Tale "ripristino" di una fusione può essere effettuato con:

$ git revert -m 1 M Dopo che gli sviluppatori del ramo laterale hanno corretto i loro errori, la storia potrebbe apparire così:

---o---o---o---M---x---x---W---x
              /
      ---A---B-------------------C---D

dove C e D stanno riparando ciò che è stato rotto in A e B, e potresti avere già alcune altre modifiche sulla linea principale dopo W.

Se unisci il ramo laterale aggiornato (con D in punta), nessuna delle modifiche apportate in A o B sarà nel risultato, perché sono state ripristinate da W. Questo è ciò che Alan ha visto.

Linus spiega la situazione:

Il ripristino di un commit regolare annulla in modo efficace ciò che ha fatto il commit ed è abbastanza semplice. Ma il ripristino di un commit di unione annulla anche i dati modificati dal commit, ma non fa assolutamente nulla agli effetti sulla cronologia che ha avuto la fusione. Quindi la fusione continuerà a esistere, e sarà comunque vista come unire i due rami insieme, e le future fusioni vedranno quella fusione come l'ultimo stato condiviso - e il ripristino che ha ripristinato la fusione introdotto non avrà alcun effetto. Quindi un "ripristino" annulla le modifiche ai dati, ma non è cosìun "annullamento", nel senso che non annulla gli effetti di un commit sulla cronologia del repository. Quindi, se pensi di "ripristinare" come "annulla", allora ti perderai sempre questa parte dei ripristini. Sì, annulla i dati, ma no, non annulla la cronologia. In una situazione del genere, si vorrebbe innanzitutto ripristinare il ripristino precedente, il che renderebbe la cronologia simile a questa:

---o---o---o---M---x---x---W---x---Y
              /
      ---A---B-------------------C---D

dove Y è il ripristino di W. Tale "ripristino del ripristino" può essere fatto con:

$ git ripristina W Questa storia (ignorando i possibili conflitti tra ciò che W e W..Y sono cambiati) equivarrebbe a non avere W o Y nella storia:

---o---o---o---M---x---x-------x----
              /
      ---A---B-------------------C---D

e la fusione del ramo laterale non avrà conflitti derivanti da un precedente ripristino e ripristino del ripristino.

---o---o---o---M---x---x-------x-------*
              /                       /
      ---A---B-------------------C---D

Naturalmente le modifiche apportate in C e D possono ancora essere in conflitto con ciò che è stato fatto da una qualsiasi delle x, ma questo è solo un normale conflitto di unione.


-2

Come accennato da Ryan, git revertpotrebbe rendere difficile la fusione lungo la strada, quindi git revertpotrebbe non essere quello che vuoi. Ho scoperto che usare il git reset --hard <commit-hash-prior-to-merge>comando per essere più utile qui.

Dopo aver eseguito la parte di ripristino hardware, è possibile forzare la spinta al ramo remoto, ovvero git push -f <remote-name> <remote-branch-name>dove <remote-name>viene spesso chiamato origin. Da quel punto puoi ricollegarti se lo desideri.


4
Tutto ciò che comporta spinte forzate è una cattiva idea a meno che tu non sia l'unico a utilizzare il repository e tu sappia esattamente cosa stai facendo. Ripristinare con Git Revert e quindi eventualmente ripristinare il Revert con Git Revert (se è necessario riportare le cose di nuovo) è un'alternativa molto più sicura.
oyvind,
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.