Divida uma confirmação anterior em várias confirmações

1225

Sem criar uma ramificação e fazer um monte de trabalho descolado em uma nova ramificação, é possível dividir uma única confirmação em algumas confirmações diferentes depois que ela foi confirmada no repositório local?

koblas
fonte
36
Uma boa fonte para aprender como fazer isso é o Pro Git §6.4 Git Tools - Rewriting History , na seção "Dividindo um commit".
2
Os documentos vinculados no comentário acima são excelentes, mais bem explicados do que as respostas abaixo.
Blaisorblade 14/09/16
2
Sugiro o uso deste alias stackoverflow.com/a/19267103/301717 . Ele permite dividir um commit utilizandogit autorebase split COMMIT_ID
Jérôme Pouiller
A coisa mais fácil de fazer sem uma nova recuperação interativa é (provavelmente) criar uma nova ramificação iniciando no commit antes do que você deseja dividir, escolher com cereja, confirmar, redefinir, ocultar, confirmar a movimentação do arquivo, reaplicar o stash e confirmar as alterações e, em seguida, mesclar com a ramificação anterior ou escolher os commits a seguir. (. Em seguida, mude o nome antigo ramo para o atual chefe) (. É provavelmente melhor seguir o conselho MBO e fazer um rebase interativo) (copiado a partir de 2010 resposta abaixo)
William Pursell
1
Eu deparei com esse problema depois que esmaguei acidentalmente dois commits durante uma rebase em um commit anterior. Minha maneira de corrigi-lo foi o de fazer o checkout da esmagado cometer, git reset HEAD~, git stash, em seguida, git cherry-picko primeiro cometer dentro do squash, então git stash pop. Meu caso cherry-pick é bastante específica aqui, mas git stashe git stash popé bastante útil para os outros.
SOFe 31/07/19

Respostas:

1800

git rebase -i vai fazer isso.

Primeiro, comece com um diretório de trabalho limpo: não git statusdeve mostrar modificações, exclusões ou inclusões pendentes.

Agora, você deve decidir quais confirmações deseja dividir.

A) Dividindo o commit mais recente

Para dividir seu commit mais recente, primeiro:

$ git reset HEAD~

Agora comprometa as peças individualmente da maneira usual, produzindo quantos commits forem necessários.

B) Dividindo um commit mais atrás

Isso requer reorganizar , ou seja, reescrever o histórico. Para encontrar a confirmação correta, você tem várias opções:

  • Se fosse três commits de volta, então

    $ git rebase -i HEAD~3
    

    onde 3é quantos commits de volta é.

  • Se estava mais atrás na árvore do que você deseja contar, então

    $ git rebase -i 123abcd~
    

    onde 123abcdestá o SHA1 do commit que você deseja dividir.

  • Se você estiver em uma ramificação diferente (por exemplo, uma ramificação de recurso) que planeja mesclar no mestre:

    $ git rebase -i master
    

Quando você receber a tela de edição de rebase, encontre a confirmação que deseja desmembrar. No início dessa linha, substitua pickpor edit( epara abreviar). Salve o buffer e saia. O rebase agora será interrompido logo após o commit que você deseja editar. Então:

$ git reset HEAD~

Confirme as peças individualmente da maneira usual, produzindo quantos commits forem necessários e, em seguida,

