Usos práticos do git reset --soft?

136

Trabalho com o git há pouco mais de um mês. Na verdade, eu usei a redefinição pela primeira vez ontem, mas a redefinição suave ainda não faz muito sentido para mim.

Entendo que posso usar a reinicialização suave para editar uma confirmação sem alterar o índice ou o diretório de trabalho, como faria com git commit --amend.

Esses dois comandos são realmente os mesmos ( reset --softvs commit --amend)? Algum motivo para usar um ou outro em termos práticos? E o mais importante, existem outros usos reset --softalém de alterar um commit?

AJJ
fonte

Respostas:

110

git reseté tudo sobre mudança HEAD, e geralmente o ramo ref .
Pergunta: e a árvore de trabalho e o índice?
Quando empregado com --soft, move HEAD, na maioria das vezes atualizando o ramo ref, e somente oHEAD .
Isso difere de commit --amend:

  • não cria um novo commit.
  • na verdade, ele pode mover HEAD para qualquer confirmação (como commit --amendé apenas não mover HEAD, enquanto permite refazer a confirmação atual)

Acabei de encontrar este exemplo de combinação:

  • uma fusão clássica
  • uma mesclagem de subárvore

tudo em um (polvo, pois há mais de dois ramos mesclados) confirma a mesclagem.

Tomas "wereHamster" Carnecky explica em seu artigo "Subtree Octopus merge" :

  • A estratégia de mesclagem de subárvore pode ser usada se você deseja mesclar um projeto em um subdiretório de outro projeto e, posteriormente, manter o subprojeto atualizado. É uma alternativa aos submódulos git.
  • A estratégia de mesclagem de polvo pode ser usada para mesclar três ou mais ramificações. A estratégia normal pode mesclar apenas dois ramos e, se você tentar mesclar mais do que isso, o git volta automaticamente à estratégia do polvo.

O problema é que você pode escolher apenas uma estratégia. Mas eu queria combinar os dois para obter um histórico limpo, no qual todo o repositório seja atualizado atomicamente para uma nova versão.

Eu tenho um superprojeto, vamos chamá-lo projectAe um subprojeto projectB, que eu mesclei em um subdiretório de projectA.

(essa é a parte de mesclagem da subárvore)

Também estou mantendo alguns commits locais.
ProjectAé atualizado regularmente, projectBtem uma nova versão a cada dois dias ou semanas e geralmente depende de uma versão específica do projectA.

Quando eu decido atualizar os dois projetos, eu simplesmente não extrao projectAe, projectB como isso criaria dois commits para o que deveria ser uma atualização atômica de todo o projeto .
Em vez disso, eu criar um único merge comprometer que combina projectA, projectBe meus commits locais .
A parte complicada aqui é que essa é uma mesclagem de polvo (três cabeças), mas projectBprecisa ser mesclada com a estratégia da subárvore . Então é isso que eu faço:

# Merge projectA with the default strategy:
git merge projectA/master

# Merge projectB with the subtree strategy:
git merge -s subtree projectB/master

Aqui, o autor usou ae reset --hard, em seguida, read-treepara restaurar o que as duas primeiras mesclagens fizeram na árvore e no índice de trabalho, mas é aí que reset --softpode ajudar:
Como refazer essas duas mesclagens que funcionaram, ou seja, minha árvore de trabalho e índice são tudo bem, mas sem ter que gravar esses dois commits?

# Move the HEAD, and just the HEAD, two commits back!
git reset --soft HEAD@{2}

Agora, podemos retomar a solução de Tomas:

# Pretend that we just did an octopus merge with three heads:
echo $(git rev-parse projectA/master) > .git/MERGE_HEAD
echo $(git rev-parse projectB/master) >> .git/MERGE_HEAD

# And finally do the commit:
git commit

Então, cada vez:

  • você está satisfeito com o que termina (em termos de árvore de trabalho e índice)
  • você não está satisfeito com todos os commits que levaram para chegar lá:

git reset --soft é a resposta.

VonC
fonte
8
Nota para self: exemplo simples de esmagamento com git reset --soft: stackoverflow.com/questions/6869705/…
VonC
3
também é útil se você se comprometer com o ramo errado. todas as alterações voltam para a área de preparação e movem-se com você enquanto você faz o checkout na ramificação direita.
smoebody
44

