git-branch lista - Mover a(s)confirmação(ões)mais recente(s)para uma nova ramificação com o Git




de branches (10)

Para fazer isso sem reescrever o histórico (ou seja, se você já enviou os commits):

git checkout master
git revert <commitID(s)>
git checkout -b new-branch
git cherry-pick <commitID(s)>

Ambos os ramos podem ser empurrados sem força!

Eu gostaria de mover os últimos commits que eu cometi para masterizar em um novo branch e levar o master de volta para antes dos commits serem feitos. Infelizmente, meu Git-fu ainda não é forte o suficiente, alguma ajuda?

Ou seja, como eu posso ir disso

master A - B - C - D - E

para isso?

newbranch     C - D - E
             /
master A - B 

A maioria das respostas anteriores está perigosamente errada!

Não faça isso:

git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch

Como na próxima vez que você rodar o git rebase (ou git pull --rebase ), esses 3 commits serão silenciosamente descartados do newbranch ! (veja a explicação abaixo)

Em vez disso, faça isso:

git reset --keep HEAD~3
git checkout -t -b newbranch
git cherry-pick [email protected]{2}
  • Primeiro, ele descarta os 3 commits mais recentes ( --keep é como --hard , mas é mais seguro, pois falha ao invés de descartar as alterações não confirmadas).
  • Então bifurca-se de newbranch .
  • Então ele escolhe aqueles 3 commits de volta para newbranch . Como eles não são mais referenciados por um branch, ele faz isso usando o reflog do git: [email protected]{2} é o commit que o HEAD usou para se referir a 2 operações atrás, ie antes de nós 1. check out newbranch e 2. used git reset para descartar os 3 commits.

Atenção: o reflog é habilitado por padrão, mas se você o tiver desativado manualmente (por exemplo, usando um repositório git "nu"), você não poderá recuperar os 3 commits após executar o git reset --keep HEAD~3

Uma alternativa que não depende do reflog é:

# newbranch will omit the 3 most recent commits.
git checkout -b newbranch HEAD~3
git branch --set-upstream-to=oldbranch
# Cherry-picks the extra commits from oldbranch.
git cherry-pick ..oldbranch
# Discards the 3 most recent commits from oldbranch.
git branch --force oldbranch oldbranch~3

(se preferir, você pode escrever @{-1} - a ramificação anteriormente retirada - em vez de oldbranch ).

Explicação técnica

Por que o git rebase descartaria os 3 commits após o primeiro exemplo? É porque o git rebase sem argumentos habilita a opção --fork-point por padrão, que usa o reflog local para tentar ser robusto contra o branch upstream sendo forçado.

Suponha que você se ramificou de origem / mestre quando continha commits M1, M2, M3, e depois fez três commits:

M1--M2--M3  <-- origin/master
         \
          T1--T2--T3  <-- topic

mas então alguém reescreve a história empurrando origem / mestre para remover M2:

M1--M3'  <-- origin/master
 \
  M2--M3--T1--T2--T3  <-- topic

Usando seu reflog local, o gase git rebase pode ver que você foi bifurcado a partir de uma encarnação anterior da origem / ramificação principal e, portanto, que os commits M2 e M3 não são realmente parte de sua ramificação de tópico. Por isso, é razoável supor que, uma vez que o M2 foi removido do branch upstream, você não o quer mais em sua ramificação de tópico, uma vez que o branch de tópicos é rebaseado:

M1--M3'  <-- origin/master
     \
      T1'--T2'--T3'  <-- topic (rebased)

Esse comportamento faz sentido e geralmente é a coisa certa a fazer quando se está rebaixando.

Então, a razão pela qual os seguintes comandos falham:

git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch

é porque eles deixam o reflog no estado errado. Git vê newbranch como tendo bifurcado o branch upstream em uma revisão que inclui os 3 commits, então o reset --hard reescreve o histórico do upstream para remover os commits, e então da próxima vez que você rodar git rebase ele os descartará como qualquer outro commit que foi removido do upstream.

