Esconder apenas mudanças encenadas no git - é possível?

367

Existe uma maneira de eu esconder apenas minhas alterações faseadas? O cenário com o qual estou tendo problemas é quando trabalhei em vários bugs em um determinado momento e tive várias alterações sem etapas. Gostaria de poder preparar esses arquivos individualmente, criar meus arquivos .patch e guardá-los até que o código seja aprovado. Dessa forma, quando for aprovado, eu posso esconder minha sessão inteira (atual), exibir o bug e enviar o código.

Estou fazendo isso da maneira errada? Estou entendendo mal como o git pode funcionar de outras maneiras para simplificar meu processo?

MrDuk
fonte
Sim, você provavelmente está fazendo coisas erradas para entrar nessa situação. Ainda é uma pergunta útil. Você realmente deve esconder ou ramificar antes de começar a próxima correção. A resposta tangencial stackoverflow.com/a/50692885 é provavelmente a melhor maneira de lidar com isso no git. Brincar com o esconderijo costuma fazer coisas estranhas na minha área de trabalho se eu tiver retirado commits do upstream.
Samuel Åslund

Respostas:

472

Sim, é possível com DOUBLE STASH

  1. Prepare todos os seus arquivos que você precisa esconder.
  2. Corra git stash --keep-index. Este comando criará um stash com TODAS as suas alterações (em etapas e não em etapas ), mas deixará as alterações em etapas no seu diretório de trabalho (ainda no estado em etapas).
  3. Corre git stash push -m "good stash"
  4. Agora o seu "good stash"tem SOMENTE encenado arquivos .

Agora, se você precisar de arquivos não faseados antes do stash, basta aplicar o primeiro stash ( aquele criado com--keep-index ) e agora poderá remover os arquivos nos quais você escondeu "good stash".

Aproveitar

Bartłomiej Semańczyk
fonte
Ele esconde alterações nos submódulos, mesmo que não sejam organizados. Existe uma maneira de contornar isso?
rluks
11
de alguma forma, isso deixou de fora todos os novos arquivos (mesmo organizados).
Aurimas
8
@ Aurimas, para esconder novos arquivos, você precisa usar o -uswitch.
Gyromite
2
quando você reaplicar o primeiro esconderijo e se tudo mudou para trás, enquanto você poderia estar interessado apenas em seus unstages mudanças usar git stash apply --indexopção. Isso tentará manter seu estado não (faseado). Agora é mais fácil remover as alterações indesejadas da árvore de trabalho.
Otomo
Embora eu não precisasse fazer exatamente o que essa resposta dizia, conhecer o sinalizador --keep-index foi muito útil.
Aaron Krauss
128

Com o último git, você pode usar a --patchopção

git stash push --patch  

git stash save --patch   # for older git versions

E o git solicitará que cada alteração em seus arquivos seja adicionada ou não ao stash.
Você acabou de responder youn


Alias UPD para DOUBLE STASH :

git config --global alias.stash-staged '!bash -c "git stash --keep-index; git stash push -m "staged" --keep-index; git stash pop stash@{1}"'

Agora você pode preparar seus arquivos e executar git stash-staged.
Como resultado, seus arquivos temporários serão salvos no stash .

Se você não deseja manter os arquivos temporários e deseja movê-los para o stash. Em seguida, você pode adicionar outro alias e executar git move-staged:

git config --global alias.move-staged '!bash -c "git stash-staged;git commit -m "temp"; git stash; git reset --hard HEAD^; git stash pop"'
Eugen Konkov
fonte
17
Tecnicamente, não responde à pergunta - mas é uma técnica muito boa que atinge o armazenamento seletivo.
Alexreardon
6
Concordo, está tudo bem, mas a idéia aqui com a pergunta é: JÁ Fiz todo esse trabalho de encenar as mudanças com as quais quero fazer alguma coisa (ostensivamente originalmente para confirmar, mas agora quero esconder), sem olhar apenas para faça tudo de novo.
Steven Lu
4
não funciona para arquivos recém-criados (obras sobre os arquivos modificados somente)
Derek Liang
@DerekLiang: Arquivos recém-criados não são rastreados. Você provavelmente deve marcar a -u|--include-untrackedopção degit-stash
Eugen Konkov 20/09
2
Na documentação : " save : esta opção foi descontinuada em favor do git stash push . Ela difere de 'stash push', pois não pode usar pathspecs e quaisquer argumentos que não sejam de opção formam a mensagem."
Borjovsky
53

Fiz um script que esconde apenas o que está sendo preparado no momento e deixa todo o resto. Isso é incrível quando começo a fazer muitas mudanças não relacionadas. Simplesmente prepare o que não está relacionado ao commit desejado e oculte exatamente isso.

(Obrigado a Bartłomiej pelo ponto de partida)

#!/bin/bash