Caso de Uso - Combine uma série de confirmações locais

"Opa. Esses três commits podem ser apenas um."

Portanto, desfaça os últimos 3 (ou qualquer outro) commit (sem afetar o índice nem o diretório de trabalho). Em seguida, confirme todas as alterações como uma.

Por exemplo

> git add -A; git commit -m "Start here."
> git add -A; git commit -m "One"
> git add -A; git commit -m "Two"
> git add -A' git commit -m "Three"
> git log --oneline --graph -4 --decorate

> * da883dc (HEAD, master) Three
> * 92d3eb7 Two
> * c6e82d3 One
> * e1e8042 Start here.

> git reset --soft HEAD~3
> git log --oneline --graph -1 --decorate

> * e1e8042 Start here.

Agora todas as suas alterações estão preservadas e prontas para serem confirmadas como uma só.

Respostas curtas para suas perguntas

Esses dois comandos são realmente os mesmos ( reset --softvs commit --amend)?

  • Não.

Algum motivo para usar um ou outro em termos práticos?

  • commit --amend adicionar / rm arquivos desde a última confirmação ou alterar sua mensagem.
  • reset --soft <commit> para combinar várias confirmações seqüenciais em uma nova.

E, mais importante, existem outros usos reset --softalém de alterar um commit?

  • Veja outras respostas :)
Shaun Luttin
fonte
2
Veja outras respostas para "existem outros usos reset --softalém de alterar um commit - Não"
Antony Hatchkins
Em resposta ao último comentário - concordado em parte, mas eu diria que alterar um único commit é muito específico. Por exemplo, depois que um recurso é escrito, você pode compactar tudo e criar novos commits mais úteis para os revisores. Eu diria que as redefinições suaves (incluindo simples git reset) são boas quando você (a) deseja reescrever a história, (b) não se preocupa com os commits antigos (para evitar a confusão de rebase interativa) e (c) tem mais de um comprometimento para alterar (caso contrário, commit --amendé mais simples).
johncip 22/05/19
18

Eu o uso para alterar mais do que apenas o último commit.

Digamos que eu cometi um erro ao confirmar A e depois cometi o B. Agora, só posso alterar o B. Assim faço git reset --soft HEAD^^, corrigo e re-confirmo A e, em seguida, re-confirmo B.

Claro, não é muito conveniente para grandes confirmações ... mas você não deve fazer grandes confirmações de qualquer maneira ;-)

Simon
fonte
3
git commit --fixup HEAD^^ git rebase --autosquash HEAD~Xfunciona bem também.
Niklas #
3
git rebase --interactive HEAD^^onde você escolhe editar as confirmações A e B. Dessa forma, mantém as mensagens de confirmação de A e B; se necessário, você também pode modificá-las.
Paul Pladijs
2
Como reconfigurar B depois de redefinir para A?
northben
1
Outra forma que é provavelmente menos trabalho: verificar o seu git log, obter o hash B cometer, em seguida git reset A, fazer e adicionar alterações, git commit --amend, git cherry-pick <B-commit-hash>.
naught101
15

Outro uso potencial é como uma alternativa ao armazenamento em cache (do qual algumas pessoas não gostam, consulte, por exemplo, https://codingkilledthecat.wordpress.com/2012/04/27/git-stash-pop-considered-harmful/ ).

Por exemplo, se estou trabalhando em uma ramificação e preciso corrigir algo com urgência no mestre, posso fazer:

git commit -am "In progress."

depois faça o checkout master e faça a correção. Quando termino, volto ao meu ramo e faço

git reset --soft HEAD~1

para continuar trabalhando de onde parei.

deltacrux
fonte
3
Atualização (agora que eu entendo melhor o git): --softé realmente desnecessário aqui, a menos que você realmente se importe com a possibilidade de realizar as alterações imediatamente. Agora eu apenas uso git reset HEAD~ao fazer isso. Se eu tiver algumas alterações preparadas quando preciso alternar ramificações, e quiser mantê-las dessa maneira, então eu faço git commit -m "staged changes", então git commit -am "unstaged changes", mais tarde git reset HEAD~seguidas por git reset --soft HEAD~para restaurar completamente o estado de trabalho. Embora, para ser honesto eu fazer ambas as coisas muito menos agora que eu sei sobre git-worktree:)
deltacrux
7

