Como dividir o último commit em dois no Git

277

Eu tenho dois ramos de trabalho, mestre e fórum e acabei de fazer algumas modificações no ramo de fórum , que gostaria de escolher no mestre . Mas, infelizmente, o commit que eu quero escolher também contém algumas modificações que eu não quero.

A solução provavelmente seria excluir, de alguma forma, a confirmação incorreta e substituí-la por duas confirmações separadas, uma pelas alterações que eu quero selecionar no mestre e outras que não pertencem a ela.

Eu tentei fazer

git reset --hard HEAD^

que excluiu todas as alterações, então tive que voltar com

git reset ORIG_HEAD

Então, minha pergunta é: qual é a melhor maneira de dividir o último commit em dois commits separados?

Jakub Arnold
fonte

Respostas:

332

Você deve usar o índice. Depois de fazer uma redefinição mista (" git reset HEAD ^"), adicione o primeiro conjunto de alterações no índice e as confirme. Em seguida, comprometa o resto.

Você pode usar " git add " para colocar todas as alterações feitas em um arquivo no índice. Se você não deseja organizar todas as modificações feitas em um arquivo, apenas algumas delas, você pode usar "git add -p".

Vamos ver um exemplo. Vamos supor que eu tivesse um arquivo chamado myfile, que contém o seguinte texto:

something
something else
something again

Eu o modifiquei no meu último commit para que agora fique assim:

1
something
something else
something again
2

Agora eu decido que quero dividi-lo em dois e quero que a inserção da primeira linha esteja no primeiro commit e a inserção da última linha no segundo commit.

Primeiro, volto ao pai do HEAD, mas quero manter as modificações no sistema de arquivos, então uso "git reset" sem argumento (o que fará a chamada redefinição "mista"):

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

Agora eu uso "git add -p" para adicionar as alterações que quero confirmar no índice (= eu as preparo). "git add -p" é uma ferramenta interativa que pergunta sobre quais alterações no arquivo devem ser adicionadas ao índice.

$ 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

Então eu confirmo esta primeira alteração:

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

Agora eu posso confirmar todas as outras alterações (ou seja, o número "2" colocado na última linha):

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

Vamos verificar o log para ver o que temos:

$ 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
hcs42
fonte
1
Eu tenho me acostumado lentamente a usar o Mercurial na última semana e meia, e há um prático comando de atalho git reset [--patch|-p] <commit>que você pode usar para evitar o problema de ter que fazer isso git add -papós a redefinição. Estou certo? Usando o git 1.7.9.5.
trojjer
2
Aqui está um pouco mais sobre essa técnica, incluindo a reestruturação se for uma confirmação mais antiga, ou você precisa alterar as confirmações N para M confirma: emmanuelbernard.com/blog/2014/04/14/… .
Chris Westin
84

Metas:

  • Eu quero dividir um commit ( splitme) passado em dois.
  • Eu quero manter a mensagem de confirmação .

Plano:

  1. rebase interativo de um antes splitme.
  2. editar splitme.
  3. Redefina os arquivos para dividir em uma segunda confirmação.
  4. Alterar confirmação, manter a mensagem, modificar conforme necessário.
  5. Adicione novamente os arquivos divididos desde o primeiro commit.
  6. Confirme com uma nova mensagem.
  7. Continue rebase.

As etapas de rebase (1 e 7) podem ser ignoradas se a splitmeconfirmação for mais 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 eu quisesse que os arquivos divididos fossem confirmados primeiro, então eu rebasei -i novamente e mudaria a ordem

git rebase -i splitme^
# swap order of splitme and 'just some files'
spazm
fonte
1
git reset HEAD^foi a peça que faltava no quebra-cabeça. Funciona bem com -ptambém. Obrigado!
Marius Gedminas
10
É importante observar o -- $filesargumento para git reset. Com os caminhos passados, git resetrestaura esses arquivos no estado do commit referenciado, mas não altera nenhum commit. Se você deixar de lado os caminhos, "perderá" o commit que deseja alterar na próxima etapa.
marcadores duelin
2
Este método impede que você precise copiar e colar sua primeira mensagem de confirmação novamente, em comparação com a resposta aceita.
Calvin
Além disso: se você deseja redefinir todos os arquivos, basta usar git reset HEAD^ -- .. Surpreendentemente, esse não é exatamente o comportamento de git reset HEAD^.
27619 allidoiswin
52

Para alterar a confirmação atual em duas confirmações, você pode fazer algo como o seguinte.

Ou:

git reset --soft HEAD^