#Stash everything temporarily.  Keep staged files, discard everything else after stashing.
git stash --keep-index

#Stash everything that remains (only the staged files should remain)  This is the stash we want to keep, so give it a name.
git stash save "$1"

#Apply the original stash to get us back to where we started.
git stash apply stash@{1}

#Create a temporary patch to reverse the originally staged changes and apply it
git stash show -p | git apply -R

#Delete the temporary stash
git stash drop stash@{1}
Joe
fonte
7
Gostaria de acrescentar que você pode transformar o script em um comando git, seguindo thediscoblog.com/blog/2014/03/29/custom-git-commands-in-3-steps #
Petr Bela
3
Isso é ótimo! Eu tweaked-lo para solicitar ao usuário uma descrição esconderijo se não introduzir um na linha de comando: gist.github.com/brookinc/e2589a8c5ca33f804e4868f6bfc18282
brookinc
11
Isso não funciona com o git 2.23.0.
Thnee 22/10/19
Obrigado, votei e o transformei em um alias aqui: stackoverflow.com/a/60875067/430128 .
Raman
35

TL; DR Basta adicionar o -- $(git diff --staged --name-only)seu <pathspec>parâmetro git

Aqui está uma linha simples:

git stash -- $(git diff --staged --name-only)

E para adicionar uma mensagem simplesmente:

git stash push -m "My work in progress" -- $(git diff --staged --name-only)

Testado nas v2.17.1 e v2.21.0.windows.1

Limitações:

  • Esteja ciente de que isso ocultará tudo, se você não tiver arquivos organizados.
  • Além disso, se você tiver um arquivo que está apenas parcialmente preparado (por exemplo, apenas algumas linhas alteradas, são preparadas enquanto outras não), o arquivo inteiro será ocultado (incluindo linhas não preparadas).
Somo S.
fonte
6
Eu acho que essa é a melhor opção para a situação descrita: fácil de entender e sem magia negra envolvida!
Luis
11
Isso é bem legal. Acabei criando um alias com isso!
Kalpesh Panchal 20/02
keep 'em votes comin 😉
Somo S.
@KalpeshPanchal você pode compartilhar seu apelido? Não sei como escapar, por isso não está interpretando corretamente.
Igor Nadj
2
@IgorNadj Sure! Aqui está: github.com/panchalkalpesh/git-aliases/commit/…
Kalpesh Panchal
15

Para realizar a mesma coisa ...

  1. Prepare apenas os arquivos nos quais você deseja trabalhar.
  2. git commit -m 'temp'
  3. git add .
  4. git stash
  5. git reset HEAD~1

Estrondo. Os arquivos que você não deseja estão escondidos. Os arquivos que você deseja estão prontos para você.

Michael
fonte
3
Isso é facilmente a melhor resposta e mais fácil de lembrar
Kevin
9

Nesse cenário, prefiro criar novas ramificações para cada problema. Eu uso um prefixo temp / então eu sei que posso excluir esses ramos mais tarde.

git checkout -b temp/bug1

Prepare os arquivos que corrigem o bug1 e os confirma.

git checkout -b temp/bug2

Em seguida, você pode escolher os commits das respectivas ramificações conforme necessário e enviar uma solicitação de recebimento.

Shamps
fonte
2
Embora seja bom saber sobre sons sofisticados de esconderijo, na prática isso parece uma abordagem com a qual provavelmente não vou trabalhar.
Ryanjdillon # 19/18
11
Use "git cherry-pick tmpCommit" para recuperar a confirmação temporária com uma confirmação de mesclagem ou "git merge tmpCommit" + "git reset HEAD ^" para obter as alterações sem a confirmação.
Samuel Åslund
11
Como essa resposta mostra às vezes, é melhor perguntar diretamente o que você deseja alcançar, em vez de como alcançá-lo com a técnica especificada. Ramos temporários e seleção de cereja são úteis em situações complicadas.
precisa saber é o seguinte
se você preparou um arquivo parcialmente, precisará ocultar suas alterações antes de voltar para a ramificação original e exibi-las novamente
jan-glx
6

Por que você não confirma a alteração para um determinado bug e cria um patch a partir dessa confirmação e de seu predecessor?

# hackhackhack, fix two unrelated bugs
git add -p                   # add hunks of first bug
git commit -m 'fix bug #123' # create commit #1
git add -p                   # add hunks of second bug
git commit -m 'fix bug #321' # create commit #2

Em seguida, para criar os patches apropriados, use git format-patch:

git format-patch HEAD^^

Isso criará dois arquivos: 0001-fix-bug-123.patche0002-fix-bug-321.patch

Ou você pode criar ramificações separadas para cada erro, para mesclar ou refazer as correções individualmente ou até excluí-las, se não derem certo.

knittl
fonte
2

