Come posso dividere un commit Git sepolto nella storia?


292

Ho ripulito la mia storia e voglio apportare alcune modifiche. Il problema è che ho un commit con due modifiche non correlate e questo commit è circondato da alcune altre modifiche nella mia cronologia locale (senza push).

Voglio suddividere questo commit prima di inviarlo, ma la maggior parte delle guide che vedo hanno a che fare con la suddivisione del commit più recente o le modifiche locali non confermate. È possibile fare questo per un commit che è sepolto un po 'nella storia, senza dover "rifare" i miei commit da allora?


Risposte:


450

C'è una guida per dividere i commit nella manpage rebase . Il breve sommario è:

  • Eseguire un rebase interattivo incluso il commit target (ad es. git rebase -i <commit-to-split>^ branch) E contrassegnarlo per essere modificato.

  • Quando il rebase raggiunge quel commit, utilizzare git reset HEAD^per ripristinare prima del commit, ma mantenere intatto l'albero di lavoro.

  • Aggiungi in modo incrementale le modifiche e le commette, effettuando tutte le commit che desideri. add -ppuò essere utile per aggiungere solo alcune delle modifiche in un determinato file. Utilizzare commit -c ORIG_HEADse si desidera riutilizzare il messaggio di commit originale per un determinato commit.

  • Se vuoi testare ciò che stai commettendo (buona idea!) Usa git stashper nascondere la parte che non hai commesso (o stash --keep-indexprima ancora che tu lo commetta), prova, quindi git stash popper riportare il resto all'albero di lavoro. Continuare a eseguire commit fino a quando non si eseguono tutte le modifiche, ad esempio un albero di lavoro pulito.

  • Esegui git rebase --continueper procedere applicando i commit dopo il commit ora diviso.


17
... ma non farlo se hai già spinto la cronologia dal momento in cui il commit si è diviso.
Wilhelmtell,

29
@wilhelmtell: ho omesso la mia consueta placca di caldaia "potenzialmente pericolosa; vedi" recupero dal rebase a monte "perché l'OP ha dichiarato esplicitamente di non aver spinto questa storia.
Cascabel,

2
e hai fatto una lettura perfetta. Stavo cercando di evitare il 'boilerplate' quando ho specificato che non era ancora una storia condivisa :) In ogni caso, ho avuto successo con il tuo suggerimento. È un grande dolore fare queste cose dopo il fatto però. Ho imparato una lezione qui, ed è per assicurarmi che gli impegni siano inseriti correttamente per cominciare!
Ben

2
Il primo passo può essere meglio indicato come git rebase -i <sha1_of_the_commit_to_split>^ branch. Ed git guiè uno strumento utile per l'attività di suddivisione, che può essere utilizzato per aggiungere diverse parti di un file in commit diversi.
Qiang Xu,

3
@QiangXu: il primo è un suggerimento ragionevole. Il secondo è esattamente il motivo per cui ho suggerito git add -p, che può fare più di quanto git guipossa fare in questo dipartimento (in particolare la modifica di hunk, la messa in scena di tutto a partire dall'hunk corrente e la ricerca di hunk tramite regex).
Cascabel,

3

Ecco come farlo con Magit .

Di 'commetti ed417ae è quello che vuoi cambiare; contiene due modifiche non correlate ed è sepolto sotto uno o più commit. Premi llper mostrare il registro e vai a ed417ae:

registro iniziale

Quindi premi rper aprire il popup rebase

popup rebase

e m per modificare il commit al punto.

Nota come @ora c'è il commit che vuoi dividere - questo significa che HEAD è ora a quel commit:

modifica di un commit

Vogliamo spostare HEAD sul genitore, quindi vai al genitore (47e18b3) e premi x( magit-reset-quickly, associato a ose stai usando evil-magit) e inserisci per dire "sì, intendevo impegnare al punto". Il registro ora dovrebbe apparire come:

accedere dopo il ripristino

Ora, premi qper andare al normale stato di Magit, quindi usa il normale ucomando di unstage per disimballare ciò che non accade nel primo commit, commetti cil resto come al solito, quindi prova se cometti ciò che accade nel secondo commit, e quando hai finito: premi rper aprire il popup rebase

popup rebase

e un altro rper continuare, e il gioco è fatto! llora mostra:

registro completo


1

Per dividere un commit <commit>e aggiungere il nuovo commit prima di questo e salvare la data dell'autore di <commit>, i passaggi sono i seguenti:

  1. Modifica il commit prima <commit>

    git rebase -i <commit>^^
    

    NB: forse sarà anche necessario modificarlo <commit>.

  2. Cherry seleziona <commit>nell'indice

    git cherry-pick -n <commit>
    
  3. Ripristina in modo interattivo le modifiche non necessarie dall'indice e ripristina l'albero di lavoro

    git reset -p && git checkout-index -f -a
    

    In alternativa, puoi semplicemente nascondere interattivamente le modifiche non necessarie: git stash push -p -m "tmp other changes"

  4. Apporta altre modifiche (se presenti) e crea il nuovo commit

    git commit -m "upd something" .
    

    Facoltativamente, ripetere le voci 2-4 per aggiungere altri commit intermedi.

  5. Continua a riformare

    git rebase --continue
    

0

Esiste una versione più veloce se desideri estrarre il contenuto da un solo file. È più veloce perché il rebase interattivo non è più effettivamente interattivo (ed è ovviamente ancora più veloce se vuoi estrarre dall'ultimo commit, quindi non c'è bisogno di rebase affatto)

  1. Usa il tuo editor ed elimina le righe da cui vuoi estrarre the_file. Vicinothe_file . Questa è l'unica edizione di cui hai bisogno, tutto il resto sono solo comandi git.
  2. Metti in scena quella cancellazione nell'indice:

    git  add  the_file
    
  3. Ripristina le righe appena eliminate nel file senza influire sull'indice !

    git show HEAD:./the_file > the_file
    
  4. "SHA1" è il commit da cui vuoi estrarre le righe:

    git commit -m 'fixup! SHA1' 
    
  5. Crea il secondo, nuovissimo commit con il contenuto da estrarre ripristinato dal passaggio 3:

    git commit -m 'second and new commit' the_file 
    
  6. Non modificare, non interrompere / continuare - accetta semplicemente tutto:

    git rebase --autosquash -i SHA1~1
    

Naturalmente ancora più veloce quando il commit da cui estrarre è l'ultimo commit:

4. git commit -C HEAD --amend
5. git commit -m 'second and new commit' thefile
6. no rebase, nothing

Se lo usi, i magitpassaggi 4, 5 e 6 sono una singola azione: commit, correzione immediata


-2

Se non hai ancora spinto, basta usare git rebase. Ancora meglio, usa git rebase -iper spostare i commit in modo interattivo. Puoi spostare il commit offensivo in primo piano, quindi dividerlo a tuo piacimento e spostare le patch indietro (se necessario).


14
Non è necessario spostarlo da nessuna parte. Dividilo dove si trova.
Cascabel,

1
Sfortunatamente, questo non funziona per me perché parte della storia dopo il commit dipende da essa, quindi sono un po 'limitato. Tuttavia, questa sarebbe stata la mia prima scelta.
Ben

@Ben: va bene - i commit in seguito non dovranno cambiare affatto (supponendo che tu mantenga tutte le modifiche, invece di buttarne via alcune). Maggiori informazioni qui - stackoverflow.com/questions/1440050/…
Ether
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.