Reordenação de commits

104

Atualmente estou trabalhando em um branch e quero que alguns commits se fundam em outros branches:

    a-b-c-d-e-f-g (branchA)
   /
--o-x-x-x-x-x-x-x-x-x-x (master)
   \
    x-x-x-x-x (branchB)

(As letras denotam confirmações e o "x" são confirmações irrelevantes.)

No entanto, percebi que seria uma boa ideia agrupar alguns commits. Eu quero "concatenar" o commit de a, d, e e g em um patch e enviá-lo ao master. Os commits b e f devem ir como um commit para o branchB. Existe uma boa maneira 'git'-ish de conseguir isso?

Peter
fonte
4
As duas respostas já fornecidas mencionam o comando correto, mas acho que com uma tarefa tão básica como esta, vale a pena ter instruções reais aqui no StackOverflow, então postei algumas. (Estou surpreso por não conseguir encontrar uma boa pergunta anterior para vincular.)
Cascabel

Respostas:

125

O comando que você está procurando é git rebase, especificamente a -i/--interactiveopção.

Vou assumir que você deseja deixar o commit c no branch A, e que você realmente quer mover os outros commits para os outros branches, ao invés de fazer a fusão, uma vez que as fusões são diretas. Vamos começar manipulando o branch A.

git rebase -i <SHA1 of commit a>^ branchA

Os ^meios cometem o anterior, então este comando diz para rebase ramo Um usando o commit antes "a" como a base. O Git apresentará uma lista de commits neste intervalo. Reordene-os e diga ao git para esmagar os apropriados:

pick c ...
pick a ...
squash d ...
squash e ...
squash g ...
pick b
squash f

Agora, a história deve ser semelhante a esta:

    c - [a+d+e+g] - [b+f] (branchA)
   /
--o-x-x-x-x-x-x-x-x-x-x (master)

Agora, vamos pegar o commit recém-comprimido b + f para branchB.

git checkout branchB
git cherry-pick branchA  # cherry-pick one commit, the tip of branchA

E o mesmo para a + d + e + g para mestre:

git checkout master
git cherry-pick branchA^

Finalmente, atualize branchA para que aponte para c:

git branch -f branchA branchA^^

Devemos agora ter:

    c (branch A) - [a+d+e+g] - [b+f] (dangling commits)
   /
--o-x-x-x-x-x-x-x-x-x-x-[a+d+e+g] (master)
   \
    x-x-x-x-x-[b+f] (branchB)

Observe que se você tiver vários commits que deseja mover entre branches, poderá usar o rebase novamente (não interativamente):

# create a temporary branch
git branch fromAtoB branchA
# move branchA back two commits
git branch -f branchA branchA~2
# rebase those two commits onto branchB
git rebase --onto branchB branchA fromAtoB
# merge (fast-forward) these into branchB
git checkout branchB
git merge fromAtoB
# clean up
git branch -d fromAtoB

Finalmente, uma isenção de responsabilidade: é bem possível reordenar os commits de forma que alguns não se apliquem mais corretamente. Isso pode ser porque você escolheu uma ordem incorreta (colocar um patch antes do commit introduzindo o recurso que ele corrigiu); nesse caso, você deseja abortar o rebase ( git rebase --abort). Caso contrário, você terá que consertar os conflitos de forma inteligente (assim como faz com os conflitos de mesclagem), adicionar as correções e, em seguida, correr git rebase --continuepara seguir em frente. Essas instruções também são fornecidas pela mensagem de erro impressa quando ocorre o conflito.

Cascabel
fonte
não é o diagrama depois do git branch -f branchA branchA^^errado? A saber, o branch A não deveria estar apontando para c neste ponto, de modo que a primeira linha do diagrama está c (branchA)e não c - [a+d+e+g] - [b+f] (branchA)?
ntc2
@enoksrd: Sim, há um erro, mas sua edição contém erros. Vou consertar quando tiver uma chance aqui.
Cascabel
Em vez de fazer, git branch -f branchA branchA^^por que você não pode simplesmente fazer um git reset --hard <sha1 of c>no ramo A?
Mikhail Vasilyev
17
git rebase -i HEAD~3

Onde 3está o número de commits que precisam ser reordenados ( fonte ).

Isso abrirá o vi , listando os commits do mais antigo (topo) ao mais recente (parte inferior).
Agora reordene as linhas, salve e saia do editor.

ddpirá mover a linha atual para baixo
ddkP irá mover a linha atual para cima ( fonte )

Nelu
fonte
9

git rebase --interactive é o comando que você deseja.

Exemplo:

O status atual é este:

bernt@le3180:~/src/stackoverflow/reordering_of_commits
$ git status
On branch master
nothing to commit, working tree clean
bernt@le3180:~/src/stackoverflow/reordering_of_commits
$ git log --oneline
a6e3c6a (HEAD -> master) Fourth commit
9a24b81 Third commit
7bdfb68 Second commit
186d1e0 First commit

Eu quero reordenar os commits 9a24b81(Terceiro commit) e 7bdfb68(Segundo commit). Para fazer isso, primeiro encontro o commit antes do primeiro commit que queremos alterar . Este é o commit 186d1e0(primeiro commit).

O comando a ser executado é git rebase --interactive COMMIT-BEFORE-FIRST-COMMIT-WE-WANT-TO-CHANGE, neste caso:

bernt@le3180:~/src/stackoverflow/reordering_of_commits
$ git rebase --interactive 186d1e0

Isso abre um arquivo no editor padrão com o seguinte conteúdo:

pick 7bdfb68 Second commit
pick 9a24b81 Third commit
pick a6e3c6a Fourth commit

# Rebase 186d1e0..a6e3c6a onto 186d1e0 (3 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#

Observe que a ordem dos commits no arquivo é oposta àquela usada para o git log. No git log, o commit mais recente está no topo. Neste arquivo, o commit mais recente está na parte inferior.

Como o comentário na parte inferior do arquivo explica, há várias coisas que posso fazer, como suprimir, descartar e reordenar commits. Para reordenar os commits, edito o arquivo de forma que fique assim (os comentários da parte inferior não são exibidos):

pick 9a24b81 Third commit
pick 7bdfb68 Second commit
pick a6e3c6a Fourth commit

O pickcomando no início de cada linha significa "use (ou seja, inclua) este commit" e quando eu salvar o arquivo temporário com os comandos rebase e sair do editor, o git executará os comandos e atualizará o repositório e o diretório de trabalho:

$ git rebase --interactive 186d1e0
Successfully rebased and updated refs/heads/master.
bernt@le3180:~/src/stackoverflow/reordering_of_commits
$ git log --oneline
ba48fc9 (HEAD -> master) Fourth commit
119639c Second commit
9716581 Third commit
186d1e0 First commit

Observe o histórico de commits reescrito.

Links:

codeape
fonte
1
Adicione detalhes relevantes de seu link para fornecer uma resposta completa e independente. SO franze a testa em "respostas apenas com link". Especificamente, como alguém usaria git rebase --interactivepara atingir a meta do OP?
SherylHohman
O segundo link não existe agora.
devsaw de
Conteúdo adicionado para tornar a resposta completa e independente. Segundo link removido.
codeape
-1

git rebase é o que você deseja. Verifique a opção --interactive.


fonte
4
Adicione detalhes relevantes de seu link para fornecer uma resposta completa e independente. SO franze a testa em "respostas apenas com link".
SherylHohman