Como injetar um commit entre dois commits arbitrários no passado?

126

Suponha que eu tenha o seguinte histórico de consolidação na minha ramificação somente local:

A -- B -- C

Como insiro um novo commit entre Ae B?

BartoszKP
fonte
3
confirmar chamado Ą ? :)
Antek
Eu tenho uma pergunta muito semelhante sobre como inserir uma nova versão no passado, em vez de um commit.
Pavel P

Respostas:

178

É ainda mais fácil do que na resposta do OP.

  1. git rebase -i <any earlier commit>. Isso exibe uma lista de confirmações no seu editor de texto configurado.
  2. Encontre o commit que você deseja inserir depois (suponha que seja a1b2c3d). No seu editor, para essa linha, mude pickpara edit.
  3. Comece a rebase fechando seu editor de texto (salve as alterações). Isso deixa você em um prompt de comando com o commit que você escolheu anteriormente ( a1b2c3d) como se tivesse acabado de ser confirmado .
  4. Faça as alterações e git commit( NÃO alterando, ao contrário da maioria das edits). Isso cria um novo commit após o que você escolheu.
  5. git rebase --continue. Isso repete as confirmações sucessivas, deixando sua nova confirmação inserida no local correto.

Cuidado para reescrever a história e quebrar qualquer outra pessoa que tentar puxar.

SLaks
fonte
1
Isso adicionou o novo commit após o commit, que é após o que eu estava refazendo (também o último commit), em vez de logo após o que estava refazendo. O resultado foi o mesmo que se eu tivesse simplesmente feito um novo commit no final com as alterações que queria inserir. Minha história tornou-se em A -- B -- C -- Dvez de desejada A -- D -- B -- C.
XedinUnknown
2
@XedinUnknown: então você não usou o Rebase corretamente.
SLaks
3
Agora Dpode ser um commit em qualquer lugar. Suponha que tenhamos A - B - Ce tenhamos algum commit Dque nem esteja nesse ramo. No entanto, sabemos o seu SHA, podemos fazer git rebase -i HEAD~3. Agora, entre as linhas Ae B pick, inserimos uma nova pick linha que diz pick SHAdar o hash do desejado D. Não precisa ser o hash completo, apenas o reduzido. git rebase -iapenas cerejas seleciona quaisquer confirmações listadas por picklinhas no buffer; eles não precisam ser os originais listados para você.
Kaz
1
@Kaz Parece uma resposta diferente e válida.
BartoszKP
3
Ainda mais fácil, você pode usar a breakpalavra - chave no editor em sua própria linha entre duas confirmações (ou na primeira linha, para inserir uma confirmação antes da confirmação especificada).
SimonT
30

Acontece que é bastante simples, a resposta encontrada aqui . Suponha que você esteja em um galho branch. Execute estas etapas:

  • crie uma ramificação temporária a partir da confirmação após desejar inserir a nova confirmação (neste caso, confirmação A):

    git checkout -b temp A
    
  • execute as alterações e as confirme, criando uma confirmação, vamos chamá-lo N:

    git commit -a -m "Message"
    

    (ou git addseguido por git commit)

  • rebase os commits que você deseja ter após o novo commit (neste caso confirma Be C) no novo commit:

    git rebase temp branch
    

(possivelmente você precisará usar -ppara preservar mesclagens, se houver) - graças a um comentário não existente de ciekawy )

  • exclua a ramificação temporária:

    git branch -d temp
    

Depois disso, o histórico é o seguinte:

A -- N -- B -- C

É claro que é possível que alguns conflitos apareçam durante o rebase.

Caso sua filial não seja local, somente isso introduzirá o histórico de reescritas, portanto, poderá causar problemas sérios.

BartoszKP
fonte
2
Não consegui seguir a resposta aceita pelo SLaks, mas funcionou para mim. Depois de obter o histórico de consolidação que eu queria, tive git push --forceque alterar o repositório remoto.
CaractereDeEscape
1
Ao usar o rebase, a opção -Xtheirs resolve automaticamente os conflitos corretamente git rebase temp branch -Xtheirs. Resposta útil para injetar em um script!
David C
Para noobs como eu, gostaria de acrescentar que depois git rebase temp branch, mas antes git branch -d temp, tudo o que você precisa fazer é corrigir e organizar a fusão de conflitos e problemas git rebase --continue, ou seja, não há necessidade de cometer nada, etc.
Pugsley
19

Solução ainda mais fácil:

  1. Crie seu novo commit no final, D. Agora você tem:

    A -- B -- C -- D
    
  2. Então corra:

    $ git rebase -i hash-of-A
    
  3. O Git abrirá seu editor e ficará assim:

    pick 8668d21 B
    pick 650f1fc C
    pick 74096b9 D
    
  4. Apenas mova D para o topo assim, então salve e saia

    pick 74096b9 D
    pick 8668d21 B
    pick 650f1fc C
    
  5. Agora você terá:

    A -- D -- B -- C
    
Mateus
fonte
6
Boa idéia, no entanto, pode ser difícil introduzir D em C, quando você pretende que essas alterações sejam incorretas. para A.
BartoszKP 14/10
Eu tenho uma situação em que tenho 3 confirmações que quero refazer juntas e uma confirmação no meio que não é relacionada. É muito bom poder mover esse commit mais cedo ou mais tarde na linha de commit.
unflores
13

