Come dividere l'ultimo commit in due in Git


277

Ho due rami funzionanti, master e forum e ho appena apportato alcune modifiche nel ramo del forum , che mi piacerebbe scegliere in master . Ma sfortunatamente, il commit che voglio selezionare ciliegia contiene anche alcune modifiche che non voglio.

La soluzione sarebbe probabilmente quella di eliminare in qualche modo il commit errato e sostituirlo con due commit separati, uno con le modifiche che voglio selezionare nel master e altri che non vi appartengono.

Ci ho provato

git reset --hard HEAD^

che ha eliminato tutte le modifiche, quindi ho dovuto tornare indietro

git reset ORIG_HEAD

Quindi la mia domanda è: qual è il modo migliore per dividere l' ultimo commit in due commit separati?

Risposte:


332

Dovresti usare l'indice. Dopo aver eseguito un ripristino misto (" git reset HEAD ^"), aggiungi la prima serie di modifiche all'indice, quindi esegui il commit. Quindi commetti il ​​resto.

Puoi usare " git add " per mettere tutte le modifiche apportate in un file all'indice. Se non vuoi mettere in scena tutte le modifiche apportate in un file, solo alcune di esse, puoi usare "git add -p".

Vediamo un esempio. Supponiamo di avere un file chiamato myfile, che contiene il seguente testo:

something
something else
something again

L'ho modificato nel mio ultimo commit in modo che ora assomigli a questo:

1
something
something else
something again
2

Ora decido che voglio dividerlo in due e voglio che l'inserimento della prima riga sia nel primo commit e l'inserimento dell'ultima riga sia nel secondo commit.

Per prima cosa torno al genitore di HEAD, ma voglio mantenere le modifiche nel file system, quindi uso "git reset" senza argomenti (che farà un cosiddetto reset "misto"):

$ git reset HEAD^
myfile: locally modified
$ cat myfile
1
something
something else
something again
2

Ora uso "git add -p" per aggiungere le modifiche che voglio impegnare nell'indice (= le metto in scena). "git add -p" è uno strumento interattivo che ti chiede quali modifiche al file dovrebbe aggiungere all'indice.

$ git add -p myfile
diff --git a/myfile b/myfile
index 93db4cb..2f113ce 100644
--- a/myfile
+++ b/myfile
@@ -1,3 +1,5 @@
+1
 something
 something else
 something again
+2
Stage this hunk [y,n,a,d,/,s,e,?]? s    # split this section into two!
Split into 2 hunks.
@@ -1,3 +1,4 @@
+1
 something
 something else
 something again
Stage this hunk [y,n,a,d,/,j,J,g,e,?]? y  # yes, I want to stage this
@@ -1,3 +2,4 @@
 something
 something else
 something again
+2
Stage this hunk [y,n,a,d,/,K,g,e,?]? n   # no, I don't want to stage this

Quindi commetto questa prima modifica:

$ git commit -m "Added first line"
[master cef3d4e] Added first line
 1 files changed, 1 insertions(+), 0 deletions(-)