$ git rebase --continue
Wayne Conrad
fonte
2
Obrigado por esta resposta. Eu queria ter alguns arquivos confirmados anteriormente na área de teste, portanto as instruções para mim eram um pouco diferentes. Antes que eu pudesse git rebase --continue, eu realmente tive que git add (files to be added), git commit, em seguida, git stash(para os arquivos restantes). Depois git rebase --continue, eu costumava git checkout stash .obter os arquivos restantes #
Eric Hu
18
A resposta do manojlds na verdade tem esse link para a documentação do git-scm , que também explica o processo de divisão de commit com muita clareza.
56
Você também deve aproveitar a vantagem de git add -padicionar apenas seções parciais de arquivos, possivelmente com a eopção de editar as diferenças para confirmar apenas um pedaço. git stashtambém é útil se você deseja levar algum trabalho adiante, mas removê-lo da confirmação atual.
Craig Ringer
2
Se você deseja dividir e reordenar as confirmações, o que eu gosto de fazer é dividir primeiro e depois reordenar separadamente usando outro git rebase -i HEAD^3comando. Dessa forma, se a divisão der errado, você não precisará desfazer tanto trabalho.
David M. Lloyd
4
@kralyk Os arquivos que foram recentemente confirmados no HEAD serão deixados no disco depois git reset HEAD~. Eles não estão perdidos.
Wayne Conrad
312

No manual git-rebase (seção SPLITTING COMMITS)

No modo interativo, você pode marcar confirmações com a ação "editar". No entanto, isso não significa necessariamente que o git rebase espera que o resultado dessa edição seja exatamente um commit. Na verdade, você pode desfazer a confirmação ou adicionar outras confirmações. Isso pode ser usado para dividir um commit em dois:

  • Inicie uma rebase interativa com git rebase -i <commit>^, onde <commit>está o commit que você deseja dividir. De fato, qualquer intervalo de confirmação serve, desde que contenha esse commit.

  • Marque o commit que você deseja dividir com a ação "edit".

  • Quando se trata de editar essa confirmação, execute git reset HEAD^. O efeito é que o HEAD é rebobinado por um e o índice segue o exemplo. No entanto, a árvore de trabalho permanece a mesma.

  • Agora adicione as alterações ao índice que você deseja ter no primeiro commit. Você pode usar git add(possivelmente interativamente) ou git gui(ou ambos) para fazer isso.

  • Confirme o índice agora atual com qualquer mensagem de confirmação apropriada agora.

  • Repita as duas últimas etapas até que sua árvore de trabalho esteja limpa.

  • Continue a rebase com git rebase --continue.

