Git
Chapters ▾ 2nd Edition

7.3 Git инструменти - Stashing и Cleaning

Stashing и Cleaning

Често докато работите по дадена част от вашия проект, нещата стават заплетени и в недовършен статус и може да поискате да спрете работата по текущия проблем за известно време за да свършите нещо друго, в друг клон на хранилището. Проблемът е в това, че не желаете да къмитвате половинчати работи само за да можете да се върнете към моментното им състояние по-късно. Отговорът на този проблем идва с командата git stash.

Stashing-ът е процес, при който Git взема вашия моментен статус на извършена работа (това са модифицираните проследявани файлове в едно с индексираните такива) — и го съхранява в нещо като стек от недовършени промени, които след това може да се приложат обратно по всяко време, дори и върху различен клон. Правейки това, работната директория се маркира като чиста и можете да превключите към друг клон. На stashing-а може да гледате като временно маскиране/скриване на промените.

Note
Миграция към git stash push

Към октомври 2017 в мейлинг листата на Git имаше оживена дискусия, в резултат на което git stash save сега се счита за отхвърлена (deprecated) и се препоръчва алтернативната git stash push. Основната причина за това е, че git stash push предоставя опцията за stashing на избрани pathspecs, нещо което git stash save не поддържа.

Обаче, имайте предвид, че git stash save няма да изчезне скоро като команда. Добре е да опитате да преминете към push алтернативата просто за да сте в тон с новата функционалност.

Stashing на промените

За да демонстрираме процеса по маскирането, ще поработим в нашия проект по няколко файла и можем да индексираме някои от промените. Ако изпълним git status, можем да видим нашия недовършен (dirty) статус:

$ 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

Сега имаме модифицирани файлове както и индексирани промени и искаме да превключим клоновете, но без да къмитваме. Ще се наложи да ги маскираме. За да вкараме нов stash в стека ни, изпълняваме git stash или git stash push:

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

Вече можем да видим, че работната ни директория е чиста, в статус clean:

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

В този момент, вече можем да превключим към друг клон, а промените ни ще се пазят в стека. За да видим какви stash-ове има там, изпълняваме git stash list:

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

В този случай можем да видим, че имаме два stash-а съхранени преди последния, така че имаме достъп до три предишни състояния на проекта. Можете да възстановите обратно последната маскирана работа използвайки командата от помощния изход отпечатан в момента, в който направихме маскирането: git stash apply. Ако искате да възстановите по-стара версия, може да я укажете с името ѝ, например така: git stash apply stash@{2}. Ако не укажете име, Git подразбира най-новия stash и опитва да приложи него:

$ 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")

Ще видите, че Git модифицира файловете и ги връща до състоянието им, в което са били по време на съхранението на stash-а. В този случай имахме чиста работна директория в момента, в който се опитахме да възстановим stash-а и се опитахме да го приложим върху същия клон, от който го записахме. Обаче, чистата работна директория и същия клон не са задължително условие за успешно прилагане на stash. В действителност можете да запишете stash в един клон, да превключите към друг по-късно и да опитате да възстановите stash-а в него. Също така можете да имате модифицирани и некъмитнати файлове в работната директория при опита да приложите stash — Git ще ви даде merge конфликти, ако нещо не се прилага чисто.

Може би сте забелязали, че файловете ви са възстановени, но файлът който беше индексиран преди вече не е индексиран. Ако искате да е, ще трябва да изпълните git stash apply с параметъра --index и Git ще се опита да възстанови и промените по индекса. Така че, ако бяхте изпълнили тази последна команда, ще се върнете в изцяло оригиналната позиция:

$ 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

Опцията apply само се опитва да приложи съхранената по-рано работа — но вие продължавате да я имате в стека. За да я махнете от там, може да изпълните git stash drop с името на stash-a, който искате да изтриете:

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

Друг вариант е да изпълните git stash pop, което прилага stash-а и веднага след това го изтрива от стека.

Креативен Stashing

