Posso excluir um commit do git, mas manter as alterações

1057

Em uma das minhas ramificações de desenvolvimento, fiz algumas alterações na minha base de código. Antes de concluir os recursos em que estava trabalhando, tive que mudar minha ramificação atual para mestre para demonstrar alguns recursos. Mas apenas o uso de um "mestre de verificação do git" preservou as alterações que também fiz no meu ramo de desenvolvimento, quebrando assim algumas das funcionalidades do mestre. Então, o que eu fiz foi confirmar as alterações no meu ramo de desenvolvimento com uma mensagem de confirmação "confirmação temporária" e depois fazer o checkout do mestre para a demonstração.

Agora que terminei a demonstração e voltei a trabalhar no meu ramo de desenvolvimento, gostaria de remover o "commit temporário" que fiz, preservando as alterações que fiz. Isso é possível?

tanookiben
fonte
65
Da próxima vez:git stash
Matt Ball
51
@MattBall, não necessariamente. Embora git stashseja uma boa ferramenta, os commits de descarte de "trabalho em andamento" também são um dispositivo legítimo.
Kostix #
3
Este é um grande recurso estreito de Github: Como desfazer (quase) qualquer coisa com Git
jasonleonhard
9
@MattBall @kostix Sim, o stash é particularmente inadequado para stash "de longo prazo", uma vez que é uma pilha global para todo o repositório. Quero poder esconder as alterações em um galho e depois ir para outros galhos, fazer o que mais, voltar dias depois sem me preocupar que eu possa ter usado git stashem outro galho nesse ínterim.
Alec
4
Uma coisa que vale a pena notar stashé que é completamente local e estará sujeito a perda de código devido à remoção ou exclusão de recreação ou falha ou perda de hardware. Na IMO, ele realmente deve ser usado apenas para WIP de curto prazo. Adoro um commit do WIP antes de sair de férias: P .. chame de commit de despejo cerebral!
ϹοδεMεδιϲ

Respostas:

1619

É simples assim:

git reset HEAD^

Nota: alguns shells são tratados ^como caracteres especiais (por exemplo, alguns shells do Windows ou ZSH com globbing ativado ), portanto, é necessário citar "HEAD^"nesses casos.

git resetsem a --hardou --softmove seu HEADpara apontar para o commit especificado, sem alterar nenhum arquivo. HEAD^refere-se ao (primeiro) commit pai do seu commit atual, que no seu caso é o commit antes do temporário.

Observe que outra opção é continuar normalmente e, no próximo ponto de confirmação, execute:

git commit --amend [-m … etc]

que editará a confirmação mais recente, com o mesmo efeito acima.

Observe que isso (como em quase todas as respostas do git) pode causar problemas se você já enviou o commit incorreto para um local de onde alguém o tenha retirado. Tente evitar isso

Gareth
fonte
47
Isso é de longe o mais simples, mas se você já empurrou seu commit para um controle remoto e alguém o puxou, eu hesitaria muito em fazer qualquer coisa, exceto pedir desculpas.
At13
11
@sicophrenic, não perca a oportunidade de ler "Reset Demystified" nesta ocasião.
Kostix #
33
Eu recebo More?depois de fazer isso. O que quer que eu escreva Nesse prompt me dáfatal: ambiguous argument 'HEADwhateverItypedIn': unknown revision or path not in the working tree.
DaAwesomeP
50
O @DaAwesomeP parece que você está usando um shell que trata ^como um caractere especial. Você poderia citar a referência "HEAD^", ou usar a sintaxe alternativa HEAD~1não cotadas
Gareth
8
Trabalhou para mim, tinha de escapar personagem emboragit reset HEAD\^
cevaris
188

Existem duas maneiras de lidar com isso. O que é mais fácil depende da sua situação

Redefinir

Se o commit do qual você deseja se livrar foi o último, e você não fez nenhum trabalho adicional, pode simplesmente usar git-reset

git reset HEAD^

Leva seu ramo de volta ao commit antes do seu HEAD atual. No entanto, ele realmente não altera os arquivos na sua árvore de trabalho. Como resultado, as alterações que ocorreram nesse commit aparecem como modificadas - é como um comando 'descomprometido'. Na verdade, eu tenho um apelido para fazer exatamente isso.