MBO
fonte
12
No Windows você usa em ~vez de ^.
21416 Kevin Kuszyk
13
Palavra de cautela: com essa abordagem, perdi a mensagem de confirmação.
user420667
11
@ user420667 Sim, é claro. Estamos resetexecutando o commit, afinal - mensagem incluída. A coisa prudente a se fazer, se você sabe que vai dividir um commit, mas deseja manter parte / toda a sua mensagem, é tirar uma cópia dessa mensagem. Portanto, git showo commit antes do rebaseing, ou se você esquecer ou preferir isso: volte mais tarde através do reflog. Nada disso será "perdido" até que seja coletado em 2 semanas ou o que for.
underscore_d
4
~e ^são coisas diferentes, mesmo no Windows. Você ainda deseja o sinal de intercalação ^, portanto, basta escapar dele conforme apropriado para o seu shell. No PowerShell, é HEAD`^. Com o cmd.exe, você pode dobrá-lo para escapar como HEAD^^. Na maioria das conchas (todas?), Você pode colocar aspas como "HEAD^".
AndrewF
7
Você também pode fazer git commit --reuse-message=abcd123. A opção curta para isso é -C.
j0057
41

Use git rebase --interactivepara editar a confirmação anterior, executar git reset HEAD~e, em seguida, git add -padicionar algumas, fazer uma confirmação, adicionar mais algumas e fazer outra confirmação, quantas vezes quiser. Quando terminar, execute git rebase --continuee você terá todas as confirmações de divisão anteriormente na sua pilha.

Importante : Observe que você pode brincar e fazer todas as alterações desejadas, e não precisa se preocupar em perder alterações antigas, porque sempre é possível executar git reflogpara encontrar o ponto em seu projeto que contém as alterações desejadas (vamos chamá-lo a8c4ab) e, em seguida git reset a8c4ab.

Aqui está uma série de comandos para mostrar como funciona:

mkdir git-test; cd git-test; git init

agora adicione um arquivo A

vi A

adicione esta linha:

one

git commit -am one

adicione esta linha a A:

two

git commit -am two

adicione esta linha a A:

three

git commit -am three

agora o arquivo A se parece com isso:

one
two
three

e nossa git logaparência é a seguinte (bem, eu usogit log --pretty=oneline --pretty="%h %cn %cr ---- %s"

bfb8e46 Rose Perrone 4 seconds ago ---- three
2b613bc Rose Perrone 14 seconds ago ---- two
9aac58f Rose Perrone 24 seconds ago ---- one

Digamos que queremos dividir o segundo commit two,.

git rebase --interactive HEAD~2

Isso traz uma mensagem parecida com esta:

pick 2b613bc two
pick bfb8e46 three

Altere o primeiro pickpara um epara editar essa confirmação.

git reset HEAD~

git diff nos mostra que acabamos de desestabilizar o commit que fizemos para o segundo commit:

diff --git a/A b/A
index 5626abf..814f4a4 100644
--- a/A
+++ b/A
@@ -1 +1,2 @@
 one
+two

Vamos preparar essa alteração e adicionar "e um terceiro" a essa linha no arquivo A.

git add .

Normalmente, esse é o ponto durante uma nova reformulação interativa em que executaríamos git rebase --continue, porque geralmente queremos voltar à nossa pilha de confirmações para editar uma confirmação anterior. Mas desta vez, queremos criar um novo commit. Então vamos correr git commit -am 'two and a third'. Agora editamos o arquivo Ae adicionamos a linha two and two thirds.

git add . git commit -am 'two and two thirds' git rebase --continue

Temos um conflito com o nosso commit three, então vamos resolvê-lo:

Nós vamos mudar

one
<<<<<<< HEAD
two and a third
two and two thirds
=======
two
three
>>>>>>> bfb8e46... three

para

one
two and a third
two and two thirds
three

git add .; git rebase --continue

Agora, nossa git log -paparência é assim:

commit e59ca35bae8360439823d66d459238779e5b4892
Author: Rose Perrone <[email protected]>
Date:   Sun Jul 7 13:57:00 2013 -0700

    three

diff --git a/A b/A
index 5aef867..dd8fb63 100644
--- a/A
+++ b/A
@@ -1,3 +1,4 @@
 one
 two and a third
 two and two thirds
+three

commit 4a283ba9bf83ef664541b467acdd0bb4d770ab8e
Author: Rose Perrone <[email protected]>
Date:   Sun Jul 7 14:07:07 2013 -0700

    two and two thirds

diff --git a/A b/A
index 575010a..5aef867 100644
--- a/A
+++ b/A
@@ -1,2 +1,3 @@
 one
 two and a third
+two and two thirds

commit 704d323ca1bc7c45ed8b1714d924adcdc83dfa44
Author: Rose Perrone <[email protected]>
Date:   Sun Jul 7 14:06:40 2013 -0700

    two and a third

diff --git a/A b/A
index 5626abf..575010a 100644
--- a/A
+++ b/A
@@ -1 +1,2 @@
 one
+two and a third

commit 9aac58f3893488ec643fecab3c85f5a2f481586f
Author: Rose Perrone <[email protected]>
Date:   Sun Jul 7 13:56:40 2013 -0700

    one

diff --git a/A b/A
new file mode 100644
index 0000000..5626abf
--- /dev/null
+++ b/A
@@ -0,0 +1 @@
+one
Rose Perrone
fonte
38

As respostas anteriores abordaram o uso de git rebase -ipara editar o commit que você deseja dividir e o comprometeu em partes.

Isso funciona bem ao dividir os arquivos em confirmações diferentes, mas se você deseja separar as alterações nos arquivos individuais, é necessário saber mais.

Tendo chegado ao commit que você deseja dividir, usando rebase -ie marcando edit, você tem duas opções.

  1. Após o uso git reset HEAD~, percorra os patches individualmente usando git add -ppara selecionar os que você deseja em cada confirmação

  2. Edite a cópia de trabalho para remover as alterações que você não deseja; comprometer esse estado provisório; e retire o commit completo para a próxima rodada.

A opção 2 é útil se você estiver dividindo uma confirmação grande, pois permite verificar se as versões intermediárias são construídas e executadas corretamente como parte da mesclagem. Isso ocorre da seguinte maneira.

Após usar rebase -ie editconfirmar a confirmação, use

git reset --soft HEAD~

para desfazer a confirmação, mas deixe os arquivos confirmados no índice. Você também pode fazer uma redefinição mista, omitindo --soft, dependendo de quão próximo do resultado final será o seu commit inicial. A única diferença é se você inicia com todas as alterações realizadas ou com todas elas não realizadas.

Agora entre e edite o código. Você pode remover alterações, excluir arquivos adicionados e fazer o que quiser para construir o primeiro commit da série que você está procurando. Você também pode compilá-lo, executá-lo e confirmar se possui um conjunto consistente de fontes.

Quando estiver satisfeito, prepare / descompacte os arquivos conforme necessário (eu gosto de usar git guipara isso) e confirme as alterações através da interface do usuário ou da linha de comando

git commit

Esse é o primeiro commit feito. Agora você deseja restaurar sua cópia de trabalho para o estado que ela tinha após a consolidação que está sendo dividida, para que você possa realizar mais alterações na sua próxima consolidação. Para encontrar o sha1 do commit que você está editando, use git status. Nas primeiras linhas do status, você verá o comando rebase que está sendo executado no momento, no qual você pode encontrar o sha1 do seu commit original:

$ git status
interactive rebase in progress; onto be83b41
Last commands done (3 commands done):
   pick 4847406 US135756: add debugging to the file download code
   e 65dfb6a US135756: write data and download from remote
  (see more in file .git/rebase-merge/done)
...

Nesse caso, o commit que estou editando tem sha1 65dfb6a. Sabendo disso, posso verificar o conteúdo desse commit no meu diretório de trabalho usando o formato git checkoutque leva tanto o commit quanto o local do arquivo. Aqui eu uso .como local do arquivo para substituir toda a cópia de trabalho:

git checkout 65dfb6a .

Não perca o ponto no final!

Isso fará o check-out e preparará os arquivos como estavam após a confirmação que você está editando, mas em relação à confirmação anterior que você fez, portanto, quaisquer alterações que você já tenha confirmado não farão parte do commit.

Você pode ir em frente agora e confirmar como está para terminar a divisão ou sair novamente, excluindo algumas partes da confirmação antes de fazer outra confirmação provisória.

Se você deseja reutilizar a mensagem de confirmação original para uma ou mais confirmações, é possível usá-la diretamente dos arquivos de trabalho da rebase:

git commit --file .git/rebase-merge/message

Por fim, depois de confirmar todas as alterações,

git rebase --continue

continuará e concluirá a operação de rebase.

Andy Mortimer
fonte
3
Obrigado!!! Essa deve ser a resposta aceita. Teria me poupado muito tempo e dor hoje. É a única resposta em que o resultado da confirmação final o leva ao mesmo estado que a confirmação em edição.
Doug Coburn
1
Gosto da maneira como você usa a mensagem de confirmação original.
Salamandar
Usando a opção 2, quando faço git checkout *Sha I'm Editing* ., sempre diz Updated 0 paths from *Some Sha That's Not In Git Log*e não dá alterações.
Noumenon 03/03
18

git rebase --interactivepode ser usado para dividir uma confirmação em confirmações menores. Os documentos do Git sobre rebase têm uma explicação concisa do processo - Divisões de consolidação :

No modo interativo, você pode marcar confirmações com a ação "editar". No entanto, isso não significa necessariamente que git rebaseo resultado dessa edição seja exatamente uma confirmação. Na verdade, você pode desfazer a confirmação ou adicionar outras confirmações. Isso pode ser usado para dividir um commit em dois:

  • Inicie uma rebase interativa com git rebase -i <commit>^, onde <commit>está o commit que você deseja dividir. De fato, qualquer intervalo de confirmação serve, desde que contenha esse commit.

  • Marque o commit que você deseja dividir com a ação "edit".

  • Quando se trata de editar essa confirmação, execute git reset HEAD^. O efeito é que o HEAD é rebobinado por um e o índice segue o exemplo. No entanto, a árvore de trabalho permanece a mesma.

  • Agora adicione as alterações ao índice que você deseja ter no primeiro commit. Você pode usar git add(possivelmente interativamente) ou git gui (ou ambos) para fazer isso.

  • Confirme o índice agora atual com qualquer mensagem de confirmação apropriada agora.

  • Repita as duas últimas etapas até que sua árvore de trabalho esteja limpa.

  • Continue a rebase com git rebase --continue.

Se você não tiver certeza absoluta de que as revisões intermediárias são consistentes (elas compilam, passam no testuite etc.), devem ser usadas git stashpara ocultar as alterações ainda não confirmadas após cada confirmação, teste e alteração da confirmação, se forem necessárias correções. .


fonte
No Windows, lembre- ^se de um caractere de escape para a linha de comando: ele deve ser dobrado. Por exemplo, emitir em git reset HEAD^^vez degit reset HEAD^ .
Frédéric
@ Frédéric: s Eu nunca me deparei com isso. Pelo menos no PowerShell, esse não é o caso. Em seguida, usar ^duas vezes redefine duas confirmações acima do HEAD atual.
Farway
@ Farway, tente em uma linha de comando clássica. O PowerShell é outra besta, seu caráter de fuga é o backtilt.
Frédéric
Para resumir: "HEAD^"no cmd.exe ou no PowerShell, HEAD^^no cmd.exe,HEAD`^ no PowerShell. É útil aprender sobre como os shells - e seu shell em particular - funcionam (ou seja, como um comando se transforma em partes individuais que são passadas para o programa), para que você possa adaptar comandos on-line aos caracteres certos para seu shell em particular. (Não é específico para o Windows.)
AndrewF
11