git stash --keep-index é uma boa solução ... exceto que não funcionou corretamente nos caminhos que foram removidos, corrigidos no Git 2.23 (terceiro trimestre de 2019)

Veja commit b932f6a (16 Jul 2019) por Thomas Gummerer ( tgummerer) .
(Incorporado por Junio ​​C Hamano - gitster- in commit f8aee85 , 25 de jul de 2019)

stash: corrija o manuseio de arquivos removidos com --keep-index

git stash push --keep-index deve manter todas as alterações que foram adicionadas ao índice, tanto no índice quanto no disco.

Atualmente, isso não se comporta corretamente quando um arquivo é removido do índice.
Em vez de mantê-lo excluído no disco, ** - keep-index atualmente restaura o arquivo. **

Corrija esse comportamento usando ' git checkout' no modo sem sobreposição, que pode restaurar fielmente o índice e a árvore de trabalho.
Isso também simplifica o código.

Observe que isso substituirá arquivos não rastreados se o arquivo não rastreado tiver o mesmo nome que um arquivo que foi excluído no índice.

VonC
fonte
2

Armazenar apenas o índice (alterações em etapas) no Git é mais difícil do que deveria ser. Encontrei a resposta de @ Joe para funcionar bem e transformou uma pequena variação dela nesse alias:

stash-index = "!f() { \
  git stash push --quiet --keep-index -m \"temp for stash-index\" && \
  git stash push \"$@\" && \
  git stash pop --quiet stash@{1} && \
  git stash show -p | git apply -R; }; f"

Ele empurra tanto a encenado e mudanças unstaged em um esconderijo temporário, deixando a mudanças encenado sozinho. Em seguida, empurra as alterações faseadas para o esconderijo, que é o esconderijo que queremos manter. Argumentos passados ​​para o alias, como --message "whatever"serão adicionados a este comando stash. Por fim, ele exibe o armazenamento temporário para restaurar o estado original e remover o armazenamento temporário e, finalmente, "remove" as alterações armazenadas no diretório ativo por meio de um aplicativo de correção reversa.

Para o problema oposto de ocultar apenas as alterações não faseadas (alias stash-working), consulte esta resposta .

Raman
fonte
1

É absolutamente necessário trabalhar com vários bugs de uma só vez? E por "de uma só vez", quero dizer "ter arquivos editados para vários bugs ao mesmo tempo". Porque, a menos que você precise absolutamente disso, eu trabalharia apenas com um bug de cada vez em seu ambiente. Dessa forma, você pode usar ramificações e rebase locais, o que acho muito mais fácil do que gerenciar um stash / estágio complexo.

Digamos que o mestre esteja no commit B. Agora trabalhe no bug # 1.

git checkout -b bug1

Agora você está no ramo bug1. Faça algumas alterações, confirme, aguarde a revisão do código. Como é local, você não está afetando ninguém, e deve ser fácil fazer um patch a partir do git diffs.

A-B < master
   \
    C < bug1

Agora você está trabalhando no bug2. Ir para trás a principal com git checkout master. Faça um novo ramo git checkout -b bug2. Faça alterações, confirme, aguarde a revisão do código.

    D < bug2
   /
A-B < master
   \
    C < bug1

Vamos fingir que alguém comete E & F no mestre enquanto você está aguardando a revisão.

    D < bug2
   /
A-B-E-F < master
   \
    C < bug1

Quando seu código for aprovado, você poderá refazê-lo novamente para dominar com as seguintes etapas:

git checkout bug1
git rebase master
git checkout master
git merge bug1

Isso resultará no seguinte:

    D < bug2
   /
A-B-E-F-C' < master, bug1

Em seguida, você pode pressionar, excluir seu ramo bug1 local e pronto. Um bug de cada vez no seu espaço de trabalho, mas com o uso de ramificações locais, seu repositório pode lidar com vários bugs. E isso evita uma dança de palco / stash complicada.

Responda à pergunta de ctote nos comentários:

Bem, você pode voltar ao esconderijo para cada bug e trabalhar apenas com um bug por vez. Pelo menos, você economiza o problema de preparação. Mas, tendo tentado isso, acho pessoalmente problemático. Os stashes são um pouco confusos em um gráfico de log do git. E o mais importante, se você estragar alguma coisa, não pode reverter. Se você tem um diretório de trabalho sujo e abre um stash, não pode "desfazer" esse pop. É muito mais difícil estragar confirmações já existentes.

Então git rebase -i.

Ao refazer uma ramificação para outra, você pode fazer isso de maneira interativa (o sinalizador -i). Ao fazer isso, você tem a opção de escolher o que deseja fazer com cada confirmação. O Pro Git é um livro incrível, que também está online no formato HTML e tem uma boa seção sobre rebasing e squash:

http://git-scm.com/book/ch6-4.html

Vou roubar seu exemplo literalmente por conveniência. Finja que você tem o seguinte histórico de consolidação e deseja rebase e squash bug1 no master:

    F < bug2
   /
