Git: Como fazer uma nova recuperação para um commit específico?

154

Eu gostaria de mudar para um commit específico, não para um HEAD do outro ramo:

A --- B --- C          master
 \
  \-- D                topic

para

A --- B --- C          master
       \
        \-- D          topic

ao invés de

A --- B --- C          master
             \
              \-- D    topic

Como posso conseguir isso?

Ondra Žižka
fonte
4
Você já tentou fazer git checkout Bantes de executar git rebase?
Peter-Paul van Gemerden
Não, isso deve ajudar? Eu acho que apenas as referências do rebasecomando são o que importa.
Ondra Žižka

Respostas:

98

Você pode evitar o uso do parâmetro --onto criando uma ramificação temporária no commit que você gosta e, em seguida, use rebase na sua forma simples:

git branch temp master^
git checkout topic
git rebase temp
git branch -d temp
Adam Dymitruk
fonte
5
Eu gosto mais dessa abordagem do tipo RISC :) Tentarei. Obrigado.
Ondra Žižka
10
Eu me pergunto por que isso não funciona para mim, em um cenário um pouco diferente . Quero que o grupo pule o pep8 e seja baseado no mestre . git rebase temp(quando no grupo ) desiste de "Os grupos de ramificações atuais estão atualizados".
Alois Mahdal
4
Esta solução não funcionará para o cenário em que o tópico já foi rebaseado no mestre, mas você deseja transformá-lo em um ancestral para o mestre. Nesse caso, você deve usar git rebase --onto <target> <from> <to>para especificar o <from> commit.
mirzmaster
Essa é de longe a opção mais fácil se você quiser refazer a mesma confirmação na qual o ramo se baseia.
Ash
1
Parece que eu trabalho, mas o GitLab diz outra coisa. Se eu tivesse 10 commits atrasados, 5 commits à frente, minha expectativa era ter 8 commits atrasados, 5 commits à frente depois de receber 2 commits. Mas, em vez disso, ele adiciona mais commits aos 5. #
ROMANIA_engineer
67

Você pode até adotar uma abordagem direta:

git checkout topic
git rebase <commitB>
r0hitsharma
fonte
6
Para mim, isso realmente não faz o que se destina. Tanto quanto posso dizer, ele tenta se refazer no "último ancestral comum" de topice commitB.
Dan Lenski 26/01
2
@DanLenski, não é assim que a rebase funciona. Citando os documentos , It works by going to the common ancestor of the two branches (the one you’re on and the one you’re rebasing onto), getting the diff introduced by each commit of the branch you’re on, saving those diffs to temporary files, resetting the current branch to the same commit as the branch you are rebasing onto, and finally applying each change in turn. tentei novamente agora e parecia funcionar muito bem.
R0hitsharma
1
Super fácil e funciona! Agora tenho: commitB_from_master-> topicCommit1-> topicCommit2.
Martin Konicek
Não funcionou para mim. Antes disso, o GitLab disse "n se compromete à frente". E agora, diz "m compromete-se à frente" onde m > n.
ROMANIA_engineer
48

Use a opção "para":

git rebase --onto master^ D^ D
Adam Dymitruk
fonte
2
De D^seria hash do último e penúltimo commit de "topic"?
Ondra Žižka
39
A sintaxe é como git rebase --onto <new-parent> <old-parent>. Consulte Definindo o ponteiro do git parent para um pai diferente . No seu caso, <new-parent> é B e <old-parent> é A.
jsz 13/10/11
7
Eu sempre uso os 3 argumentos: desitnation, start e end of commits para rebase.
Adam Dymitruk 16/11/12
14
Isso funcionou para mim:git rebase --onto <commit-ID> master
4
O comentário de @ jsz está correto, ao contrário do comentário de Simon South, é o contrário: git rebase --onto master <commit-ID-of-old-parent>e para OP git rebase --onto B A.
gaborous
19