Agora, no TortoiseGit mais recente do Windows, você pode fazer isso com muita facilidade.

Abra a caixa de diálogo rebase, configure-a e execute as seguintes etapas.

  • Clique com o botão direito do mouse no commit que você deseja dividir e selecione " Edit" (entre pick, squash, delete ...).
  • Clique em " Start" para começar a rebasear.
  • Quando chegar ao commit para dividir, verifique o Edit/Splitbotão " " e clique em " Amend" diretamente. A caixa de diálogo de confirmação é aberta.
    Editar / dividir confirmação
  • Desmarque os arquivos que deseja colocar em uma confirmação separada.
  • Edite a mensagem de confirmação e clique em " commit".
  • Até que haja arquivos a serem confirmados, a caixa de diálogo de confirmação será aberta novamente. Quando não houver mais arquivos a serem confirmados, ele ainda perguntará se você deseja adicionar mais um commit.

Muito útil, obrigado TortoiseGit!

Mikaël Mayer
fonte
10

Você pode fazer o rebase interativo git rebase -i . A página de manual tem exatamente o que você deseja:

http://git-scm.com/docs/git-rebase#_splitting_commits

manojlds
fonte
14
Dar um pouco mais de contexto sobre como abordar os problemas do que apenas fornecer um RTFM seria um pouco mais útil.
Jordan Dea-Mattson
8

