Come si rimuove una revisione specifica nella cronologia di Git?


213

Supponiamo che la tua cronologia git sia simile a questa:

1 2 3 4 5

1–5 sono revisioni separate. Devi rimuovere 3 mantenendo ancora 1, 2, 4 e 5. Come si può fare?

Esiste un metodo efficiente quando ci sono centinaia di revisioni dopo quella da eliminare?


Questa domanda è mal definita. Non dice esplicitamente che l'autore vuole 1-2- (3 + 4) -5 o 1-2-4-5
RandyTek

14
Bene, 8 anni dopo, non posso dirti esattamente quale problema stavo cercando di risolvere. Ma in git ci sono sempre molti modi per fare qualcosa, e ci sono molte risposte che sono piaciute a varie persone, quindi immagino che l'ambiguità non stia causando troppe difficoltà a molte persone
1800 INFORMAZIONI

1
git rebase --onto 2 3 HEADpraticamente significa rifare su 2, con commit tra 3 e HEAD (HEAD è opzionale, o 5 in questo caso)
neaumusic

Risposte:


76

Per combinare le revisioni 3 e 4 in un'unica revisione, puoi usare git rebase. Se si desidera rimuovere le modifiche nella revisione 3, è necessario utilizzare il comando modifica in modalità rebase interattiva. Se vuoi combinare le modifiche in un'unica revisione, usa squash.

Ho usato con successo questa tecnica di squash, ma non ho mai avuto bisogno di rimuovere una revisione prima. La documentazione di git-rebase sotto "Splitting commit" dovrebbe sperare di darti abbastanza idea per capirlo. (O qualcun altro potrebbe sapere).

Dalla documentazione git :

Inizia con il commit più vecchio che vuoi conservare così com'è:

git rebase -i <after-this-commit>

Un editor verrà attivato con tutti i commit nel tuo ramo corrente (ignorando i commit di unione), che vengono dopo il commit dato. Puoi riordinare gli commit in questo elenco in base al contenuto del tuo cuore e puoi rimuoverli. L'elenco è più o meno così:

pick deadbee L'online di questo commit
pick fa1afe1 L'online del prossimo commit
...

Le descrizioni on-line sono puramente per il tuo piacere; git-rebase non li guarderà ma i nomi di commit ("deadbee" e "fa1afe1" in questo esempio), quindi non cancellare o modificare i nomi.

Sostituendo il comando "pick" con il comando "edit", puoi dire a git-rebase di fermarsi dopo aver applicato quel commit, in modo da poter modificare i file e / o il messaggio di commit, modificare il commit e continuare a riformulare.

Se si desidera piegare due o più commit in uno, sostituire il comando "pick" con "squash" per il secondo e successivo commit. Se i commit hanno autori diversi, attribuirà il commit schiacciato all'autore del primo commit.


42
-1 La domanda è ben definita ma questa risposta non è così chiara. L'autore non dice quale sia la soluzione esatta.
Aleksandr Levchuk,

1
Questo è un indizio sbagliato. La sezione IMPEGNI DI SPLITTING non è quella corretta. Vuoi leggere molto più in alto nel manuale - vedi la risposta di @Rares Vernica.
Aleksandr Levchuk,

1
@AleksandrLevchuk La domanda non è ben definita: non è chiaro dal modo in cui viene dichiarata la domanda se il changeset in 3 debba essere mantenuto o scartato. Concordo sul fatto che se le modifiche devono essere scartate, le altre risposte offrono un approccio più semplice. Se le modifiche dovessero essere mantenute, tuttavia, si tratterebbe di un'operazione puramente cosmetica. Entrambi gli approcci riscrivono la storia in modi che sono pericolosi se altri potrebbero aver basato il lavoro sulla storia imperfetta; in tal caso, la pulizia cosmetica non dovrebbe essere eseguita e la cancellazione del cambiamento sarebbe meglio tramite git revert.
Theodore Murdock,

125

Per questo commento (e ho verificato che questo sia vero), la risposta di Rado è molto vicina ma lascia git in uno stato di testa distaccato. Invece, rimuovilo HEADe usalo per rimuovere <commit-id>dal ramo in cui ti trovi:

git rebase --onto <commit-id>^ <commit-id>

1
Se potessi dirmi come rimuovere anche quel commit-id dalla cronologia basato solo su commit-id, saresti il ​​mio eroe.
kayleeFrye_onDeck

Potete per favore includere spiegazioni su cosa fa il comando magico nella risposta? Cioè che cosa indica ogni parametro?
alexandroid,

è fantastico ma cosa succede se desidero rimuovere il primo commit nella cronologia? (motivo per cui sono venuto qui: P)
Sterling Camden

2
Per qualche motivo, non succede nulla quando eseguo questo. Tuttavia, cambiando ^per ~1farlo funzionare.
Antimonio

Molto tardi, ma aggiungerò che se si incontrano conflitti di unione, interrompere il rebase e quindi eseguirlo nuovamente --strategy-option theirsalla fine.
LastStar007,

122

Ecco un modo per rimuovere in modo non interattivo uno specifico <commit-id>, sapendo solo ciò <commit-id>che desideri rimuovere:

git rebase --onto <commit-id>^ <commit-id> HEAD

2
Ha funzionato anche per me. A proposito, cosa fa l'operatore ^? Significa il prossimo commit dopo quello specificato?
hopia,

3
@hopia indica il (primo) genitore del commit specificato. Vedi "revisioni di aiuto git"
Emil Styrke,

12
Vedi la raccomandazione di @ kareem nella sua risposta per omettere in HEADmodo da evitare una testa distaccata.
mklement0,