Isso desfaz o último commit, mas deixa tudo preparado. Você pode desestabilizar certos arquivos:

git reset -- file.file

Como opção, reestabeleça partes desses arquivos:

git add -p file.file

Faça um novo primeiro commit:

git commit

O estágio e confirma o restante das alterações em um segundo commit:

git commit -a

Ou:

Desfazer e desestabilizar todas as alterações da última confirmação:

git reset HEAD^

Preparar seletivamente a primeira rodada de alterações:

git add -p

Confirmar:

git commit

Confirme o restante das alterações:

git commit -a

(Em qualquer uma das etapas, se você desfez uma confirmação que adicionou um arquivo totalmente novo e deseja adicioná-lo à segunda confirmação, será necessário adicioná-lo manualmente, pois commit -aapenas altera as etapas nos arquivos já rastreados.)

CB Bailey
fonte
22

Execute git gui, selecione o botão de opção "Alterar última confirmação" e desinstale (Confirmar> Desinstalar do commit, ou Ctrl- U) as alterações que você não deseja entrar no primeiro commit. Eu acho que é a maneira mais fácil de fazer isso.

Outra coisa que você pode fazer é escolher a alteração sem confirmar ( git cherry-pick -n) e, em seguida, manualmente ou com git guias alterações desejadas selecionadas antes de confirmar.

Michael Krelin - hacker
fonte
15
git reset HEAD^

o --hard é o que está matando suas alterações.

semanticart
fonte
13

Estou surpreso que ninguém tenha sugerido git cherry-pick -n forum. Isso preparará as alterações da forumconfirmação mais recente, mas não as comprometerá - você poderá resetafastar as alterações desnecessárias e confirmar o que deseja manter.

dahlbyk
fonte
3

O método de dupla reversão-squash

  1. Faça outra confirmação que remova as alterações indesejadas. (Se for por arquivo, isso é realmente fácil: git checkout HEAD~1 -- files with unwanted changese git commit. Caso contrário, os arquivos com alterações mistas podem ser parcialmente preparados git reset filee git add -p filecomo uma etapa intermediária.) Chame isso de reverter .
  2. git revert HEAD- Faça outro commit, que adiciona de volta as alterações indesejadas. Esta é a dupla reversão
  3. Dos 2 commits que você fez agora, esmague o primeiro no commit para split ( git rebase -i HEAD~3). Agora, esse commit fica livre das alterações indesejadas, pois estão no segundo commit.

Benefícios

  • Preserva a mensagem de confirmação
  • Funciona mesmo se o commit para dividir não for o último. Requer apenas que as alterações indesejadas não entrem em conflito com confirmações posteriores
user2394284
fonte
1

Como você escolhe a cereja, pode:

  1. cherry-pickcom a --no-commitopção adicionada.
  2. resete use add --patch, add --editou apenas addpara preparar o que você deseja manter.
  3. commit as mudanças encenadas.
    • Para reutilizar a mensagem de confirmação original, você pode adicionar --reuse-message=<old-commit-ref>ou --reedit-message=<old-commit-ref>opções ao commitcomando.
  4. Sopre as mudanças sem estágio com reset --hard.

Outra maneira, preservando ou editando a mensagem de confirmação original:

  1. cherry-pick o commit original como normal.
  2. Inverta as alterações que você não deseja e use addpara encenar a reversão.
    • Essa etapa seria fácil se você estiver removendo o que adicionou, mas um pouco complicada se estiver adicionando o que removeu ou revertendo uma alteração.
  3. commit --amend para efetuar a reversão no commit escolhido pela cereja.
    • Você receberá a mesma mensagem de confirmação novamente, que poderá manter ou revisar conforme necessário.
ADTC
fonte
0

Essa pode ser outra solução direcionada para casos em que há uma confirmação enorme e uma pequena quantidade de arquivos precisa ser movida para uma nova confirmação. Isso funcionará se um conjunto de<path> arquivos que ser extraído da última confirmação no HEAD e todos movidos para uma nova confirmação. Se forem necessárias várias confirmações, outras soluções podem ser usadas.

Primeiro faça correções nas áreas preparadas e não preparadas que conteriam as alterações para reverter o código para antes e depois da modificação, respectivamente:

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>

Para entender o que vai acontecer (seta e comentários não fazem parte do comando):

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

Reverter <path>alterações na última confirmação:

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

Crie uma nova confirmação com <path>alterações:

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

Isso tem o efeito de criar uma nova confirmação contendo as alterações extraídas da última confirmação.

Jose Cifuentes
fonte