Има няколко stash варианта, които могат да бъдат полезни. Първият от тях, който е доста популярен, е опцията --keep-index към командата git stash. Това указва на Git не само да включи в stash-а индексираното съдържание, но едновременно с това да го остави в индекса.

$ 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

Друга често използвана опция е да се stash-нат едновременно проследяваните и непроследяваните от Git файлове. По подразбиране, git stash ще съхрани в stash стека само променените и индексирани проследявани файлове. Ако укажете --include-untracked или -u, Git ще включи в новия stash и файловете, които не следи Обаче, включването на непроследяващи се файлове в stash-a все пак няма да добави изрично игнорираните файлове. За да присъедините и тях, използвайте флага --all (или само -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
$

Накрая, ако подадете параметъра --patch към командата, Git няма да включи всичко променено, а ще ви пита интерактивно кои от промените искате да включите в stash-а и кои да оставите в работната директория.

$ 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

Създаване на клон от Stash

Ако stash-нете някаква ваша работа, изоставите я за повече време и продължите да работите по клона, от който сте го направили — може да се окаже, че обратното ѝ прилагане причинява проблеми. Ако процесът по възстановяването се опита да промени файл, който е бил редактиран след stash-а, ще получите merge конфликт и ще трябва да го разрешите. Обаче, ако търсите по-лесен начин да тествате маскираните промени отново, можете да изпълните git stash branch <new branchname>, което ще създаде нов клон с избраното име, ще извлече в работната директория къмита в който сте били, когато сте направили stash-а, ще приложи обратно работата ви там и ще изтрие stash-а, ако се приложи успешно:

$ 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)

Това е лесен и удобен начин за възстановяване на stash-ната работа.

Почистване на работната директория

Накрая, може да не искате да маскирате определени файлове от работната директория а просто да се отървете от тях. Командата git clean ще ви помогне за това.

Чести случаи за подобна необходимост са генерирани временни файлови от външни инструменти или компилация, които искате да махнете, за да остане чист вариант на вашата работа.

Трябва да внимавате с тази команда, защото тя е проектирана да изтрива непроследявани файлове от работната директория. Ако решите по-късно, че те ви трябват, често няма връщане назад. По-безопасен вариант е git stash --all за да изтриете всичко, но да си го запазите за всеки случай в stash.

Ако решите да чистите въпреки тези съображения, използвайте git clean. Изпълнението на git clean -f -d премахва всички непроследявани файлове от работната директория, както и всички поддиректории, които могат да се окажат празни в резултат. Флагът -f означава force или “really do this” и се изисква, ако конфигурационната променлива на Git clean.requireForce не е изрично указана като false.

Ако все пак искате да видите предварително какъв ще е резултата от изпълнението на командата, добавете флага -n (или --dry-run) за да направите “dry run” и да видите какво ще се изтрие преди да е станало късно.

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

По подразбиране, git clean ще изтрие само файловете, които не се следят и не се игнорират. Всеки файл, който съвпада с маска от .gitignore или други ignore файлове няма да бъде изтрит. Ако искате да премахнете и тези файлове (като например .o файлове генерирани от компилатора) така, че да имате чиста версия на проекта, добавете флага -x към командата.

$ 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/

Ако не сте сигурни в крайния резултат, добре е винаги да използвате -n флага преди да почистите окончателно. Командата също така може да се изпълнява интерактивно с флага -i или “interactive”, което също може да е полезно като предпазно средство.

Така ще може лично да потвърждавате изтриването на всеки обект.

$ 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>

По този начин минавате през всеки файл постъпково или може да укажете шаблон за търсене интерактивно.

Note

Съществуват специфични ситуации, в които трябва да сте по-настойчиви за да накарате Git да ви почисти работната директория. Ако сте в работна директория, под която сте копирали или клонирали други Git хранилища (вероятно като под-модули), дори git clean -fd ще откаже да ги изтрие. В случаи като този, трябва да добавите втори -f флаг за да потвърдите.