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
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ê só 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.
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
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:
ABCDE (HEAD) ↑ master
Depois git branch newBranch
:
newBranch ↓ ABCDE (HEAD) ↑ master
Após git reset --hard HEAD~2
:
newBranch ↓ ABCDE (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.
O método exposto pelo sykora é a melhor opção neste caso. Mas às vezes não é o mais fácil e não é um método geral. Para um método geral use git cherry-pick :
Para alcançar o que o OP quer, é um processo de duas etapas:
newbranch
Executar
git checkout master git log
Observe os hashes de (digamos 3) commits que você quer no newbranch
. Aqui vou usar:
C commit: 9aa1233
D commit: 453ac3d
E commit: 612ecb3
Nota: Você pode usar os primeiros sete caracteres ou todo o hash de confirmação
newbranch
git checkout newbranch git cherry-pick 612ecb3 git cherry-pick 453ac3d git cherry-pick 9aa1233
git checkout newbranch git cherry-pick 612ecb3~1..9aa1233
git cherry-pick aplica esses três commits para newbranch.
Ainda outra maneira de fazer isso, usando apenas 2 comandos. Também mantém sua tree 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.
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 ..HEAD@{2}
--keep
é como --hard
, mas é mais seguro, pois falha ao invés de descartar as alterações não confirmadas). newbranch
. newbranch
. Como eles não são mais referenciados por um branch, ele faz isso usando o reflog do git: HEAD@{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 repository 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
).
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 .
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)
Para fazer isso sem rewrite o histórico (ou seja, se você já enviou os commits):
git checkout master git revert git checkout -b new-branch git cherry-pick
Ambos os ramos podem ser empurrados sem força!
Solução muito mais simples
E se:
master
e Então, o seguinte é muito mais simples (iniciando no branch master
que possui commits errados):
git reset HEAD~3 git stash git checkout newbranch git stash pop
O que isto faz, por número de linha:
master
, mas deixa todos os arquivos de trabalho intactos master
igual ao estado HEAD ~ 3 newbranch
existente 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:
O OP declarou que o objective 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.
Teve apenas esta situação:
Branch one: ABCDEFJLM \ (Merge) Branch two: GIKN
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
1) Crie uma nova ramificação, que move todas as suas alterações para new_branch.
git checkout -b new_branch
2) Então volte para o ramo antigo.
git checkout master
3) fazer rebase git
git rebase -i
4) Em seguida, o editor aberto contém as últimas 3 informações de confirmação.
... pick C pick D pick E ...
5) Alterar pick
para drop
em todos os 3 commits. Em seguida, salve e feche o editor.
... drop C drop D drop E ...
6) Agora, os últimos 3 commits são removidos da ramificação atual ( master
). Agora empurre o ramo com força, com o sinal +
antes do nome da ramificação.
git push origin +master
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
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