1
Molto più facile che dover capire quanti si impegnano nuovamente a cercare.
Dana Woodman,

1
è fantastico ma cosa succede se desidero rimuovere il primo commit nella cronologia? (motivo per cui sono venuto qui: P)
Sterling Camden

76

Come notato prima, git-rebase (1) è tuo amico. Supponendo che i commit siano nel tuo masterramo, faresti:

git rebase --onto master~3 master~2 master

Prima:

1---2---3---4---5  master

Dopo:

1---2---4'---5' master

Da git-rebase (1):

Una serie di commit potrebbe anche essere rimossa con rebase. Se abbiamo la seguente situazione:

E---F---G---H---I---J  topicA

quindi il comando

git rebase --onto topicA~5 topicA~3 topicA

comporterebbe la rimozione di commit F e G:

E---H'---I'---J'  topicA

Ciò è utile se F e G erano in qualche modo difettosi o non facevano parte dell'argomento A. Nota che l'argomento --onto e il parametro possono essere qualsiasi commit-ish valido.


3
non dovrebbe essere --onto master~3 master~1?
Matthias,

3
Se vuoi semplicemente cancellare l'ultimo commit, è --onto master ~ 1 master
MikeHoss

Vorrei portare questo sol. ma ottengo l' MERGE CONFLICTerrore. Ho usato lo scenario menzionato in stackoverflow.com/questions/2938301/remove-specific-commit e non sono riuscito a rimuovere il secondo commit in quell'esempio.
Maan81,

22

Se tutto ciò che vuoi fare è rimuovere le modifiche apportate nella revisione 3, potresti voler usare git revert.

Git Revert crea semplicemente una nuova revisione con modifiche che annullano tutte le modifiche nella revisione che stai ripristinando.

Ciò significa che conservi le informazioni sia sul commit indesiderato sia sul commit che rimuove tali modifiche.

Questo è probabilmente molto più amichevole se è possibile che qualcuno abbia estratto dal tuo repository nel frattempo, dal momento che il ripristino è fondamentalmente solo un commit standard.


4
Sfortunatamente non è una buona soluzione per me perché qualcuno ha commesso accidentalmente 100 MB di schifezze nel repository, aumentando le dimensioni e rendendo lenta l'interfaccia web.
Stephen Smith,

18

Tutte le risposte finora non affrontano il problema finale:

Esiste un metodo efficiente quando ci sono centinaia di revisioni dopo quella da eliminare?

Seguono i passaggi, ma per riferimento, supponiamo che la seguente cronologia:

[master] -> [hundreds-of-commits-including-merges] -> [C] -> [R] -> [B]

C : commit appena dopo il commit da rimuovere (clean)

R : Il commit da rimuovere

B : commit appena precedente il commit da rimuovere (base)

A causa del vincolo "centinaia di revisioni", presumo le seguenti condizioni preliminari:

  1. c'è un impegno imbarazzante che vorresti non esistesse mai
  2. ci sono ZERO commit successivi che dipendono effettivamente da quel commit imbarazzante (zero conflitti al ripristino)
  3. non ti interessa che verrai elencato come "Committer" delle centinaia di commit intermedi ("Autore" verrà preservato)
  4. non hai mai condiviso il repository
    • o hai effettivamente abbastanza influenza su tutte le persone che hanno mai clonato la storia con quella commessa in essa per convincerli ad usare la tua nuova storia
    • e non si cura circa riscrivere la storia

Questo è un insieme piuttosto restrittivo di vincoli, ma c'è una risposta interessante che funziona davvero in questo caso d'angolo.

Ecco i passaggi:

  1. git branch base B
  2. git branch remove-me R
  3. git branch save
  4. git rebase --preserve-merges --onto base remove-me

Se non ci sono veramente conflitti, questo dovrebbe procedere senza ulteriori interruzioni. Se ci sono conflitti, puoi risolverli e rebase --continuedecidere di convivere con l'imbarazzo e rebase --abort.

Ora dovresti essere attivo masterche non ha più commesso R in esso. Il saveramo indica dove eri prima, nel caso in cui desideri riconciliarti.

Il modo in cui desideri organizzare il trasferimento di tutti gli altri nella tua nuova cronologia dipende da te. Avrete bisogno di essere a conoscenza di stash, reset --harde cherry-pick. E si può eliminare i base, remove-mee savefiliali


come posso piacermi questo 150000 volte?
Gena Moroz,

Ho lavorato per me per rimuovere 3 commit in sequenza dalla metà della storia di un ramo in cui qualcuno ha commesso un mucchio di file da 150 MB.
Marcos,

3

Sono anche atterrato in una situazione simile. Utilizzare rebase interattivo utilizzando il comando seguente e durante la selezione, rilasciare il 3o commit.

git rebase -i remote/branch

2

Quindi, ecco lo scenario che ho affrontato e come l'ho risolto.

[branch-a]

[Hundreds of commits] -> [R] -> [I]

ecco Ril commit che avevo bisogno di essere rimosso, ed Iè un singolo commit che viene dopoR

Ho fatto un commit di ripristino e li ho schiacciati insieme

git revert [commit id of R]
git rebase -i HEAD~3

Durante lo squase rebase interattivo gli ultimi 2 commit.


0

Le risposte di rado e kareem non fanno nulla per me (appare solo il messaggio "Il ramo attuale è aggiornato."). Forse questo accade perché il simbolo '^' non funziona nella console di Windows. Tuttavia, secondo questo commento, la sostituzione di '^' con '~ 1' risolve il problema.

git rebase --onto <commit-id>^ <commit-id>
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.