Git cherry pick vs rebase

120

Recentemente comecei a trabalhar com o Git.

Examinando o livro Git online, encontrei o seguinte na seção "Git Rebase":

Com o comando rebase, você pode pegar todas as mudanças que foram confirmadas em uma ramificação e reproduzi-las em outra.

(Citado em: http://git-scm.com/book/en/Git-Branching-Rebasing )

Eu pensei que esta é a definição exata de git cherry-pick (reaplique um commit ou um conjunto de objetos de commit no branch atualmente verificado).

Qual é a diferença entre os dois ?

ácido lisérgico
fonte

Respostas:

166

Desde o tempo git cherry-pickaprendeu a ser capaz de aplicar vários commits, a distinção de fato se tornou um tanto discutível, mas isso é algo a ser chamado de evolução convergente ;-)

A verdadeira distinção reside na intenção original de criar as duas ferramentas:

  • git rebaseA tarefa de é encaminhar uma série de mudanças que um desenvolvedor possui em seu repositório privado, criado contra a versão X de algum branch upstream, para a versão Y desse mesmo branch (Y> X). Isso efetivamente muda a base daquela série de commits, daí o "rebasing".

    (Também permite que o desenvolvedor transplante uma série de commits para qualquer commit arbitrário, mas isso é de uso menos óbvio.)

  • git cherry-pické para trazer um commit interessante de uma linha de desenvolvimento para outra. Um exemplo clássico é o backport de uma correção de segurança feita em um branch de desenvolvimento instável para um branch estável (manutenção), onde um mergenão faz sentido, pois traria uma série de alterações indesejadas.

    Desde sua primeira aparição, git cherry-picktem sido capaz de escolher vários commits de uma vez, um por um.

Portanto, possivelmente a diferença mais marcante entre esses dois comandos é como eles tratam o branch em que trabalham: git cherry-picknormalmente traz um commit de algum outro lugar e o aplica no topo do seu branch atual, gravando um novo commit, enquanto git rebasepega seu branch atual e reescreve uma série de sua própria dica compromete de uma forma ou de outra. Sim, esta é uma descrição bastante simplificada do que git rebasepodemos fazer, mas é intencional, para tentar fazer com que a ideia geral seja absorvida.

Atualize para explicar melhor um exemplo de uso que git rebaseestá sendo discutido.

Dada esta situação,
um estado do repo antes de rebasing
o livro afirma:

No entanto, há outra maneira: você pode pegar o patch da mudança que foi introduzida em C3 e reaplicá-lo em cima de C4. No Git, isso é chamado de rebase. Com o comando rebase, você pode pegar todas as alterações que foram confirmadas em um branch e aplicá-las em outro.

Neste exemplo, você executaria o seguinte:

$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command

"O problema" aqui é que, neste exemplo, o branch "experimento" (o assunto para rebase) foi originalmente bifurcado do branch "mestre" e, portanto, compartilha commits de C0 a C2 com ele - efetivamente, "experimento" é " master "até, e incluindo, C2 mais commit C3 em cima dele. (Este é o caso mais simples possível; claro, "experimento" pode conter várias dezenas de commits em cima de sua base original.)