git config --global alias.uncommit 'reset HEAD^'

Depois, você pode apenas usar git uncommitno futuro para fazer backup de uma confirmação.

Esmagar

Esmagar um commit significa combinar dois ou mais commit em um. Faço isso com bastante frequência. No seu caso, você tem um recurso concluído pela metade e, em seguida, finalizava e confirmava novamente com a mensagem de confirmação permanente e adequada.

git rebase -i <ref>

Digo acima, porque quero deixar claro que isso pode ser um número qualquer de confirmações. Execute git loge encontre o commit do qual você deseja se livrar, copie o SHA1 e use-o no lugar de <ref>. O Git o levará ao modo de rebase interativo. Ele mostrará todos os commits entre seu estado atual e o que você colocar no lugar <ref>. Portanto, se <ref>há 10 confirmações atrás, ele mostrará todos os 10 confirmações.

Na frente de cada commit, ele terá a palavra pick. Encontre o commit do qual deseja se livrar e altere-o de pickpara fixupou squash. O uso fixupsimples de descarte que confirma a mensagem e mescla as alterações em seu predecessor imediato na lista. A squashpalavra-chave faz a mesma coisa, mas permite editar a mensagem de confirmação do recém-combinado commit.

Observe que as confirmações serão confirmadas novamente na ordem em que aparecem na lista quando você sair do editor. Portanto, se você fez uma confirmação temporária, fez outro trabalho na mesma ramificação e concluiu o recurso em uma confirmação posterior, o uso de rebase permitirá reorganizar as confirmações e esmagá-las.

ATENÇÃO:

O rebasing modifica o histórico - NÃO faça isso em nenhum commit que você já tenha compartilhado com outros desenvolvedores.

Esconderijo

No futuro, para evitar esse problema, considere usar git stashpara armazenar temporariamente trabalho não confirmado.

git stash save 'some message'

Isso armazenará suas alterações atuais ao lado da sua lista de stash. Acima está a versão mais explícita do comando stash, permitindo um comentário para descrever o que você está ocultando. Você também pode simplesmente executar git stashe nada mais, mas nenhuma mensagem será armazenada.

Você pode navegar na sua lista de stash com ...

git stash list

