Como posso esmagar meu último X confirmado em um commit usando o Git?
Como posso esmagar meu último X confirmado em um commit usando o Git?
Respostas:
Use git rebase -i <after-this-commit>
e substitua "pick" na segunda confirmação e subseqüentes por "squash" ou "fixup", conforme descrito no manual .
Neste exemplo, <after-this-commit>
é o hash SHA1 ou o local relativo do HEAD da ramificação atual a partir da qual as confirmações são analisadas para o comando rebase. Por exemplo, se o usuário desejar visualizar 5 confirmações do HEAD atual no passado, o comando será git rebase -i HEAD~5
.
<after-this-commit>
?
<after-this-commit>
é commit X + 1, ou seja, pai do commit mais antigo que você deseja esmagar.
rebase -i
abordagem e reset --soft
é, rebase -i
permite que eu retenha o autor do commit, enquanto reset --soft
me permite confirmar novamente. Às vezes, preciso esmagar os commits de solicitações pull, mantendo as informações do autor. Às vezes, preciso redefinir o software por conta própria. Upvotes para ambas as grandes respostas de qualquer maneira.
Você pode fazer isso facilmente, sem git rebase
ou git merge --squash
. Neste exemplo, esmagaremos os últimos 3 commits.
Se você deseja escrever a nova mensagem de confirmação do zero, basta:
git reset --soft HEAD~3 &&
git commit
Se você deseja começar a editar a nova mensagem de confirmação com uma concatenação das mensagens de confirmação existentes (ou seja, semelhante ao que uma git rebase -i
lista de instruções de pick / squash / squash /… / squash o iniciaria), será necessário extrair essas mensagens e passar eles git commit
:
git reset --soft HEAD~3 &&
git commit --edit -m"$(git log --format=%B --reverse HEAD..HEAD@{1})"
Ambos os métodos esmagam os três últimos commits em um único novo commit da mesma maneira. A reinicialização suave apenas aponta novamente HEAD para o último commit que você não deseja esmagar. Nem o índice nem a árvore de trabalho são tocados pela reinicialização suave, deixando o índice no estado desejado para seu novo commit (ou seja, ele já possui todas as alterações dos commits que você está prestes a "jogar fora").
git rebase --squash-recent
, ou até git commit --amend-many
.
branch@{upstream}
(ou apenas @{upstream}
para o ramo atual; em ambos os casos, a última parte poderá ser abreviada para @{u}
; veja gitrevisions ). Isso pode diferir do seu "último envio por push" (por exemplo, se alguém enviou algo que foi construído sobre o envio mais recente e você o buscou), mas parece que pode estar próximo do que você deseja.
push -f
mas, caso contrário, era adorável, obrigado.
git push --force
mais tarde para que ele leva a cometer
Você pode usar git merge --squash
isso, que é um pouco mais elegante que git rebase -i
. Suponha que você esteja no mestre e queira compactar os últimos 12 commits em um.
AVISO: Primeiro, certifique-se de comprometer o seu trabalho - verifique se git status
está limpo (pois git reset --hard
descartará alterações em etapas e em etapas)
Então:
# Reset the current branch to the commit just before the last 12:
git reset --hard HEAD~12
# HEAD@{1} is where the branch was just before the previous command.
# This command sets the state of the index to be as it would just
# after a merge from that commit:
git merge --squash HEAD@{1}
# Commit those squashed changes. The commit message will be helpfully
# prepopulated with the commit messages of all the squashed commits:
git commit
A documentação paragit merge
descreve a --squash
opção com mais detalhes.
Atualização: a única vantagem real desse método sobre a mais simples git reset --soft HEAD~12 && git commit
sugerida por Chris Johnsen em sua resposta é que você recebe a mensagem de confirmação pré-preenchida com todas as mensagens de confirmação que você está esmagando.
git rebase -i
, mas não explica por que. Provisivamente, coloquei isso em prática porque me parece que, de fato, o oposto é verdadeiro e isso é um truque; você não está executando mais comandos do que o necessário apenas para forçar git merge
a executar uma das coisas git rebase
especificamente projetadas?
git merge --squash
também é mais fácil de usar em um script. Essencialmente, o raciocínio era que você não precisa da "interatividade" git rebase -i
para isso.
git merge --squash
é menos provável que produza conflitos de mesclagem em face de movimentos / exclusões / renomeações em comparação com a reestruturação, especialmente se você estiver mesclando de uma ramificação local. (disclaimer: com base em apenas uma experiência, corrija-me se isso não é verdade no caso geral!)
HEAD@{1}
apenas estar do lado seguro, por exemplo, quando seu fluxo de trabalho é interrompido por uma hora por uma queda de energia, etc.
Eu recomendo evitar git reset
sempre que possível - especialmente para iniciantes no Git. A menos que você realmente precise automatizar um processo com base em uma série de confirmações, existe uma maneira menos exótica ...
git merge --squash (working branch name)
git commit
A mensagem de confirmação será pré-preenchida com base na squash.
gitk
para rotular a linha de código que você está esmagando e também rotular a base sobre a qual apertar. No caso normal, esses dois rótulos já existem, portanto, a etapa (1) pode ser ignorada.
git branch your-feature && git reset --hard HEAD~N
a maneira mais conveniente. No entanto, envolve git reset novamente, o que esta resposta tentou evitar.
Baseado na resposta de Chris Johnsen ,
Adicione um alias global de "squash" do bash: (ou Git Bash no Windows)
git config --global alias.squash '!f(){ git reset --soft HEAD~${1} && git commit --edit -m"$(git log --format=%B --reverse HEAD..HEAD@{1})"; };f'
... ou usando o prompt de comando do Windows:
git config --global alias.squash "!f(){ git reset --soft HEAD~${1} && git commit --edit -m\"$(git log --format=%B --reverse HEAD..HEAD@{1})\"; };f"
Seu ~/.gitconfig
agora deve conter este alias:
[alias]
squash = "!f(){ git reset --soft HEAD~${1} && git commit --edit -m\"$(git log --format=%B --reverse HEAD..HEAD@{1})\"; };f"
Uso:
git squash N
... Que esmaga automaticamente os últimos N
commits, inclusive.
Nota: A mensagem de confirmação resultante é uma combinação de todas as confirmações esmagadas, em ordem. Se você não estiver satisfeito com isso, poderá sempre git commit --amend
modificá-lo manualmente. (Ou edite o alias para corresponder ao seu gosto.)
git squash -m "New summary."
e ter N
determinado automaticamente como o número de confirmações não enviadas.
git commit --amend
para alterar ainda mais a mensagem, mas esse alias permite que você tenha um bom começo sobre o que deve estar na mensagem de confirmação.
Graças a este post útil, descobri que você pode usar este comando para esmagar os últimos 3 commits:
git rebase -i HEAD~3
Isso é útil, pois funciona mesmo quando você está em uma filial local sem informações de rastreamento / repositório remoto.
O comando abrirá o editor de rebase interativo, que permite reordenar, esmagar, reformular, etc., normalmente.
Usando o editor de rebase interativo:
O editor de rebase interativo mostra os três últimos commits. Essa restrição foi determinada HEAD~3
ao executar o comando git rebase -i HEAD~3
.
A confirmação mais recente,, HEAD
é exibida primeiro na linha 1. As linhas que começam com a #
são comentários / documentação.
A documentação exibida é bastante clara. Em qualquer linha, você pode alterar o comando de pick
para um comando de sua escolha.
Prefiro usar o comando, fixup
pois isso "esmaga" as alterações do commit no commit na linha acima e descarta a mensagem do commit.
Como o commit na linha 1 é HEAD
, na maioria dos casos você deixaria isso como pick
. Você não pode usar squash
ou fixup
porque não há outro commit para compactar o commit.
Você também pode alterar a ordem dos commits. Isso permite esmagar ou consertar confirmações que não são adjacentes cronologicamente.
Um exemplo prático do dia a dia
Eu comprometi recentemente um novo recurso. Desde então, cometi duas correções de bugs. Mas agora eu descobri um bug (ou talvez apenas um erro de ortografia) no novo recurso que cometi. Que irritante! Não quero um novo commit poluindo meu histórico de commit!
A primeira coisa que faço é corrigir o erro e fazer um novo commit com o comentário squash this into my new feature!
.
Em seguida, executo git log
ou gitk
obtenho o commit SHA do novo recurso (neste caso 1ff9460
).
Em seguida, trago o editor de rebase interativo com git rebase -i 1ff9460~
. O ~
SHA após o commit diz ao editor para incluir esse commit no editor.
Em seguida, movo a confirmação que contém o fix ( fe7f1e0
) para embaixo da confirmação do recurso e mudo pick
para fixup
.
Ao fechar o editor, a correção será compactada no commit do recurso e meu histórico de commit ficará bonito e limpo!
Isso funciona bem quando todos os commits são locais, mas se você tentar alterar qualquer commit já enviado para o controle remoto, poderá realmente causar problemas para outros desenvolvedores que fizeram check-out no mesmo ramo!
pick
na linha 1. Se você escolher squash
ou fixup
para o commit na linha 1, o git mostrará uma mensagem dizendo "erro: não é possível 'consertar' sem um commit anterior". Então, você terá a opção de corrigi-lo: "Você pode corrigir isso com 'git rebase --edit-todo' e, em seguida, execute 'git rebase --continue'." ou você pode simplesmente abortar e começar de novo: "Ou você pode abortar o rebase com 'git rebase --abort'.".
Se você usa o TortoiseGit, pode executar a função Combine to one commit
:
Show Log
Combine to one commit
no menu de contextoEssa função executa automaticamente todas as etapas necessárias de git único. Infelizmente disponível apenas para Windows.
Para fazer isso, você pode usar o seguinte comando git.
git rebase -i HEAD~n
n (= 4 aqui) é o número da última confirmação. Então você tem as seguintes opções,
pick 01d1124 Message....
pick 6340aaa Message....
pick ebfd367 Message....
pick 30e0ccb Message....
Atualize como abaixo de pick
um commit e squash
os outros para os mais recentes,
p 01d1124 Message....
s 6340aaa Message....
s ebfd367 Message....
s 30e0ccb Message....
Para detalhes, clique no link
Com base neste artigo , achei esse método mais fácil para o meu caso de usuário.
Meu ramo 'dev' estava à frente de 'origin / dev' em 96 confirmações (portanto, essas confirmações ainda não foram enviadas para o controle remoto).
Eu queria esmagar esses commits em um antes de fazer a alteração. Eu prefiro redefinir a ramificação para o estado 'origin / dev' (isso deixará todas as alterações dos commits do 96 sem estágio) e depois confirmar as alterações de uma só vez:
git reset origin/dev
git add --all
git commit -m 'my commit message'
No ramo em que você gostaria de combinar as confirmações, execute:
git rebase -i HEAD~(n number of commits back to review)
exemplo:
git rebase -i HEAD~1
Isso abrirá o editor de texto e você deverá alternar a 'seleção' na frente de cada confirmação com 'squash' se desejar que esses commits sejam mesclados. Da documentação:
Por exemplo, se você deseja mesclar todos os commits em um, o 'pick' é o primeiro commit que você fez e todos os futuros (colocados abaixo do primeiro) devem ser configurados como 'squash'. Se estiver usando o vim, use : x no modo de inserção para salvar e sair do editor.
Em seguida, para continuar a rebase:
git rebase --continue
Para saber mais sobre essa e outras maneiras de reescrever seu histórico de consolidação, consulte este post útil
--continue
e vim :x
faz.
git add
a configuração correta nos arquivos ser usada git rebase --continue
para passar para a próxima confirmação e começar a mesclar. :x
é um comando que salvará as alterações do arquivo ao usar o vim, veja isto
A resposta das anomias é boa, mas me senti insegura quanto a isso, então decidi adicionar algumas capturas de tela.
Veja onde você está git log
. Mais importante, encontre o hash de confirmação do primeiro commit que você não deseja esmagar. Portanto, apenas o:
Execute git rebase -i [your hash]
, no meu caso:
$ git rebase -i 2d23ea524936e612fae1ac63c95b705db44d937d
No meu caso, quero compactar tudo o que foi confirmado pela primeira vez. A ordem é do primeiro ao último, de maneira exatamente oposta git log
. No meu caso, eu quero:
Se você selecionou apenas uma confirmação e esmagou o restante, poderá ajustar uma mensagem de confirmação:
É isso aí. Depois de salvar este ( :wq
), está pronto. Dê uma olhada nisso com git log
.
git log
1) Identifique o hash curto de confirmação
# git log --pretty=oneline --abbrev-commit
abcd1234 Update to Fix for issue B
cdababcd Fix issue B
deab3412 Fix issue A
....
Aqui mesmo git log --oneline
também pode ser usado para obter hash curto.
2) Se você quiser esmagar (mesclar) os dois últimos commit
# git rebase -i deab3412
3) Isso abre um nano
editor para mesclar. E parece que abaixo
....
pick cdababcd Fix issue B
pick abcd1234 Update to Fix for issue B
....
4) Renomeie a palavra pick
à squash
qual está presente antes abcd1234
. Depois de renomear, deve ser como abaixo.
....
pick cdababcd Fix issue B
squash abcd1234 Update to Fix for issue B
....
5) Agora salve e feche o nano
editor. Pressione ctrl + o
e pressione Enter
para salvar. E então pressionectrl + x
para sair do editor.
6) Então nano
editor é aberto novamente para atualizar os comentários, se necessário, atualize-o.
7) Agora é esmagado com sucesso, você pode verificá-lo verificando os logs.
# git log --pretty=oneline --abbrev-commit
1122abcd Fix issue B
deab3412 Fix issue A
....
8) Agora pressione para repo. Nota para adicionar +
sinal antes do nome da filial. Isso significa empurrão forçado.
# git push origin +master
Nota: Isso se baseia no uso do git no ubuntu
shell. Se você estiver usando sistemas operacionais diferentes ( Windows
ou Mac
), os comandos acima serão os mesmos, exceto o editor. Você pode obter um editor diferente.
git add <files>
--fixup
opção e OLDCOMMIT
deve estar na qual precisamos mesclar (squash) esse commit.git commit --fixup=OLDCOMMIT
Agora, isso cria um novo commit no topo do HEAD fixup1 <OLDCOMMIT_MSG>
.
OLDCOMMIT
.git rebase --interactive --autosquash OLDCOMMIT^
Aqui ^
significa o compromisso anterior com OLDCOMMIT
. Este rebase
comando abre uma janela interativa em um editor (vim ou nano), em que não precisamos fazer nada, basta salvar e sair é suficiente. Como a opção passada para isso moverá automaticamente a confirmação mais recente para a confirmação antiga e alterará a operação parafixup
(equivalente a squash). Em seguida, o rebase continua e termina.
--amend
possível usar os meios git-commit
. # git log --pretty=oneline --abbrev-commit
cdababcd Fix issue B
deab3412 Fix issue A
....
# git add <files> # New changes
# git commit --amend
# git log --pretty=oneline --abbrev-commit
1d4ab2e1 Fix issue B
deab3412 Fix issue A
....
Aqui --amend
mescla as novas mudanças para a última confirmaçãocdababcd
e gera um novo ID de confirmação1d4ab2e1
Para compactar os últimos 10 commit em um único commit:
git reset --soft HEAD~10 && git commit -m "squashed commit"
Se você também deseja atualizar a ramificação remota com o commit compactado:
git push -f
Se você estiver em uma ramificação remota (chamada feature-branch
) clonada de um Repositório de Ouro ( golden_repo_name
), então aqui está a técnica para compactar seus commits em um:
Finalize o repo de ouro
git checkout golden_repo_name
Crie um novo ramo a partir dele (repositório de ouro) da seguinte maneira
git checkout -b dev-branch
O Squash se mescla com a filial local que você já possui
git merge --squash feature-branch
Confirme suas alterações (esse será o único commit que ocorre no ramo dev)
git commit -m "My feature complete"
Envie a ramificação para o seu repositório local
git push origin dev-branch
O que pode ser realmente conveniente:
encontre o hash de confirmação que você deseja esmagar, digamosd43e15
.
Agora usa
git reset d43e15
git commit -am 'new commit name'
Isso é super-duper kludgy, mas de uma maneira legal, então eu vou jogá-lo no ringue:
GIT_EDITOR='f() { if [ "$(basename $1)" = "git-rebase-todo" ]; then sed -i "2,\$s/pick/squash/" $1; else vim $1; fi }; f' git rebase -i foo~5 foo
Tradução: forneça um novo "editor" para o git que, se o nome do arquivo a ser editado for git-rebase-todo
(o prompt de rebase interativo), alterará tudo, exceto o primeiro "pick" para "squash", e gerará o vim - de modo que quando você for solicitado Para editar a mensagem de confirmação compactada, você obtém o vim. (E, obviamente, eu estava esmagando os últimos cinco commits no ramo foo, mas você pode mudar isso da maneira que quiser.)
Eu provavelmente faria o que Mark Longair sugeriu , no entanto.
Se você deseja compactar cada commit em um único commit (por exemplo, ao liberar um projeto publicamente pela primeira vez), tente:
git checkout --orphan <new-branch>
git commit
Eu acho que a maneira mais fácil de fazer isso é criar uma nova ramificação fora do master e fazer uma mesclagem - sucata da ramificação de recursos.
git checkout master
git checkout -b feature_branch_squashed
git merge --squash feature_branch
Então você tem todas as alterações prontas para confirmar.
Uma linha simples que sempre funciona, já que você está atualmente no ramo que deseja esmagar, master é o ramo de origem e o commit mais recente contém a mensagem e o autor do commit que você deseja usar:
git reset --soft $(git merge-base HEAD master) && git commit --reuse-message=HEAD@{1}
se, por exemplo, você deseja compactar as últimas 3 confirmações em uma única confirmação em uma ramificação (repositório remoto), por exemplo: https://bitbucket.org
O que eu fiz é
(MASTER)
Fleetwood Mac Fritz
║ ║
Add Danny Lindsey Stevie
Kirwan Buckingham Nicks
║ ╚═══╦══════╝
Add Christine ║
Perfect Buckingham
║ Nicks
LA1974══════════╝
║
║
Bill <══════ YOU ARE EDITING HERE
Clinton (CHECKED OUT, CURRENT WORKING DIRECTORY)
Nesta história muito abreviada do repositório https://github.com/fleetwood-mac/band-history, você abriu uma solicitação pull para mesclar o commit de Bill Clinton no commit original ( MASTER
) do Fleetwood Mac.
Você abriu uma solicitação de recebimento e no GitHub você vê isso:
Quatro confirmações:
Pensando que ninguém se importaria em ler a história completa do repositório. (Na verdade, existe um repositório, clique no link acima!) Você decide esmagar esses commits. Então você vai e corre git reset --soft HEAD~4 && git commit
. Então vocêgit push --force
no GitHub para limpar seu PR.
E o que acontece? Você acabou de fazer um único commit que vai de Fritz a Bill Clinton. Porque você esqueceu que ontem estava trabalhando na versão Buckingham Nicks deste projeto. E git log
não corresponde ao que você vê no GitHub.
git checkout
elesgit reset --soft
quegit commit
que entorte diretamente do de para o paragit rebase -i HEAD^^
onde o número de ^ é X
(neste caso, esmague os dois últimos commits)
Além de outras excelentes respostas, gostaria de acrescentar como git rebase -i
sempre me confunde com a ordem de confirmação - da mais antiga para a mais recente ou vice-versa? Portanto, este é o meu fluxo de trabalho:
git rebase -i HEAD~[N]
, em que N é o número de confirmações nas quais quero ingressar, começando pela mais recente . Então git rebase -i HEAD~5
, significa "esmagar os últimos 5 commits em um novo";Que tal uma resposta para a pergunta relacionada a um fluxo de trabalho como este?
merge --squash
após o PR, mas a equipe achou que isso atrasaria o processo.)Não vi um fluxo de trabalho como esse nesta página. (Talvez sejam meus olhos.) Se eu entendi rebase
corretamente, várias mesclagens exigiriam várias resoluções de conflito . Eu não quero nem pensar nisso!
Então, isso parece funcionar para nós.
git pull master
git checkout -b new-branch
git checkout -b new-branch-temp
git checkout new-branch
git merge --squash new-branch-temp
// coloca todas as alterações no estágiogit commit 'one message to rule them all'
git push
Acho que uma solução mais genérica não é especificar confirmações 'N', mas sim o ID da filial / confirmação que você deseja esmagar. Isso é menos propenso a erros do que contar as confirmações até uma confirmação específica - basta especificar a tag diretamente ou, se você realmente quiser contar, pode especificar HEAD ~ N.
No meu fluxo de trabalho, inicio uma ramificação, e meu primeiro commit nessa ramificação resume o objetivo (ou seja, geralmente é o que enviarei como a mensagem 'final' do recurso para o repositório público.) Então, quando terminar, tudo Eu quero fazer é git squash master
voltar para a primeira mensagem e então estou pronto para enviar.
Eu uso o alias:
squash = !EDITOR="\"_() { sed -n 's/^pick //p' \"\\$1\"; sed -i .tmp '2,\\$s/^pick/f/' \"\\$1\"; }; _\"" git rebase -i
Isso irá despejar o histórico que está sendo compactado antes de fazê-lo - isso lhe dá uma chance de recuperar, pegando um ID de confirmação antigo do console, se você quiser reverter. (Os usuários do Solaris observam que ele usa a -i
opção GNU sed ; os usuários de Mac e Linux devem ficar bem com isso.)
git squash master
quando fazemos check-out no escravo. o que isso vai acontecer? vamos esconder o conflito?
Em questão, pode ser ambíguo o que se entende por "último".
por exemplo, git log --graph
gera o seguinte (simplificado):
* commit H0
|
* merge
|\
| * commit B0
| |
| * commit B1
| |
* | commit H1
| |
* | commit H2
|/
|
As últimas confirmações por hora são H0, mesclagem, B0. Para esmagá-los, você precisará refazer sua ramificação mesclada no commit H1.
O problema é que H0 contém H1 e H2 (e geralmente confirma mais antes da mesclagem e após a ramificação), enquanto B0 não. Então você precisa gerenciar as alterações de H0, mesclagem, H1, H2, B0 pelo menos.
É possível usar o rebase, mas de maneira diferente das respostas mencionadas nas outras:
rebase -i HEAD~2
Isso mostrará as opções de escolha (conforme mencionado em outras respostas):
pick B1
pick B0
pick H0
Coloque a abóbora em vez de escolher para H0:
pick B1
pick B0
s H0
Após salvar e sair, o rebase aplicará confirmações sucessivas após H1. Isso significa que ele solicitará que você resolva conflitos novamente (onde HEAD será H1 no início e depois acumulará confirmações à medida que forem aplicadas).
Após o término da recuperação, você pode escolher a mensagem para H0 e B0 esmagados:
* commit squashed H0 and B0
|
* commit B1
|
* commit H1
|
* commit H2
|
PS Se você apenas redefinir o BO: (por exemplo, usar reset --mixed
isso é explicado em mais detalhes aqui https://stackoverflow.com/a/18690845/2405850 ):
git reset --mixed hash_of_commit_B0
git add .
git commit -m 'some commit message'
então você esmaga as alterações B0 de H0, H1, H2 (perdendo completamente as confirmações após as ramificações e antes da mesclagem).