O comentário de jsz acima me salvou muito, então aqui está um destinatário passo a passo com base nele que eu tenho usado para rebase / mover qualquer commit sobre qualquer outro commit:

  1. Encontre um ponto de ramificação anterior da ramificação a ser reformulada (movida) - chame-a de pai antiga. No exemplo acima, isso é A
  2. Encontre o commit no qual você deseja mover o ramo - chame-o de novo pai. No exemplo que é B
  3. Você precisa estar no seu ramo (o que você move):
  4. Aplique sua rebase: git rebase --onto <new parent> <old parent>

No exemplo acima, é tão simples quanto:

   git checkout topic
   git rebase --onto B A
Nestor Milyaev
fonte
6
Essa deve ser a resposta correta. Exceto que eu uso git rebase --onto B master, veja minha resposta para uma explicação mais completa.
Zack Morris
Não funcionou bem para mim. Escolhi 2 confirmações consecutivas (a última do mestre que estava na ramificação atual e a primeira do mestre que não estava na ramificação atual). Comecei com 100 atrás - 10 à frente e, em vez de ter 99 atrás - 10 à frente, agora tenho 105 atrás - 13 à frente.
ROMANIA_engineer
Lamento saber que não funcionou. Parece que seus galhos divergiram bastante - sugiro esmagar antes de tentar refazer os galhos com tantas diferenças.
Nestor Milyaev 01/05/19
11

Tópico Solução

O comando correto para responder à pergunta postada pode ser um dos seguintes (supondo que a ramificação topicjá esteja com check-out):

git rebase --onto B master
git rebase --onto master~1 master
git rebase --onto B A
git rebase --onto B C
git rebase --onto B

Se topicnão estiver marcado, basta anexar topicao comando (exceto o último) da seguinte forma:

git rebase --onto B master topic

Como alternativa, verifique primeiro o ramo com:

git checkout topic

Rebase qualquer sequência de confirmações para uma confirmação de destino

A forma básica do comando que precisamos, extraída da documentação, é:

git rebase --onto <Target> [<Upstream> [<Branch>]]

<Branch>é opcional e tudo o que faz é fazer check-out da ramificação especificada antes de executar o restante do comando. Se você já fez check-out da ramificação que deseja refazer a recuperação, não precisa disso. Observe que você deve ter especificado <Upstream>para especificar <Branch>ou o git pensará que você está especificando<Upstream> .

<Target>é o commit ao qual anexaremos nossa cadeia de commits. Ao fornecer um nome de filial, você está simplesmente especificando o commit principal dessa filial. <Target>pode ser qualquer confirmação que não esteja contida na cadeia de confirmações que está sendo movida. Por exemplo:

A --- B --- C --- D         master
      \
       \-- X --- Y --- Z    feature

Para mover todo o ramo de funcionalidade, você não pode selecionar X, Y, Z, ou featurecomo o<Target> desde os todos são commits dentro do grupo que está sendo movido.

<Upstream>é especial porque pode significar duas coisas diferentes. Se for um commit que é um ancestral do ramo com check-out, ele servirá como ponto de corte. No exemplo que eu forneci, isso seria qualquer coisa que não é C, Dou master. Todas as confirmações depois <Upstream>até o chefe da ramificação com check-out são as que serão movidas.

No entanto, se <Upstream>não for um ancestral, o git fará o backup da cadeia a partir do commit especificado até que encontre um ancestral comum com o ramo com check-out (e interrompa se não conseguir encontrar um). No nosso caso, um <Upstream>dos B, C, D, ou mastertudo vai resultar em comprometer Bservindo como ponto de corte. <Upstream>é ele próprio um comando opcional e, se não for especificado, o git examinará o pai do ramo com check-out equivalente à entradamaster .

Agora que o git selecionou os commits que serão cortados e movidos, aplica-os para <Target>, ignorando os que já estão aplicados ao alvo.