Mas neste caso particular, queremos que esses 3 commits sejam considerados como parte do branch tópico. Para conseguir isso, precisamos extrair o upstream na revisão anterior que não inclui os 3 commits. Isso é o que minhas soluções sugeridas fazem, portanto, elas deixam o reflog no estado correto.

Para mais detalhes, veja a definição de --fork-point nos documentos git rebase e git merge-base .


Você pode fazer isso é apenas 3 simples passo que eu usei.

1) faça uma nova ramificação onde você deseja enviar sua atualização recente.

git branch <branch name>

2) Encontrar o ID de confirmação recente para confirmar no novo ramo.

git log

3) Copie essa nota de ID de commit que a lista de commits Most Recent ocorre no topo. então você pode encontrar o seu commit. você também encontra isso via mensagem.

git cherry-pick d34bcef232f6c...

você também pode fornecer alguns toques de id de confirmação.

git cherry-pick d34bcef...86d2aec

Agora seu trabalho feito. Se você escolheu o ID correto e o ramo correto, você terá sucesso. Então, antes disso, tenha cuidado. mais outro problema pode ocorrer.

Agora você pode empurrar seu código

git push


Isso não "move" eles no sentido técnico, mas tem o mesmo efeito:

A--B--C  (branch-foo)
 \    ^-- I wanted them here!
  \
   D--E--F--G  (branch-bar)
      ^--^--^-- Opps wrong branch!

While on branch-bar:
$ git reset --hard D # remember the SHAs for E, F, G (or E and G for a range)

A--B--C  (branch-foo)
 \
  \
   D-(E--F--G) detached
   ^-- (branch-bar)

Switch to branch-foo
$ git cherry-pick E..G

A--B--C--E'--F'--G' (branch-foo)
 \   E--F--G detached (This can be ignored)
  \ /
   D--H--I (branch-bar)

Now you won't need to worry about the detached branch because it is basically
like they are in the trash can waiting for the day it gets garbage collected.
Eventually some time in the far future it will look like:

A--B--C--E'--F'--G'--L--M--N--... (branch-foo)
 \
  \
   D--H--I--J--K--.... (branch-bar)

Solução muito mais simples usando o git stash

E se:

  • Seu objetivo principal é reverter o master e
  • Você quer manter as alterações, mas não se importa particularmente com os commits individuais, e
  • Você ainda não empurrou e
  • Você quer que isso seja fácil e não complicado com ramos temporários e outras dores de cabeça

Então, o seguinte é muito mais simples (iniciando no branch master que possui três commits errados):

git reset HEAD~3
git stash
git checkout newbranch
git stash pop

O que isto faz, por número de linha

  1. Desfaz os últimos três commits (e suas mensagens) para master , mas deixa todos os arquivos de trabalho intactos
  2. Armazena todas as alterações do arquivo de trabalho, tornando a árvore de trabalho master igual ao estado HEAD ~ 3
  3. Muda para um branch newbranch existente
  4. Aplica as alterações ocultas ao seu diretório de trabalho e limpa o estoque

Agora você pode usar git add e git commit como faria normalmente. Todos os novos commits serão adicionados ao newbranch .

O que isso não faz

  • Não deixa galhos temporários aleatórios bagunçando sua árvore
  • Ele não preserva os commits e commit das mensagens, então você precisa adicionar uma nova mensagem de commit a este novo commit

Objetivos

O OP declarou que o objetivo era "levar o mestre de volta a antes que os commits fossem feitos" sem perder as alterações e essa solução faz isso.

Eu faço isso pelo menos uma vez por semana quando eu acidentalmente faço novos commits para master ao invés de develop . Normalmente eu tenho apenas um commit para rollback, em cujo caso usar o git reset HEAD^ na linha 1 é uma forma mais simples de reverter apenas um commit.

Não faça isso se você forçar as mudanças do mestre a montante