Você pode usar git reset --softpara alterar a versão que deseja ter como pai das alterações que você possui no seu índice e na árvore de trabalho. Os casos em que isso é útil são raros. Às vezes, você pode decidir que as alterações que você tem na sua árvore de trabalho devem pertencer a um ramo diferente. Ou você pode usar isso como uma maneira simples de recolher vários commits em um (semelhante ao squash / fold).

Veja esta resposta de VonC para um exemplo prático: Squash os dois primeiros commits no Git?

Johannes Rudolph
fonte
Essa é uma boa pergunta que eu não tinha encontrado antes. Mas não tenho certeza de que posso ver como fazer alterações em outro ramo usando o recurso soft reset. Então eu tenho checkout Branch, então eu uso git reset --soft anotherBranche depois comprometo lá? Mas você realmente não muda o ramo ckeckout, então se comprometerá com o Branch ou com o outroBranch ?
AJJ
Ao fazer isso, é importante que você não use o git checkout, pois isso alterará sua árvore. Pense no git reset --soft como uma maneira de manipular a revisão que HEAD aponta.
Johannes Rudolph
7

Um uso possível seria quando você deseja continuar seu trabalho em uma máquina diferente. Funcionaria assim:

  1. Faça check-out de uma nova ramificação com um nome semelhante a stash,

    git checkout -b <branchname>_stash
    
  2. Empurre seu galho para cima,

    git push -u origin <branchname>_stash
    
  3. Mude para a sua outra máquina.

  4. Puxe para baixo o seu estoque e os galhos existentes,

    git checkout <branchname>_stash; git checkout <branchname>
    
  5. Você deve estar em sua filial existente agora. Mesclar as alterações do ramo stash,

    git merge <branchname>_stash
    
  6. Redefina manualmente sua ramificação existente para 1 antes da mesclagem,

    git reset --soft HEAD^
    
  7. Remova o seu esconderijo,

    git branch -d <branchname>_stash
    
  8. Remova também seu ramo stash da origem,

    git push origin :<branchname>_stash
    
  9. Continue trabalhando com suas alterações como se você as tivesse ocultado normalmente.

Eu acho que, no futuro, GitHub e companhia. deve oferecer essa funcionalidade de "esconderijo remoto" em menos etapas.

llaughlin
fonte
2
Quero ressaltar que o primeiro stash e pop na sua primeira máquina são completamente desnecessários; você pode simplesmente criar uma nova ramificação diretamente de uma cópia de trabalho suja, confirmar e enviar as alterações para o controle remoto.
7

Um uso prático é se você já se comprometeu com seu repo local (por exemplo, git commit -m), então você pode reverter esse último commit, executando git reset --soft HEAD ~ 1

Também para o seu conhecimento, se você já organizou suas alterações (ou seja, com o git add.), Pode reverter o estadiamento fazendo git reset --misted HEAD ou geralmente também apenas useigit reset

por fim, o git reset --hard limpa tudo, incluindo as alterações locais. O cabeçalho ~ after diz a você quantos commits devem ir do topo.

j2emanue
fonte
1
git reset --soft HEAD ~1dá-me fatal: Cannot do soft reset with paths.eu acho que nós precisamos remover o espaço após a cabeça de modo que seriagit reset --soft HEAD~1
Andrew Lohr
6

Um ótimo motivo para usar ' git reset --soft <sha1>' é mover-se HEADem um repositório simples.

Se você tentar usar a opção --mixedou --hard, receberá um erro, pois está tentando modificar e trabalhar em árvore e / ou índice que não existe.

Nota: você precisará fazer isso diretamente do repositório simples.

Nota novamente: você precisará garantir que a ramificação que deseja redefinir no repositório simples seja a ramificação ativa. Caso contrário, siga a resposta do VonC sobre como atualizar a ramificação ativa em um repo bare quando você tiver acesso direto ao repo.