Isso mostrará todos os seus stashes, em quais ramos eles foram feitos, a mensagem e no início de cada linha e o identificador para o stash que se parece com este, stash@{#}onde # é sua posição no conjunto de stashes.

Para restaurar um stash (que pode ser feito em qualquer ramificação, independentemente de onde o stash foi originalmente criado), basta executar ...

git stash apply stash@{#}

Novamente, há # a posição na matriz de esconderijos. Se o stash que você deseja restaurar estiver na 0posição - isto é, se foi o stash mais recente. Depois, é só executar o comando sem especificar a posição stash, git irá assumir que você quer dizer a última: git stash apply.

Por exemplo, se eu estiver trabalhando no ramo errado - posso executar a seguinte sequência de comandos.

git stash
git checkout <correct_branch>
git stash apply

No seu caso, você mudou um pouco mais os galhos, mas a mesma idéia ainda se aplica.

Espero que isto ajude.

eddiemoya
fonte
6
git config --global alias.uncommit reset HEAD^apenas aliases descomprometidos para redefinir. Em vez disso, façagit config --global alias.uncommit 'reset HEAD^'
mernst 14/04/2015
3
Observe que você precisará usar ^^ em vez de ^ se estiver usando em um prompt de comando do Windows.
Pramod BR
106

Eu acho que você está procurando por isso

git reset --soft HEAD~1

Desfaz a confirmação mais recente, mantendo as alterações feitas na confirmação na preparação.

Sudhanshu Jain
fonte
7
Obrigado. Isso funcionou para mim. A chamada git reset HEAD^no Windows apenas solicita "Mais?" - o que quer que isso signifique
Tyron
12
@Tyron ^é um caractere de escape no DOS. Quando emparelhado com uma nova linha, ele serve como um prompt de continuação para o comando anterior. A digitação git reset HEAD^^deve funcionar no Windows.
trk
40

Sim, você pode excluir seu commit sem excluir as alterações: git reset @ ~

DURGESH
fonte
7
Isso é realmente o que eu quero, e deve ser a resposta aceita em minha opinião, muito obrigado!
Carlos Liu
2
Sintaxe interessante e compacta, eu nunca vi ou usei antes. Como isso é diferente de git reset --softou git reset --keep?
user776686
18

Você está procurando por um git reset HEAD^ --softou outro git reset HEAD^ --mixed.

Existem 3 modos para o comando reset, conforme indicado nos documentos :

git reset HEAD^ --soft

desfazer o git commit. Ainda existem alterações na árvore de trabalho (a pasta do projeto) + no índice (--cached)

git reset HEAD^ --mixed

desfazer git commit+ git add. Ainda existem alterações na árvore de trabalho

git reset HEAD^ --hard

Como você nunca fez essas alterações na base de código. As alterações foram eliminadas da árvore de trabalho.

Bar Horing
fonte
9

Para aqueles que usam o zsh, você precisará usar o seguinte:

git reset --soft HEAD\^

Explicado aqui: https://github.com/robbyrussell/oh-my-zsh/issues/449

Caso a URL fique inoperante, a parte importante é:

Escape do ^ em seu comando

Como alternativa, você pode usar HEAD ~ para não precisar escapar sempre.

Greg Hilston
fonte
15
Eu nunca consigo lembrar esse comando e tem que google isso e encontrar a minha própria resposta hahahaha
Greg Hilston
2
git reset HEAD^está trabalhando no zsh para mim, pode ter sido corrigido.
Ben Kolya Mansley
@BenKolyaMansley Qual versão do zsh você está executando?
Greg Hilston
Estou usando #zsh 5.3 (x86_64-apple-darwin18.0)
Ben Kolya Mansley
7

No meu caso, eu já fui ao repositório. Ai!

Você pode reverter uma confirmação específica, mantendo as alterações nos arquivos locais, fazendo:

git revert -n <sha>

Dessa maneira, pude manter as alterações necessárias e desfazer um commit que já havia sido enviado.

Wim Feijen
fonte
No meu caso, eu precisava reverter algo que já enviei para o repositório remoto. Ainda mais ai! A melhor maneira era git revert bad-commit-sha, então git revert -n revert-commit-just-created-shae depois reparar a partir daí. Você me pegou no meio do caminho. Obrigado!
TinkerTenorSoftwareGuy
Isso parece fazer o oposto, não? Ele cria novas alterações que correspondem a uma reversão das alterações feitas no commit selecionado. Se você confirmar essas novas alterações, terá desfeito o trabalho que realmente queria manter.
Svaens
3

O uso do git 2.9 (precisamente 2.9.2.windows.1) git reset HEAD^solicita mais; não tenho certeza do que é esperado entrada aqui. Consulte a captura de tela abaixo

insira a descrição da imagem aqui

Encontrei outra solução git reset HEAD~#numberOfCommitsusando a qual podemos escolher o número de confirmações locais que você deseja redefinir mantendo as alterações intactas. Portanto, temos a oportunidade de descartar todas as confirmações locais, bem como um número limitado de confirmações locais.

Consulte as capturas de tela abaixo git reset HEAD~1em ação: insira a descrição da imagem aqui

insira a descrição da imagem aqui

nkharche
fonte
Você provavelmente precisa escapar do caractere ^ - tente git reset "HEAD ^" ou git reset HEAD \ ^
Mark Fisher
Além disso, um git reset HEAD ^^ funciona como um único ^ é tratado como uma nova linha.
John Oss
2

Mais uma maneira de fazer isso.

Adicione confirmação na parte superior da confirmação temporária e faça:

git rebase -i

Para mesclar duas confirmações em uma (o comando abrirá o arquivo de texto com instruções explícitas, edite-o).

Ruslan Osipov
fonte
2
Tecnicamente correto, mas nem de longe tão elegante quanto simplesmente fazer git reset HEAD^. O rebase do Git tem muito espaço para erro aqui.
TheBuzzSaw /
0

2020 maneira simples:

git reset <commit_hash>

(O hash de confirmação da última confirmação que você deseja manter).

Se a confirmação foi enviada, você pode:

git push -f

Você manterá as alterações agora não confirmadas localmente

Xys
fonte