Por favor, note que também há git reset --soft HEAD^. É semelhante a git reset(cujo padrão é --mixed), mas mantém o conteúdo do índice. Portanto, se você adicionou / removeu arquivos, já os tem no índice.

Se mostra muito útil em caso de commits gigantes.

letal
fonte
3

Aqui está como dividir um commit no IntelliJ IDEA , PyCharm , PhpStorm etc

  1. Na janela de log do Controle de versão, selecione a confirmação que você deseja dividir, clique com o botão direito do mouse e selecione a opção Interactively Rebase from Here

  2. marque o que você deseja dividir como edit, clique emStart Rebasing

  3. Você deverá ver uma etiqueta amarela, o que significa que o HEAD está definido para esse commit. Clique com o botão direito do mouse nessa confirmação e selecioneUndo Commit

  4. Agora que essas confirmações estão de volta à área de preparação, você pode confirmá-las separadamente. Depois que todas as alterações foram confirmadas, a confirmação antiga se torna inativa.

bzuo
fonte
2

A coisa mais fácil de fazer sem uma nova recuperação interativa é (provavelmente) criar uma nova ramificação iniciando no commit antes do que você deseja dividir, escolher com cereja, confirmar, redefinir, ocultar, confirmar a movimentação do arquivo, reaplicar o stash e confirmar as alterações e, em seguida, mesclar com a ramificação anterior ou escolher as confirmações a seguir. (Em seguida, mude o nome da filial anterior para o cabeçalho atual.) (Provavelmente é melhor seguir os conselhos dos MBOs e fazer uma nova análise interativa.)

