Chapters ▾ 2nd Edition

7.3 Ferramentas do Git - Fazendo Stash e Limpando

Fazendo Stash e Limpando

Frequentemente, quando você está trabalhando em parte do seu projeto, as coisas estão em um estado confuso e você quer mudar de branch por um tempo para trabalhar em outra coisa. O problema é que você não quer fazer um commit de um trabalho feito pela metade só para poder voltar a este ponto mais tarde. A resposta para esse problema é o comando git stash.

O stashing pega o estado sujo (dirty) do seu diretório de trabalho — ou seja, seus arquivos modificados rastreados e alterações preparadas (staged) — e o salva em uma pilha de alterações não concluídas que você pode reaplicar a qualquer momento (mesmo em um branch diferente).

Note
Migrando para git stash push

A partir do final de outubro de 2017, houve uma extensa discussão na lista de discussão do Git, na qual o comando git stash save está sendo descontinuado (deprecated) em favor da alternativa existente git stash push. O principal motivo para isso é que o git stash push introduz a opção de fazer stash de pathspecs selecionados, algo que o git stash save não suporta.

O git stash save não vai desaparecer tão cedo, então não se preocupe com ele sumindo de repente. Mas você pode querer começar a migrar para a alternativa push pelas novas funcionalidades.

Fazendo Stash do Seu Trabalho

Para demonstrar o stashing, você irá para o seu projeto e começará a trabalhar em alguns arquivos e, possivelmente, preparará (stage) uma das alterações. Se você rodar git status, poderá ver o seu estado sujo:

$ git status
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   index.html

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   lib/simplegit.rb

Agora você deseja mudar de branch, mas ainda não quer comitar o que tem trabalhado, então fará o stash das alterações. Para enviar um novo stash para sua pilha, execute git stash ou git stash push:

$ git stash
Saved working directory and index state \
  "WIP on master: 049d078 Create index file"
HEAD is now at 049d078 Create index file
(To restore them type "git stash apply")

Agora você pode ver que o seu diretório de trabalho está limpo:

$ git status
# On branch master
nothing to commit, working directory clean

Neste ponto, você pode mudar de branch e trabalhar em outro lugar; as suas alterações estão armazenadas na sua pilha. Para ver quais stashes você armazenou, você pode usar o comando git stash list:

$ git stash list
stash@{0}: WIP on master: 049d078 Create index file
stash@{1}: WIP on master: c264051 Revert "Add file_size"
stash@{2}: WIP on master: 21d80a5 Add number to log

Neste caso, dois stashes foram salvos anteriormente, então você tem acesso a três trabalhos em stash diferentes. Você pode reaplicar aquele que você acabou de fazer o stash usando o comando mostrado na saída de ajuda do comando stash original: git stash apply. Se você deseja aplicar um dos stashes mais antigos, você pode especificá-lo nomeando-o, assim: git stash apply stash@{2}. Se você não especificar um stash, o Git assume que é o stash mais recente e tenta aplicá-lo:

$ git stash apply
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   index.html
	modified:   lib/simplegit.rb

no changes added to commit (use "git add" and/or "git commit -a")

Você pode ver que o Git modifica novamente os arquivos que você reverteu quando salvou o stash. Neste caso, você tinha um diretório de trabalho limpo quando tentou aplicar o stash, e você tentou aplicá-lo no mesmo branch em que o salvou. Ter um diretório de trabalho limpo e aplicá-lo no mesmo branch não são necessários para aplicar um stash com sucesso. Você pode salvar um stash em um branch, mudar para outro branch mais tarde e tentar reaplicar as alterações. Você também pode ter arquivos modificados e não comitados no seu diretório de trabalho quando aplicar um stash — o Git informará conflitos de merge se algo não for mais aplicável de forma limpa.

As alterações nos seus arquivos foram reaplicadas, mas o arquivo que você havia preparado (staged) antes não foi preparado novamente. Para fazer isso, você deve executar o comando git stash apply com a opção --index para dizer ao comando que tente reaplicar as alterações preparadas. Se você tivesse rodado isso em vez do outro comando, teria retornado à sua posição original:

$ git stash apply --index
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   index.html

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   lib/simplegit.rb

A opção apply tenta apenas aplicar o trabalho do stash — você continua a tê-lo na sua pilha. Para removê-lo, você pode executar o git stash drop com o nome do stash a remover:

$ git stash list
stash@{0}: WIP on master: 049d078 Create index file
stash@{1}: WIP on master: c264051 Revert "Add file_size"
stash@{2}: WIP on master: 21d80a5 Add number to log
$ git stash drop stash@{0}
Dropped stash@{0} (364e91f3f268f0900bc3ee613f9f733e82aaed43)

Você também pode executar git stash pop para aplicar o stash e então imediatamente removê-lo da sua pilha.

Fazendo Stash Criativo

Existem algumas variantes de stash que também podem ser úteis. A primeira opção que é bastante popular é a opção --keep-index do comando git stash. Isso diz ao Git para não apenas incluir todo o conteúdo preparado (staged) no stash sendo criado, mas simultaneamente deixá-lo no index.

$ git status -s
M  index.html
 M lib/simplegit.rb

$ git stash --keep-index
Saved working directory and index state WIP on master: 1b65b17 added the index file
HEAD is now at 1b65b17 added the index file

$ git status -s
M  index.html

