Como posso dividir um commit do Git enterrado na história?

292

Conheci minha história e quero fazer algumas alterações. O problema é que tenho um commit com duas alterações não relacionadas, e esse commit está cercado por outras alterações no meu histórico local (não enviado por push).

Quero dividir esse commit antes de enviá-lo, mas a maioria dos guias que vejo estão relacionados à divisão do commit mais recente ou das alterações locais não confirmadas. É possível fazer isso com um commit que está um pouco enterrado na história, sem ter que "refazer" meus commits desde então?

Ben
fonte

Respostas:

450

Há um guia para dividir confirmações na página de manual rebase . O resumo rápido é:

  • Execute uma nova reformulação interativa, incluindo o commit de destino (por exemplo git rebase -i <commit-to-split>^ branch) e marque-o para ser editado.

  • Quando o rebase atingir esse commit, use git reset HEAD^para redefinir para antes do commit, mas mantenha sua árvore de trabalho intacta.

  • Adicione alterações de forma incremental e as confirme, fazendo o número de confirmações desejadas. add -ppode ser útil adicionar apenas algumas das alterações em um determinado arquivo. Use commit -c ORIG_HEADse desejar reutilizar a mensagem de confirmação original para uma determinada confirmação.

  • Se você quiser testar o que está comprometendo (boa ideia!) git stashPara ocultar a parte que não foi comprometida (ou stash --keep-indexantes mesmo de executá-la), teste e, em seguida, git stash popretorne o restante para a árvore de trabalho. Continue fazendo confirmações até obter todas as modificações confirmadas, ou seja, tenha uma árvore de trabalho limpa.

  • Execute git rebase --continuepara continuar aplicando as confirmações após a confirmação agora dividida.

Cascabel
fonte
17
... mas não faça nada se você já empurrou o histórico desde que o commit foi dividido.
wilhelmtell
29
@wilhelmtell: omiti meu clichê habitual "potencialmente perigoso; veja 'recuperação da recuperação a montante'" porque o OP disse explicitamente que não havia divulgado essa história.
Cascabel
2
e você fez uma leitura perfeita. Eu estava tentando evitar o 'clichê' quando especifiquei que ainda não era um histórico compartilhado :) De qualquer forma, tive sucesso com sua sugestão. É uma grande dor fazer essas coisas depois do fato. Eu aprendi uma lição aqui, e é para garantir que as confirmações sejam feitas corretamente, para começar!
Ben
2
O primeiro passo pode ser melhor indicado como git rebase -i <sha1_of_the_commit_to_split>^ branch. E git guié uma boa ferramenta para a tarefa de divisão, que pode ser usada para adicionar diferentes partes de um arquivo em diferentes confirmações.
Qiang Xu
3
@QiangXu: A primeira é uma sugestão razoável. A segunda é exatamente por que eu sugeri git add -p, que pode fazer mais do que git guipode neste departamento (notavelmente editando pedaços, organizando tudo a partir do pedaço atual e procurando pedaços por expressão regular).
Cascabel
3

Veja como fazer isso com o Magit .

Digamos que commit ed417ae é o que você deseja alterar; ele contém duas alterações não relacionadas e está oculto sob uma ou mais confirmações. Pressione llpara mostrar o log e navegue para ed417ae:

log inicial

Em seguida, pressione rpara abrir o pop-up rebase

pop-up rebase

e mpara modificar o commit no ponto.

Observe como o @agora existe no commit que você deseja dividir - isso significa que HEAD está agora no commit:

modificando um commit

Queremos mover HEAD para o pai, então navegue até o pai (47e18b3) e pressione x( magit-reset-quickly, vinculado a ose você estiver usando evil-magit) e entre para dizer "sim, eu quis dizer confirmar no momento". Seu log agora deve se parecer com:

log após redefinir

Agora, pressione qpara acessar o status regular do Magit, depois use o ucomando regular unstage para desestabilizar o que não ocorre no primeiro commit, comprometa co resto como de costume, depois scalcule e comita o que ocorre no segundo commit e quando terminar: clique rpara abrir o pop-up rebase

pop-up rebase

e outro rpara continuar, e pronto! llagora mostra:

log completo

sem martelo
fonte
1

Para dividir uma confirmação <commit>e adicionar a nova confirmação antes desta , e salve a data do autor de <commit>, - as etapas a seguir:

  1. Edite a confirmação antes <commit>

    git rebase -i <commit>^^
    

    NB: talvez também seja necessário editar <commit>também.

  2. Escolha cereja <commit>no índice

    git cherry-pick -n <commit>
    
  3. Redefina interativamente as alterações desnecessárias do índice e redefina a árvore de trabalho

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

    Como alternativa, apenas armazene alterações desnecessárias de maneira interativa: git stash push -p -m "tmp other changes"

  4. Faça outras alterações (se houver) e crie o novo commit

    git commit -m "upd something" .
    

    Opcionalmente, repita os itens 2-4 para adicionar mais confirmações intermediárias.

  5. Continuar rebaseando

    git rebase --continue
    
ruvim
fonte
0

Existe uma versão mais rápida se você deseja extrair o conteúdo de apenas um arquivo. É mais rápido porque o rebase interativo não é mais interativo (e, é claro, ainda mais rápido se você deseja extrair do último commit, então não há necessidade de rebase)

  1. Use seu editor e exclua as linhas das quais deseja extrair the_file. Fechar the_file. Essa é a única edição que você precisa, todo o resto são apenas comandos git.
  2. Estágio que exclusão no índice:

    git  add  the_file
    
  3. Restaure as linhas que você acabou de excluir de volta no arquivo sem afetar o índice !

    git show HEAD:./the_file > the_file
    
  4. "SHA1" é o commit do qual você deseja extrair as linhas:

    git commit -m 'fixup! SHA1' 
    
  5. Crie a segunda confirmação totalmente nova com o conteúdo a ser extraído restaurado na etapa 3:

    git commit -m 'second and new commit' the_file 
    
  6. Não edite, não pare / continue - apenas aceite tudo:

    git rebase --autosquash -i SHA1~1
    

Obviamente, ainda mais rápido quando o commit a ser extraído é o último commit:

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

Se você usar magit, as etapas 4, 5 e 6 são uma ação única: Confirmar, correção instantânea

Março
fonte
-2

Se você ainda não pressionou, basta usar git rebase. Melhor ainda, use git rebase -ipara mover confirmações de maneira interativa. Você pode mover a confirmação incorreta para a frente, depois dividi-la como quiser e mover as correções para trás (se necessário).

Gintautas Miliauskas
fonte
14
Não há necessidade de movê-lo para qualquer lugar. Divida onde estiver.
Cascabel
1
Infelizmente, isso não funciona para mim porque parte da história após o commit depende disso, por isso estou um pouco restrito. No entanto, essa teria sido minha primeira escolha.
Ben
@ Ben: tudo bem - as confirmações posteriores não precisarão ser alteradas (supondo que você mantenha todas as alterações, em vez de jogar algumas fora). Mais informações aqui - stackoverflow.com/questions/1440050/…
Ether