William Pursell
fonte
de acordo com os padrões da SO atualmente, isso deve ser qualificado como não resposta; mas isso ainda pode ser útil para outras pessoas, por isso, se você não se importa, mova isso para comentários da postagem original
YakovL
@YakovL Parece razoável. Sobre o princípio da ação mínima, não excluirei a resposta, mas não contestaria se outra pessoa o fizesse.
William Pursell
isso seria muito mais fácil do que todo rebase -i sugestões. Acho que isso não recebeu muita atenção devido à falta de formatação. Talvez você possa revisá-lo, agora que possui 126 mil pontos e provavelmente sabe SO. ;)
erikbwork
1

Se você tem isso:

A - B <- mybranch

Onde você confirmou um conteúdo no commit B:

/modules/a/file1
/modules/a/file2
/modules/b/file3
/modules/b/file4

Mas você deseja dividir B em C - D e obter este resultado:

A - C - D <-mybranch

Você pode dividir o conteúdo como este, por exemplo (conteúdo de diferentes diretórios em diferentes confirmações) ...

Redefina a ramificação de volta para a confirmação antes da divisão:

git checkout mybranch
git reset --hard A

Crie o primeiro commit (C):

git checkout B /modules/a
git add -u
git commit -m "content of /modules/a"

Crie o segundo commit (D):

git checkout B /modules/b
git add -u
git commit -m "content of /modules/b"
Martin G
fonte
E se houver confirmações acima de B?
precisa saber é o seguinte
1

Faz mais de 8 anos, mas talvez alguém ache útil de qualquer maneira. Eu era capaz de fazer o truque sem rebase -i. A idéia é levar o git ao mesmo estado que estava antes de você git commit:

# first rewind back (mind the dot,
# though it can be any valid path,
# for instance if you want to apply only a subset of the commit)
git reset --hard <previous-commit> .

# apply the changes
git checkout <commit-you-want-to-split>

# we're almost there, but the changes are in the index at the moment,
# hence one more step (exactly as git gently suggests):
# (use "git reset HEAD <file>..." to unstage)
git reset

Depois disso, você verá isso brilhante Unstaged changes after reset:e seu repositório estará em um estado como se estivesse prestes a confirmar todos esses arquivos. A partir de agora você pode facilmente cometer novamente, como costuma fazer. Espero que ajude.

Stanislav E. Govorov
fonte
0

Uma referência rápida dos comandos necessários, porque basicamente sei o que fazer, mas sempre esqueço a sintaxe correta:

git rebase -i <sha1_before_split>
# mark the targeted commit with 'edit'
git reset HEAD^
git add ...
git commit -m "First part"
git add ...
git commit -m "Second part"
git rebase --continue

Créditos à postagem de Emmanuel Bernard .

Sparkofska
fonte