A-B-G-H < master
   \
    C-D-E < bug1

Aqui está o que você verá quando digitar git rebase -i master bug1

pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
#
# Commands:
#  p, pick = use commit
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

Para compactar todas as confirmações de uma ramificação em uma única confirmação, mantenha a primeira confirmação como "pick" e substitua todas as entradas "pick" subsequentes por "squash" ou simplesmente "s". Você também terá a oportunidade de alterar a mensagem de confirmação.

pick f7f3f6d changed my name a bit
s 310154e updated README formatting and added blame
s a5f4a0d added cat-file
#
# Commands:
#  p, pick = use commit
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit

Então, sim, esmagar é um pouco doloroso, mas eu ainda o recomendaria sobre o uso pesado de esconderijos.

Mike Monkiewicz
fonte
11
Obrigado pelo post detalhado! Isso resolve muitos dos meus problemas, com certeza - o único problema que vejo é que nossa equipe atual solicitou que mantivéssemos todas as entregas em um único commit. :(
MrDuk
11
Se eles não precisam ou desejam seu histórico de trabalho no repositório de produção, tudo bem: torne seu histórico de rastreamento menos histórico aplicando diffs em vez de mesclar ramificações. Você pode descobrir como manter uma ramificação principal decorada com o histórico mesclado real e fazer o seu trabalho real com isso, assim será fácil automatizar a geração das diferenças corretas.
jthill
2
Observe que git checkout master; git checkout -b bug2pode ser reduzido para git checkout -b bug2 master. O mesmo se aplica git checkout bug1; git rebase master; git checkout master; git merge bug1, que é idêntico ao git rebase master bug1; git push . bug1:master(concedida, o pushtruque não é óbvio)
knittl
11
Eu dei um passo a passo para stashing acima na resposta principal para que eu pudesse usar a formatação de fantasia
Mike Monkiewicz
6
Eu diminuí o voto porque isso não responde à pergunta original. Estou em um ramo trabalhando em algo e fiz uma alteração que acho que deve ser comprometida com o ramo de integração separadamente. Tudo o que quero fazer é encenar essa alteração e ocultá-la para que eu possa alternar para outra ramificação e confirmar separadamente, em vez da minha ramificação "trabalho em andamento" atual. (Aviso, git falando à frente.) É absurdo que isso seja tão difícil de fazer; Eu tenho que imaginar que isso é uma ocorrência comum . (Trabalho em um ramo e manchando uma rápida mudança que precisa ser feito e esquecer de mudar primeiro.)
jpmc26
0

Dos seus comentários à resposta de Mike Monkiewicz, sugiro usar um modelo mais simples: use ramos de desenvolvimento regulares, mas use a opção squash da mesclagem para obter um único commit em seu ramo mestre:

git checkout -b bug1    # create the development branch
* hack hack hack *      # do some work
git commit
* hack hack hack *
git commit
* hack hack hack *
git commit
* hack hack hack *
git commit
git checkout master     # go back to the master branch
git merge --squash bug1 # merge the work back
git commit              # commit the merge (don't forget
                        #    to change the default commit message)
git branch -D bug1      # remove the development branch

A vantagem deste procedimento é que você pode usar o fluxo de trabalho normal do git.

Rudi
fonte
Não vejo como essa resposta poderia ajudar. Não está relacionado à pergunta original.
frapen
0

TL; DR ;git stash-staged

Depois de criar um alias:

git config --global alias.stash-staged '!bash -c "git stash -- \$(git diff --staged --name-only)"'

Aqui git diffretorna lista de --stagedarquivos --name-only
e, em seguida, passamos esta lista como pathspeca git stashcommad.

De man git stash:

git stash [--] [<pathspec>...]

<pathspec>...
   The new stash entry records the modified states only for the files
   that match the pathspec. The index entries and working tree
   files are then rolled back to the state in HEAD only for these
   files, too, leaving files that do not match the pathspec intact.

Eugen Konkov
fonte
-1

Para remover uma alteração acidental, especialmente a exclusão de vários arquivos, faça o seguinte:

git add <stuff to keep> && git stash --keep-index && git stash drop

em outras palavras, esconda a porcaria e jogue-a fora com a ocultação.

Testado na versão 2.17.1 do git

wmax
fonte
um voto negativo sem um comentário não é útil para mim nem para o próximo leitor ... zaenks rabugento anon. Embora eu possa imaginar um problema com essa linha única: deve-se ter muito cuidado para não esquecer de adicionar todas as alterações desejadas ao índice, caso contrário, essas alterações importantes também serão excluídas. Mas, novamente, o uso não cuidadoso de qualquer ferramenta CLI pode ser muito perigoso para o tempo e o trabalho preciosos da pessoa, no pior dos casos.
Wmax