Hazok
fonte
1
Conforme mencionado na resposta anterior ( stackoverflow.com/questions/4624881/… ), isso ocorre quando você tem acesso direto ao repositório simples (que você mencionou aqui). +1 embora.
VonC
@VonC Sim, absolutamente correto e obrigado por adicionar a nota! Eu continuo esquecendo de adicionar isso, já que estou assumindo que as redefinições são feitas diretamente no repositório. Além disso, estou assumindo que o ramo que a pessoa deseja redefinir no repositório simples é o ramo ativo. Se a ramificação não for a ramificação ativa, será necessário atualizar por sua resposta ( stackoverflow.com/questions/3301956/… ) sobre como atualizar a ramificação ativa para repo bare. Também atualizarei a resposta com as informações da filial ativa. Obrigado novamente!!!
Hazok 17/05
2

O SourceTree é uma GUI do git, que possui uma interface bastante conveniente para preparar apenas os bits que você deseja. Não possui nada remotamente semelhante para alterar uma revisão adequada.

Portanto, git reset --soft HEAD~1é muito mais útil do que commit --amendneste cenário. Posso desfazer a confirmação, recuperar todas as alterações na área de preparação e continuar ajustando os bits em etapas usando o SourceTree.

Realmente, parece-me que commit --amendé o comando mais redundante dos dois, mas o git é git e não se esquiva de comandos semelhantes que fazem coisas ligeiramente diferentes.

Roman Starkov
fonte
1

Embora eu realmente goste das respostas neste tópico, eu uso git reset --softpara um cenário um pouco diferente, mas muito prático, no entanto.

Eu uso um IDE para desenvolvimento, que tem uma boa ferramenta diff para mostrar alterações (em etapas e em etapas) após a minha última confirmação. Agora, a maioria das minhas tarefas envolve várias confirmações. Por exemplo, digamos que eu faça 5 confirmações para concluir uma tarefa específica. Eu uso a ferramenta diff no IDE durante cada confirmação incremental de 1 a 5 para examinar minhas alterações desde a última confirmação. Acho uma maneira muito útil de revisar minhas alterações antes de confirmar.

Mas no final da minha tarefa, quando eu quero ver todas as minhas alterações juntas (desde antes do 1º commit), para fazer uma auto-revisão de código antes de fazer uma solicitação pull, eu só veria as alterações do commit anterior (após o commit 4) e não alterações de todos os commits da minha tarefa atual.

Então, eu uso git reset --soft HEAD~4para voltar 4 commits. Isso me permite ver todas as alterações juntas. Quando estou confiante em minhas alterações, posso fazer git reset HEAD@{1}e enviá-lo para o controle remoto com confiança.

Swanky Coder
fonte
1
... faça git add --patche git commitcrie repetidamente a série de consolidação que você teria construído se soubesse o que estava fazendo o tempo todo. Seus primeiros commits são como as anotações em sua mesa ou o primeiro rascunho de um memorando, eles estão lá para organizar seu pensamento, não para publicação.
jthill
Ei, eu não entendi direito o que você está sugerindo.
Swanky Coder
1
Apenas isso, em vez de git reset @{1}restaurar sua série de primeiro rascunho, você pode criar uma série para publicação a partir de git add -pe git commit.
jthill
Sim, correto. Uma outra maneira é (a que eu costumo seguir) rebocar no upstream / master e compactar os commits em um único.
Swanky Coder
1

Outro caso de uso é quando você deseja substituir a outra ramificação pela sua em uma solicitação pull, por exemplo, digamos que você tenha um software com os recursos A, B, C em desenvolvimento.

Você está desenvolvendo com a próxima versão e você:

  • Recurso removido B

  • Adicionado recurso D

No processo, desenvolva apenas hotfixes adicionados ao recurso B.

Você pode mesclar o desenvolvimento para o próximo, mas às vezes isso pode ser confuso, mas também é possível usar git reset --soft origin/develope criar uma confirmação com suas alterações, e a ramificação é mesclável sem conflitos e mantém as alterações.

Acontece que git reset --softé um comando útil. Pessoalmente, eu o uso muito para compactar confirmações que não "concluíram o trabalho" como "WIP"; portanto, quando abro a solicitação de recebimento, todas as confirmações são compreensíveis.

cnexans
fonte