Agora git rebaseé instruído a realocar "experimento" na ponta atual do "mestre" e funciona git rebaseassim:

  1. Executa git merge-basepara ver qual é o último commit compartilhado por "experimento" e "mestre" (qual é o ponto de desvio, em outras palavras). Este é C2.
  2. Salva todos os commits feitos desde o ponto de desvio; em nosso exemplo de brinquedo, é apenas C3.
  3. Rebobina o HEAD (que aponta para a confirmação da ponta do "experimento" antes que a operação comece a ser executada) para apontar para a ponta do "mestre" - estamos fazendo o rebaseamento nele.
  4. Tenta aplicar cada um dos commits salvos (como se com git apply) em ordem. Em nosso exemplo de brinquedo, é apenas um commit, C3. Digamos que seu aplicativo produza um commit C3 '.
  5. Se tudo correr bem, a referência "experimento" é atualizada para apontar para o commit resultante da aplicação do último commit salvo (C3 'em nosso caso).

Agora, de volta à sua pergunta. Como você pode ver, aqui, tecnicamente, de git rebase fato, transplanta uma série de commits de "experimento" para a ponta de "mestre", então você pode dizer com razão que de fato há "outro ramo" no processo. Mas a essência é que o commit de ponta de "experimento" acabou sendo o novo commit de ponta em "experimento", apenas mudou sua base:
estado após a fusão

Novamente, tecnicamente você pode dizer que git rebaseaqui estão incorporados certos commits do "master", e isso é absolutamente correto.

Kostix
fonte
2
Obrigado. Eu ainda não entendi totalmente o que você quer dizer aqui. No livro, é dado o exemplo de que rebase aplica uma série de commits de ponta de outro branch, enquanto você diz que é do "mesmo branch". Ou talvez haja alguns casos de como isso funciona?
ácido lisérgico de
1
Tentei explicar o assunto atualizando minha resposta.
kostix
98

Com a escolha certa, os commits / branch originais permanecem e novos commits são criados. Com o rebase, todo o branch é movido com o branch apontando para os commits reproduzidos.

Digamos que você começou com:

      A---B---C topic
     /
D---E---F---G master

Rebase:

$ git rebase master topic

Você obtém:

              A'--B'--C' topic
             /
D---E---F---G master

Colher cerejas:

$ git checkout master -b topic_new
$ git cherry-pick A^..C

Você obtém:

      A---B---C topic
     /
D---E---F---G master
             \
              A'--B'--C' topic_new

para mais informações sobre git, este livro tem a maior parte (http://git-scm.com/book)

Kenny Ho
fonte
3
Bem respondido. Também é comum que você queira selecionar apenas os commits de A e B, mas deixar C pendurado nos casos em que você pode querer manter o branch e apenas escolher as mudanças que os colegas podem precisar ver. O Git foi feito para funcionar com pessoas, então, se você não vê os benefícios de algo ao trabalhar sozinho, é mais comumente usado ao trabalhar em grupos maiores.
Pablo Jomer
Se um rebase interativo fosse feito, deixando de fora um ou mais commits, quais branches você teria no final? se fosse apenas topicrebaseado no topo master, ele não contém os commits deixados de fora, então de qual branch eles farão parte?
Anthony
Só mais uma coisa que quero acrescentar: se você git checkout topice git reset --hard C'depois da colheita seletiva, você terá o mesmo resultado que após a rebase. Eu me salvei de muitos conflitos de mesclagem usando a escolha seletiva ao invés de rebase, porque o ancestral comum estava há muito tempo.
sorrymissjackson
@anthony - stackoverflow.com/questions/11835948/… : até onde eu entendo, eles estão perdidos. Eu não sou nenhum git-guru mas esta rebase/ cherry-pické em todos os detalhes com gitque eu tinha uma compreensão problema.
thoni56
1
Seus gráficos fazem mais mal do que bem, porque são funcionalmente idênticos. A única diferença é o ramo criado por git checkout -b, que não tem nada a ver com git cherry-pick. Uma maneira melhor de explicar o que você está tentando dizer seria “você corre git rebaseno topicgalho e passa master; você executa git cherry-pickno masterbranch e passa (commits de) topic. ”
Rory O'Kane de
14

A seleção seletiva funciona para commits individuais .

Quando você faz rebase, ele aplica todos os commits do histórico ao HEAD do branch que está faltando lá.

iltempo
fonte
Obrigado. Você sabe se eles funcionam da mesma forma sob as tampas? (armazene suas saídas intermediárias em arquivos de "patch", etc).
ácido lisérgico de
Afaik sim. Ele aplica todos os patches um por um. Essa é a razão pela qual às vezes você precisa resolver conflitos de mesclagem no meio de um rebase antes de continuar.
iltempo
6
@iltempo, funcionava para commits individuais apenas em versões anteriores do Git; no momento você pode fazer algo como git cherry-pick foo~3..fooobter os commits do topo da árvore de "foo" escolhidos um por um.
kostix
1
git-rebase usa a mesma api que a seleção seletiva usa na base de código, iirc
alternativa
Eu não acho que eles realmente funcionam da mesma forma sob as cobertas. Eu tentei rebasear milhares de commits e acho que o git cria um enorme arquivo de caixa de correio e depois executa git amnele. Considerando que uma escolha certa aplica commit por commit (possivelmente criando uma caixa de correio de mensagem única para cada patch). Meu rebase estava falhando porque o arquivo de caixa de correio que estava criando ficou sem espaço na unidade, mas a escolha seletiva com o mesmo intervalo de revisão funcionou (e parece funcionar mais rápido).
apenas em
11

Uma resposta curta:

  • git cherry-pick é mais "baixo nível"
  • Como tal, pode emular rebase git

As respostas dadas acima são boas, eu só queria dar um exemplo na tentativa de demonstrar sua inter-relação.

Não é recomendado substituir "git rebase" por esta sequência de ações, é apenas uma "prova de conceito" que, espero, ajuda a entender como as coisas funcionam.

Dado o seguinte repositório de brinquedos:

$ git log --graph --decorate --all --oneline
* 558be99 (test_branch_1) Test commit #7
* 21883bb Test commit #6
| * 7254931 (HEAD -> master) Test commit #5
| * 79fd6cb Test commit #4
| * 48c9b78 Test commit #3
| * da8a50f Test commit #2
|/
* f2fa606 Test commit #1

Digamos que temos algumas mudanças muito importantes (commits # 2 a # 5) no master que queremos incluir em nosso test_branch_1. Normalmente, apenas mudamos para um branch e fazemos "git rebase master". Mas como estamos fingindo que estamos equipados apenas com o "git cherry-pick", fazemos:

$ git checkout 7254931                # Switch to master (7254931 <-- master <-- HEAD)
$ git cherry-pick 21883bb^..558be99   # Apply a range of commits (first commit is included, hence "^")    

Depois de todas essas operações, nosso gráfico de confirmação ficará assim:

* dd0d3b4 (HEAD) Test commit #7
* 8ccc132 Test commit #6
* 7254931 (master) Test commit #5
* 79fd6cb Test commit #4
* 48c9b78 Test commit #3
* da8a50f Test commit #2
| * 558be99 (test_branch_1) Test commit #7
| * 21883bb Test commit #6
|/
* f2fa606 Test commit #1

Como podemos ver, os commits # 6 e # 7 foram aplicados contra 7254931 (um commit de ponta do master). HEAD foi movido e aponta um commit que é, essencialmente, uma ponta de um branch rebased. Agora, tudo o que precisamos fazer é excluir um ponteiro de branch antigo e criar um novo:

$ git branch -D test_branch_1
$ git checkout -b test_branch_1 dd0d3b4

test_branch_1 agora está enraizado na última posição mestre. Feito!

Raiks
fonte
Mas o rebase também pode simular o git cherry-pick?
945
Já que cherry-pické capaz de aplicar uma série de commits, acho que sim. Embora esta seja uma maneira um pouco estranha de fazer as coisas, nada impede que você selecione todos os commits em seu branch de recursos e master, em seguida, exclua o branch de recursos e recrie-o de forma que aponte para a ponta de master. Você pode pensar git rebasea partir de uma sequência de git cherry-pick feature_branch, git branch -d feature_branche git branch feature_branch master.
raiks
7

Ambos são comandos para reescrever os commits de um branch em cima do outro: a diferença está em qual branch - "seu" (o atualmente em check-out HEAD) ou "deles" (o branch passado como um argumento para o comando) - é a base para esta reescrita.

git rebasepega um commit inicial e repete seus commits como se fossem os deles (o commit inicial).

git cherry-pickpega um conjunto de commits e reproduz os commits deles como vindo depois do seu (seu HEAD).

Em outras palavras, os dois comandos são, em seu comportamento central (ignorando suas características de desempenho divergentes, convenções de chamada e opções de aprimoramento), simétricos : fazer check-out do branch bare executar git rebase foodefine o barbranch com o mesmo histórico que o check-out do branch fooe a execução git cherry-pick ..bardefiniria foopara (as mudanças de foo, seguidas pelas mudanças de bar).

Em termos de nomenclatura, a diferença entre os dois comandos pode ser lembrada em que cada um descreve o que faz ao branch atual : rebasetorna o outro chefe a nova base para suas alterações, enquanto cherry-pickescolhe as alterações do outro branch e as coloca no topo seuHEAD (como cerejas em cima de um sundae).

Stuart P. Bentley
fonte
1
Não consegui entender nenhuma das respostas, exceto a sua! É conciso e faz todo o sentido, sem palavras supérfluas.
neoxic
4

Ambos fazem coisas muito semelhantes; a principal diferença conceitual é (em termos simplificados) que:

  • rebase move os commits do branch atual para outro branch .

  • as cópias selecionadas para commits de outro branch para o branch atual .

Usando diagramas semelhantes à resposta de @Kenny Ho :

Dado este estado inicial:

A---B---C---D master
     \
      E---F---G topic

... e assumindo que você deseja que os commits do topicbranch sejam reproduzidos no topo do masterbranch atual , você tem duas opções:

  1. Usando rebase: primeiro, você iria para topicfazendo git checkout topice, em seguida, moveria o branch executando git rebase master, produzindo:

    A---B---C---D master
                 \
                  E'---F'---G' topic
    

    Resultado: seu branch atual topicfoi realocado (movido) para master.
    A topicfilial foi atualizada, enquanto a masterfilial permaneceu no local.

  2. Usando a escolha seletiva : primeiro você iria para masterfazendo git checkout mastere, em seguida, copiasse o branch executando git cherry-pick topic~3..topic(ou, equivalentemente, git cherry-pick B..G), produzindo:

    A---B---C---D---E'---F'---G' master
         \
          E---F---G topic
    

    Resultado: os commits de topicforam copiados para master.
    A masterfilial foi atualizada, enquanto a topicfilial permaneceu no local.


Claro, aqui você teve que dizer explicitamente a pick-pick para escolher uma sequência de commits , usando a notação de intervalo foo..bar . Se você simplesmente tivesse passado o nome do branch, como em git cherry-pick topic, ele teria obtido apenas o commit na ponta do branch, resultando em:

A---B---C---D---G' master
     \
      E---F---G topic
waldyrious
fonte