Ora posso eseguire il commit di tutte le altre modifiche (ovvero il numero "2" inserito nell'ultima riga):

$ git commit -am "Added last line"
[master 5e284e6] Added last line
 1 files changed, 1 insertions(+), 0 deletions(-)

Controlliamo il registro per vedere cosa ci impegniamo:

$ git log -p -n2 | cat
Commit 5e284e652f5e05a47ad8883d9f59ed9817be59d8
Author: ...
Date: ...

    Added last line

Diff --git a/myfile b/myfile
Index f9e1a67..2f113ce 100644
--- a/myfile
+++ b/myfile
@@ -2,3 +2,4 @@
 something
 something else
 something again
+2

Commit cef3d4e0298dd5d279a911440bb72d39410e7898
Author: ...
Date: ...

    Added first line

Diff --git a/myfile b/myfile
Index 93db4cb..f9e1a67 100644
--- a/myfile
+++ b/myfile
@@ -1,3 +1,4 @@
+1
 something
 something else
 something again

1
Mi sono abituato lentamente a git da Mercurial nell'ultima settimana e mezza, e c'è un comodo comando di scelta rapida git reset [--patch|-p] <commit>che puoi usare per salvarti la difficoltà di doverlo git add -pdopo aver ripristinato. Ho ragione? Usando git 1.7.9.5.
trojjer,

2
Ecco un po 'di più su questa tecnica, incluso il rebasing se si trattava di un commit precedente, oppure è necessario modificare N commit in M ​​commit: emmanuelbernard.com/blog/2014/04/14/… .
Chris Westin,

84

obiettivi:

  • Voglio dividere un commit passato ( splitme) in due.
  • Voglio mantenere il messaggio di commit .

Piano:

  1. rebase interattivo da uno precedente splitme.
  2. modifica splitme.
  3. Ripristina i file per dividerli in un secondo commit.
  4. Modifica il commit, mantenendo il messaggio, modifica se necessario.
  5. Aggiungi nuovamente i file suddivisi dal primo commit.
  6. Impegnati con un nuovo messaggio.
  7. Continua rebase.

I passaggi rebase (1 e 7) possono essere saltati se splitmeè il commit più recente.

git rebase -i splitme^
# mark splitme commit with 'e'
git reset HEAD^ -- $files
git commit --amend
git add $files
git commit -m "commit with just some files"
git rebase --continue

Se volessi che i file divisi venissero impegnati prima, rifarei di nuovo -i e cambierei l'ordine

git rebase -i splitme^
# swap order of splitme and 'just some files'

1
git reset HEAD^era il pezzo mancante del puzzle. Funziona bene -panche con . Grazie!
Marius Gedminas,

10
È importante notare l' -- $filesargomento git reset. Con i percorsi passati, git resetripristina quei file allo stato del commit referenziato ma non cambia alcun commit. Se lasci i percorsi, allora "perdi" il commit che desideri modificare nel passaggio successivo.
marcatori di duelli

2
Questo metodo ti impedisce di dover copiare e incollare nuovamente il tuo primo messaggio di commit, rispetto alla risposta accettata.
Calvin,

Inoltre: se si desidera ripristinare tutti i file, basta usare git reset HEAD^ -- .. Sorprendentemente, questo non è esattamente il comportamento di git reset HEAD^.
allidoiswin,

52

Per modificare il commit corrente in due commit, puoi fare qualcosa di simile al seguente.

O:

git reset --soft HEAD^

Questo annulla l'ultimo commit ma lascia tutto in scena. È quindi possibile disinstallare determinati file:

git reset -- file.file

Facoltativamente ripristinare parti di tali file:

git add -p file.file

Effettua un nuovo primo commit:

git commit

Lo stage e il resto delle modifiche in un secondo commit:

git commit -a

O:

Annulla e ripristina tutte le modifiche dall'ultimo commit:

git reset HEAD^

Metti in scena in modo selettivo il primo round di modifiche:

git add -p

Commettere:

git commit

Impegna il resto delle modifiche:

git commit -a

(In entrambi i passaggi, se si annulla un commit che ha aggiunto un nuovo file e si desidera aggiungerlo al secondo commit, è necessario aggiungerlo manualmente in quanto commit -asolo le modifiche ai file già tracciati vengono aggiunte ).


22

Esegui git gui, selezionare il pulsante di opzione "Modifica ultimo commit" e annullare le modifiche (Commit> Unstage From Commit o Ctrl- U) che non si desidera inserire nel primo commit. Penso che sia il modo più semplice per farlo.

Un'altra cosa che potresti fare è selezionare la modifica senza eseguire il commit ( git cherry-pick -n) e quindi manualmente o con git guiselezionare le modifiche desiderate prima di eseguire il commit.


15
git reset HEAD^

il --hard è ciò che sta uccidendo le tue modifiche.


13

Sono sorpreso che nessuno abbia suggerito git cherry-pick -n forum. Questo metterà in scena i cambiamenti dall'ultimo forumcommit ma non li commetterà - puoi quindi reseteliminare i cambiamenti che non ti servono e impegnare ciò che vuoi conservare.


3

Il metodo del doppio ripristino-squash

  1. Effettua un altro commit che rimuove le modifiche indesiderate. (Se è per file, questo è davvero facile: git checkout HEAD~1 -- files with unwanted changese git commit. In caso contrario, i file con modifiche miste possono essere messi in scena parzialmente git reset filee git add -p filecome un passaggio intermedio.) Chiamare questo il ripristino .
  2. git revert HEAD- Effettua ancora un altro commit, che aggiunge le modifiche indesiderate. Questo è il doppio ripristino
  3. Dei 2 commit che hai fatto ora, schiaccia il primo sul commit a split ( git rebase -i HEAD~3). Questo commit ora diventa libero dalle modifiche indesiderate, poiché quelle sono nel secondo commit.

Benefici

  • Conserva il messaggio di commit
  • Funziona anche se il commit da dividere non è l'ultimo. Richiede solo che le modifiche indesiderate non siano in conflitto con impegni successivi

1

Dato che stai raccogliendo ciliegie, puoi:

  1. cherry-pickcon l' --no-commitopzione aggiunta.
  2. resete utilizzare add --patch, add --edito semplicemente addper mettere in scena ciò che si desidera conservare.
  3. commit i cambiamenti in scena.
    • Per riutilizzare il messaggio di commit originale, è possibile aggiungere --reuse-message=<old-commit-ref>o --reedit-message=<old-commit-ref>opzioni al commitcomando.
  4. Elimina le modifiche non messe in scena con reset --hard.

Un altro modo, preservando o modificando il messaggio di commit originale:

  1. cherry-pick il commit originale come di consueto.
  2. Invertire le modifiche che non desideri e utilizzare addper organizzare l'inversione.
    • Questo passaggio sarebbe facile se stai rimuovendo ciò che hai aggiunto, ma un po 'complicato se aggiungi ciò che hai rimosso o invertendo una modifica.
  3. commit --amend per effettuare l'inversione del commit selezionato dalla ciliegia.
    • Riceverai di nuovo lo stesso messaggio di commit, che puoi conservare o rivedere se necessario.

0

Questa potrebbe essere un'altra soluzione mirata per i casi in cui vi è un enorme commit e una piccola quantità di file deve essere spostata in un nuovo commit. Questo funzionerà se un set di<path> file deve essere estratto dall'ultimo commit su HEAD e tutti spostati in un nuovo commit. Se sono necessari più commit, è possibile utilizzare le altre soluzioni.

Prima fai delle patch nelle aree gestite e non messe in scena che conterrebbero le modifiche per ripristinare rispettivamente il codice prima della modifica e dopo la modifica:

git reset HEAD^ <path>

$ git status
On branch <your-branch>
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   <path>

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   <path>

Per capire cosa accadrà (freccia e commenti non fanno parte del comando):

git diff --cached   -> show staged changes to revert <path> to before HEAD
git diff            -> show unstaged changes to add current <path> changes

Annulla <path>modifiche nell'ultimo commit:

git commit --amend  -> reverts changes on HEAD by amending with staged changes

Crea nuovo commit con <path>modifiche:

git commit -a -m "New Commit" -> adds new commit with unstaged changes

Ciò ha l'effetto di creare un nuovo commit contenente le modifiche estratte dall'ultimo commit.

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.