Outra coisa comum que você pode querer fazer com o stash é fazer o stash dos arquivos não rastreados, bem como dos rastreados. Por padrão, git stash fará o stash apenas de arquivos rastreados modificados e preparados. Se você especificar --include-untracked ou -u, o Git incluirá os arquivos não rastreados no stash sendo criado. No entanto, incluir arquivos não rastreados no stash ainda não incluirá arquivos explicitamente ignorados; para incluir também arquivos ignorados, use --all (ou apenas -a).

$ git status -s
M  index.html
 M lib/simplegit.rb
?? new-file.txt

$ git stash -u
Saved working directory and index state WIP on master: 1b65b17 added the index file
HEAD is now at 1b65b17 added the index file

$ git status -s
$

Finalmente, se você especificar a flag --patch, o Git não fará o stash de tudo o que está modificado, mas, em vez disso, perguntará interativamente quais das alterações você gostaria de fazer o stash e quais gostaria de manter no seu diretório de trabalho.

$ git stash --patch
diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index 66d332e..8bb5674 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -16,6 +16,10 @@ class SimpleGit
         return `#{git_cmd} 2>&1`.chomp
       end
     end
+
+    def show(treeish = 'master')
+      command("git show #{treeish}")
+    end

 end
 test
Stash this hunk [y,n,q,a,d,/,e,?]? y

Saved working directory and index state WIP on master: 1b65b17 added the index file

Criando um Branch a partir de um Stash

Se você fizer o stash de algum trabalho, deixá-lo lá por um tempo e continuar no branch do qual fez o stash, poderá ter problemas ao reaplicar o trabalho. Se a aplicação tentar modificar um arquivo que você modificou desde então, você terá um conflito de merge e terá que tentar resolvê-lo. Se você quiser uma maneira mais fácil de testar novamente as alterações no stash, pode rodar git stash branch <nome do novo branch>, que cria um novo branch para você com o nome do branch selecionado, faz checkout do commit em que você estava quando fez o stash do trabalho, reaplica seu trabalho lá e, em seguida, descarta o stash se for aplicado com sucesso:

$ git stash branch testchanges
M	index.html
M	lib/simplegit.rb
Switched to a new branch 'testchanges'
On branch testchanges
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   index.html

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   lib/simplegit.rb

Dropped refs/stash@{0} (29d385a81d163dfd45a452a2ce816487a6b8b014)

Este é um atalho útil para recuperar o trabalho em stash de forma fácil e trabalhar nele em um novo branch.

Limpando Seu Diretório de Trabalho

Por fim, você pode não querer fazer o stash de algum trabalho ou arquivos no seu diretório de trabalho, mas simplesmente se livrar deles; é para isso que serve o comando git clean.

Algumas razões comuns para limpar seu diretório de trabalho podem ser remover lixo (cruft) que foi gerado por merges ou ferramentas externas ou para remover artefatos de build a fim de executar um build limpo.

Você deve ter muito cuidado com este comando, já que ele foi projetado para remover arquivos do seu diretório de trabalho que não são rastreados. Se você mudar de ideia, frequentemente não há como recuperar o conteúdo desses arquivos. Uma opção mais segura é rodar git stash --all para remover tudo, mas salvar em um stash.

Assumindo que você realmente deseja remover arquivos inúteis (cruft) ou limpar seu diretório de trabalho, você pode fazer isso com git clean. Para remover todos os arquivos não rastreados no seu diretório de trabalho, você pode rodar git clean -f -d, que remove quaisquer arquivos e também quaisquer subdiretórios que se tornem vazios como resultado. O -f significa 'force' (forçar) ou “realmente faça isso”, e é obrigatório se a variável de configuração do Git clean.requireForce não for explicitamente definida como falsa.

Se você quiser ver o que ele faria, pode executar o comando com a opção --dry-run (ou -n), que significa “faça uma simulação (dry run) e me diga o que você teria removido”.

$ git clean -d -n
Would remove test.o
Would remove tmp/

Por padrão, o comando git clean removerá apenas os arquivos não rastreados que não são ignorados. Qualquer arquivo que corresponda a um padrão no seu .gitignore ou outros arquivos de ignore não será removido. Se você quiser remover esses arquivos também, como por exemplo remover todos os arquivos .o gerados a partir de um build para que possa fazer um build totalmente limpo, pode adicionar um -x ao comando clean.

$ git status -s
 M lib/simplegit.rb
?? build.TMP
?? tmp/

$ git clean -n -d
Would remove build.TMP
Would remove tmp/

$ git clean -n -d -x
Would remove build.TMP
Would remove test.o
Would remove tmp/

Se você não sabe o que o comando git clean fará, sempre o execute com um -n primeiro para verificar duas vezes antes de mudar o -n para um -f e fazê-lo de verdade. A outra maneira de ser cuidadoso com o processo é executá-lo com a flag -i ou “interactive”.

Isso rodará o comando clean num modo interativo.

$ git clean -x -i
Would remove the following items:
  build.TMP  test.o
*** Commands ***
    1: clean                2: filter by pattern    3: select by numbers    4: ask each             5: quit
    6: help
What now>

Dessa forma, você pode percorrer cada arquivo individualmente ou especificar padrões para exclusão interativamente.

Note

Há uma situação peculiar em que você pode precisar ser mais enérgico ao pedir ao Git para limpar o seu diretório de trabalho. Se por acaso você estiver em um diretório de trabalho sob o qual você copiou ou clonou outros repositórios Git (talvez como submódulos), até mesmo o git clean -fd se recusará a excluir esses diretórios. Nesses casos, você precisa adicionar uma segunda opção -f para enfatizar.