Exemplos e resultados interessantes

Usando este ponto de partida:

A --- B --- C --- D --- E         master
            \
             \-- X --- Y --- Z    feature
  • git rebase --onto D A feature
    Será aplicada commits B, C, X, Y, Zpara cometer De acabam pulando Be Cporque eles já foram aplicadas.

  • git rebase --onto C X feature
    Aplicará confirmações Ye Zconfirmações C, excluindo efetivamente as confirmaçõesX

Isaac Brown
fonte
4

Uma solução mais simples é git rebase <SHA1 of B> topic. Isso funciona independentemente de onde você HEADestiver.

Podemos confirmar esse comportamento no git rebase doc

<upstream>Ramo a montante para comparar. Pode ser qualquer confirmação válida , não apenas um nome de filial existente. O padrão é o upstream configurado para a ramificação atual.


Você pode estar pensando o que acontecerá se eu mencionar SHA1 topictambém no comando acima?

git rebase <SHA1 of B> <SHA1 of topic>

Isso também funcionará, mas a reorganização não fará Topicsentido para uma nova ramificação criada e HEADestará no estado desanexado. Portanto, a partir daqui, você deve excluir manualmente o antigo Topice criar uma nova referência de ramificação em uma nova ramificação criada por rebase.

Número945
fonte
3

Eu usei uma mistura de soluções descritas acima:

$ git branch temp <specific sha1>
$ git rebase --onto temp master topic
$ git branch -d temp

Achei muito mais fácil ler e entender. A solução aceita levou-me a um conflito de mesclagem (com preguiça de corrigir manualmente):

$ git rebase temp
First, rewinding head to replay your work on top of it...
Applying: <git comment>
Using index info to reconstruct a base tree...
M       pom.xml
.git/rebase-apply/patch:10: trailing whitespace.
    <some code>
.git/rebase-apply/patch:17: trailing whitespace.
        <some other code>
warning: 2 lines add whitespace errors.
Falling back to patching base and 3-way merge...
Auto-merging pom.xml
CONFLICT (content): Merge conflict in pom.xml
error: Failed to merge in the changes.
Patch failed at 0001 <git comment>
The copy of the patch that failed is found in: .git/rebase-apply/patch

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
malat
fonte
1
mesmo aqui, vários arquivos tinha conflitos quando eu usei os 2 a maioria das respostas populares (ie por r0hitsharma e Dymitruk)
Oliver
3

Como o rebaseamento é tão fundamental, aqui está uma expansão da resposta de Nestor Milyaev . A combinação dos comentários de jsz e Simon South da resposta de Adam Dymitruk produz esse comando que funciona no topicramo, independentemente de ele se masterramificar no commit do ramo Aou C:

git checkout topic
git rebase --onto <commit-B> <pre-rebase-A-or-post-rebase-C-or-base-branch-name>

Observe que o último argumento é necessário (caso contrário, ele rebobina sua ramificação para confirmar B).

Exemplos:

# if topic branches from master commit A:
git checkout topic
git rebase --onto <commit-B> <commit-A>
# if topic branches from master commit C:
git checkout topic
git rebase --onto <commit-B> <commit-C>
# regardless of whether topic branches from master commit A or C:
git checkout topic
git rebase --onto <commit-B> master

Portanto, o último comando é o que eu normalmente uso.

Zack Morris
fonte
-2

Existe outra maneira de fazer isso ou se você deseja voltar para mais do que apenas um commit.

Aqui está um exemplo para retornar ao nnúmero de confirmações:

git branch topic master~n

Para o bem desta pergunta, isso também pode ser feito:

git branch topic master~1

O comando funciona perfeitamente git version 2.7.4. Não testei em nenhuma outra versão.

Talha Ashraf
fonte
Você interpretou mal isso como uma pergunta sobre ramificação? Esta é realmente uma pergunta sobre rebasing.
NetherGranite 12/10