Alguém pode ter puxado essas mudanças. Se você está apenas reescrevendo seu mestre local, não há impacto quando ele é empurrado para cima, mas empurrar um histórico reescrito para os colaboradores pode causar dores de cabeça.


Movendo-se para um novo ramo

AVISO: Este método funciona porque você está criando uma nova ramificação com o primeiro comando: git branch newbranch . Se você quiser mover confirmações para uma ramificação existente, precisará mesclar suas alterações na ramificação existente antes de executar a git reset --hard HEAD~3 (consulte Movendo para uma ramificação existente abaixo). Se você não mesclar suas alterações primeiro, elas serão perdidas.

A menos que haja outras circunstâncias envolvidas, isso pode ser feito facilmente, ramificando e retrocedendo.

# Note: Any changes not committed will be lost.
git branch newbranch      # Create a new branch, saving the desired commits
git reset --hard HEAD~3   # Move master back by 3 commits (GONE from master)
git checkout newbranch    # Go to the new branch that still has the desired commits

Mas tenha certeza de quantos commits voltar. Alternativamente, você pode, em vez de HEAD~3 , simplesmente fornecer o hash do commit (ou a referência como origem / master ) que você quer "reverter" para o branch master (/ current), por exemplo:

git reset --hard a1b2c3d4

* 1 Você estará "perdendo" commits da ramificação master, mas não se preocupe, você terá aqueles commits no newbranch!

ATENÇÃO: Com o Git versão 2.0 e posterior, se você posteriormente git rebase a nova ramificação na ramificação original ( master ), você pode precisar de uma opção explícita --no-fork-point durante o rebase para evitar perder as confirmações transportadas. Ter branch.autosetuprebase always definido torna isso mais provável. Veja a resposta de John Mellor para detalhes.

Movendo-se para um ramo existente

Se você quiser mover seus commits para uma ramificação existente , ficará assim:

git checkout existingbranch
git merge master
git checkout master
git reset --hard HEAD~3 # Go back 3 commits. You *will* lose uncommitted work.
git checkout existingbranch

Ainda outra maneira de fazer isso, usando apenas 2 comandos. Também mantém sua árvore de trabalho atual intacta.

git checkout -b newbranch # switch to a new branch
git branch -f master HEAD~3 # make master point to some older commit

Versão antiga - antes de aprender sobre git branch -f

git checkout -b newbranch # switch to a new branch
git push . +HEAD~3:master # make master point to some older commit 

Ser capaz de push para . é um bom truque para saber.


Para aqueles que estão se perguntando por que funciona (como eu estava no começo):

Você quer voltar para C e mover D e E para o novo ramo. Aqui está o que parece no começo:

A-B-C-D-E (HEAD)
        ↑
      master

Depois git branch newBranch :

    newBranch
        ↓
A-B-C-D-E (HEAD)
        ↑
      master

Após git reset --hard HEAD~2 :

    newBranch
        ↓
A-B-C-D-E (HEAD)
    ↑
  master

Como um ramo é apenas um ponteiro, o mestre apontou para o último commit. Quando você fez newBranch , você simplesmente fez um novo ponteiro para o último commit. Então, usando o git reset você moveu o ponteiro mestre de volta para dois commits. Mas desde que você não mudou newBranch , ele ainda aponta para o commit originalmente feito.


Teve apenas esta situação:

Branch one: A B C D E F     J   L M  
                       \ (Merge)
Branch two:             G I   K     N

Eu executei:

git branch newbranch 
git reset --hard HEAD~8 
git checkout newbranch

Eu esperava que cometer eu seria a cabeça, mas comprometo L é agora ...

Para ter certeza de pousar no lugar certo na história, é mais fácil trabalhar com o hash do commit

git branch newbranch 
git reset --hard #########
git checkout newbranch

Simplificando, para criar um novo ramo local , faça:

git branch <branch-name>

Para empurrá-lo para o repositório remoto , faça:

git push -u origin <branch-name>




git git-branch branching-and-merging