Supondo que o histórico de confirmação seja preA -- A -- B -- C, se você deseja inserir uma confirmação entre Ae B, as etapas são as seguintes:

  1. git rebase -i hash-of-preA

  2. O Git abrirá seu editor. O conteúdo pode ser assim:

    pick 8668d21 A
    pick 650f1fc B
    pick 74096b9 C
    

    Mude o primeiro pickpara edit:

    edit 8668d21 A
    pick 650f1fc B
    pick 74096b9 C
    

    Salvar e sair.

  3. Modifique seu código e depois git add . && git commit -m "I"

  4. git rebase --continue

Agora seu histórico de confirmação do Git é preA -- A -- I -- B -- C


Se você encontrar um conflito, o Git irá parar nesse commit. Você pode usar git diffpara localizar marcadores de conflito e resolvê-los. Depois de resolver todos os conflitos, você precisa git add <filename>dizer ao Git que o conflito foi resolvido e depois executado novamente git rebase --continue.

Se você quiser desfazer a rebase, use git rebase --abort.

haolee
fonte
10

Aqui está uma estratégia que evita fazer um "corte de edição" durante o rebase visto nas outras respostas que li.

Ao usar, git rebase -ivocê obtém uma lista de confirmações desde essa confirmação. Basta adicionar uma "quebra" na parte superior do arquivo, isso fará com que a rebase se quebre nesse ponto.

break
pick <B's hash> <B's commit message>
pick <C's hash> <C's commit message>

Uma vez lançado, git rebaseagora irá parar no ponto do "intervalo". Agora você pode editar seus arquivos e criar seu commit normalmente. Em seguida, você pode continuar a recuperação com git rebase --continue. Isso pode causar conflitos que você precisará corrigir. Se você se perder, não se esqueça de que pode sempre abortar o uso git rebase --abort.

Essa estratégia pode ser generalizada para inserir um commit em qualquer lugar, basta colocar o "break" no local em que você deseja inserir um commit.

Após reescrever o histórico, não esqueça git push -f. Os avisos usuais sobre outras pessoas que buscam sua filial se aplicam.

axerologementy
fonte
Desculpe, mas tenho problemas para entender como é isso "evitando rebase". Você é correndo rebaseaqui. Não há muita diferença se você criará o commit durante a rebase ou antes.
precisa saber é o seguinte
Woops, eu quis dizer evitando o "corte de edição" durante o rebase, acho que expressei mal isso.
precisa saber é o seguinte
Certo. Minha resposta também não usa o recurso "editar" do rebase. Ainda assim, essa é outra abordagem válida - obrigado! :-)
BartoszKP
6

Muitas boas respostas aqui já. Eu só queria adicionar uma solução "no rebase", em 4 etapas fáceis.


Resumo

git checkout A
git commit -am "Message for commit D"
git cherry-pick A..C
git branch -f master HEAD

Explicação

(Nota: uma vantagem desta solução é que você não toca na sua ramificação até a etapa final, quando tem 100% de certeza de que está bem com o resultado final, para ter uma etapa muito útil de "pré-confirmação" permitindo testes AB .)


Estado inicial (assumi mastero nome do seu ramo)

A -- B -- C <<< master <<< HEAD

1) Comece apontando HEAD no lugar certo

git checkout A

     B -- C <<< master
    /
   A  <<< detached HEAD

(Opcionalmente aqui, em vez de desconectar o HEAD, poderíamos criar um ramo temporário com o git checkout -b temp Aqual precisaríamos excluir no final do processo. Ambas as variantes funcionam, fazem o que você prefere, já que todo o resto permanece o mesmo.


2) Crie o novo commit D a ser inserido

# at this point, make the changes you wanted to insert between A and B, then

git commit -am "Message for commit D"

     B -- C <<< master
    /
   A -- D <<< detached HEAD (or <<< temp <<< HEAD)

3) Em seguida, traga cópias dos últimos commits ausentes B e C (seria a mesma linha se houvesse mais commits)

git cherry-pick A..C

# (if any, resolve any potential conflicts between D and these last commits)

     B -- C <<< master
    /
   A -- D -- B' -- C' <<< detached HEAD (or <<< temp <<< HEAD)

(Teste AB confortável aqui, se necessário)

Agora é o momento de inspecionar seu código, testar qualquer coisa que precise ser testada e você também pode comparar / comparar / inspecionar o que tinha e o que obteria após as operações.


4) Dependendo dos testes entre Ce C', está OK ou está com KO.

(OU) 4-OK) Por fim, mova o ref demaster

git branch -f master HEAD

     B -- C <<< (B and C are candidates for garbage collection)
    /
   A -- D -- B' -- C' <<< master

(OU) 4-KO) Apenas deixe masterinalterado

Se você criou uma ramificação temporária, exclua-a com git branch -d <name> , mas se você optou pela rota HEAD desanexada, nenhuma ação será necessária neste momento, as novas confirmações serão elegíveis para a coleta de lixo logo após você reconectar HEADcom umgit checkout master

Nos dois casos (OK ou KO), neste momento, faça o checkout masternovamente para reconectar HEAD.

RomainValeri
fonte