Код
#статьи

Локальное хранилище Git: как работать с git stash

Упростите себе жизнь и не создавайте миллион ненужных веток в репозитории.

Кадр: мультсериал «Утиные истории» (2017–2021)

Разбираемся, зачем нужна команда git stash, какие у неё возможности и почему вам стоит применять её в работе уже сегодня.

Зачем «откладывать» код

Представьте, что вы садитесь утром за компьютер и видите новое сообщение: «Привет, Андрей! Не мог бы пофиксить этот дурацкий баг с кнопкой у нас на главной странице? Все подробности уже у тебя в задаче. Желательно сделать это сегодня до вечера». Вы приступаете к работе.

Пока вы тестируете гипотезы в коде, вам прилетает новое задание: «Тут такое дело, нужно срочно пофискить другой баг на сервере. Из-за него у нас не работает оплата на сайте. Бросай всё и иди исправлять». Но у вас уже почти получилось сделать то задание — не удалять же весь код, который вы написали.

Некоторые срочные задачи реально могут быть срочными
Иллюстрация: Оля Ежак для Skillbox Media

Вариантов решения такой дилеммы несколько. Например, можно создать новую ветку и поместить туда все изменения — закоммитить. Но из-за этого в репозитории проекта появятся десятки веток с очень странными названиями: TEST 1, BUTTON FIX, «НЕ ТРОГАТЬ!!!» — и однажды он просто превратится в свалку.

Есть и второй способ — скопировать все файлы, в которых вы делали изменения, и переименовать их понятным для вас образом: «имя_файла_ФИКС-БАГА». Но это нужно будет делать с каждым файлом — и, в принципе, это очень неудобно. Поэтому такой вариант тоже не подходит.

Часто простое решение не самое лучшее
Иллюстрация: Оля Ежак для Skillbox Media

Хотелось бы что-то автоматизированное и удобное. Именно для этого и придумали команду git stash.

Что делает команда git stash

Команда git stash упрощает работу с изменениями в коде. Она помогает быстро сохранить всё в архив и скрыть правки, чтобы переключиться на другую ветку и продолжить работу без них.

При этом изменения не добавляются в репозиторий в виде коммита: они складываются в локальное хранилище у вас на компьютере. Этим git stash и удобна.

Теперь давайте вернёмся снова к нашему примеру. Мы поняли, что нам нужно где-то сохранить все изменения и пойти фиксить другой срочный баг. Давайте же это сделаем.

git stash save

Итак, код ещё не дописан, поэтому коммитить его в основную ветку — бессмысленно. Значит, нам нужно сохранить изменения локально. Для этого воспользуемся командой git stash save:

$ git status
On branch main
Changes not staged for commit:

    modified:   index.html
    modified:   style.css

$ git stash save
Saved working directory and index state WIP on main: 4532d67 our new homepage
HEAD is now at 4532d67 our new homepage

$ git status
On branch main
nothing to commit, working tree clean

Мы видим, что изначально у нас были изменения в файлах: index.html и style.css. А после запуска команды git stash save все изменения пропали — но это не навсегда.

Теперь все правки лежат в локальном хранилище, а наш проект как бы находится в состоянии, в котором он был до того, как мы начали фиксить баг. В подобный локальный архив можно положить сколько угодно сохранений. Все они будут лежать там в виде списка, или, лучше сказать, стека, работающего по принципу: «Первый вошёл — первый вышел».

Локальное хранилище — как корзина: вы видите только то, что находится сверху. А если нужно что-то снизу, придётся поискать
Иллюстрация: Оля Ежак для Skillbox Media

Хорошо, мы можем сложить много изменений в хранилище — но как мы потом разберём, какое именно сохранение нам нужно достать. Хотелось бы как-то подписать все эти сейвы. Для этого нужно всего лишь указать в кавычках имя сохранения:

$ git stash save "Bug Fix: Main page"

Ещё может так случиться, что мы добавили новые файлы, которых изначально в проекте не было, — следовательно, их нам тоже нужно будет спрятать. Делается это всё той же командой git stash save, но с добавлением параметра --include-untracked, или -u:

$ git stash save -u "Bug Fix: Main page"

Если нужно добавить в локальное хранилище все игнорируемые файлы, то используем параметр --all, или -a:

$ git stash save -a "Bug Fix: Main page"

Есть ещё более продвинутый способ добавления отдельных файлов через git stash — с помощью параметра --patch, или -p. Если указать его, Git будет предлагать вам по очереди все файлы, в которых содержатся изменения, и спрашивать: добавлять ли их в локальное хранилище или нет.

$ git stash save -p "Bug Fix: Main page"

$ git stash -p
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..d92368b
--- /dev/null
+++ b/style.css
@@ -0,0 +1,3 @@
+* {
+  color: black;
+}
Stash this hunk [y,n,q,a,d,/,e,?]? y
diff --git a/index.html b/index.html
index 9daeafb..ebdcbd2 100644
--- a/index.html
+++ b/index.html
@@ -1 +1,2 @@
+<link rel="stylesheet" href="style.css"/>
Stash this hunk [y,n,q,a,d,/,e,?]? n

Чтобы понять, что означают все эти буквы при выборе, можно ввести знак ?, и появится справка:

$ git stash save -p "Bug Fix: Main page"

$ git stash -p
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..d92368b
--- /dev/null
+++ b/style.css
@@ -0,0 +1,3 @@
+* {
+  color: black;
+}
Stash this hunk [y,n,q,a,d,/,e,?]? ?

This lets you choose one path out of a status like selection. After choosing the path, it presents the diff between the index and the working tree file and asks you if you want to stage the change of each hunk. You can select one of the following options and type return: 
    y - stage this hunk 
    n - do not stage this hunk 
    q - quit; do not stage this hunk nor any of the remaining ones
    a - stage this hunk and all later hunks in the file 
    d - do not stage this hunk nor any of the later hunks in the file 
    / - search for a hunk matching the given regex 
    e - manually edit the current hunk 
    ? - print help

Основными опциями тут будут y и n, которые означают: откладывать файл или не откладывать.

Теперь, когда мы умеем локально сохранять изменения, пора научиться смотреть, какие из них уже находятся в локальном хранилище.

git stash list

Мы пошли делать ту самую срочную задачу и быстро её закончили. Пора возвращаться к нашим изменениям. Но для начала нужно посмотреть, что мы вообще закинули в локальное хранилище. Делается это командой git stash list:

$ git stash list
stash@{0}: "Bug Fix: Main page"
stash@{1}: WIP on main: 4532d67 our new homepage

Команда выводит список всех сохранений и их описания, которые задаются в кавычках после команды git stash save. Мы также видим, что у нас есть непонятное сохранение «WIP on main: 4532d67 our new homepage». Оно получило такое название по дефолту — просто потому, что мы не задали ему название в кавычках.

git stash show

Давайте теперь посмотрим, что находится внутри наших сохранений — а уже потом применим их к проекту. Используем команду git stash show:

$ git stash show
 index.html | 1 +
 style.css | 4 +++
 2 files changed, 5 insertions(+)

Итак, мы видим, что в этом сохранении есть два изменения в файле index.html и четыре — в файле style.css. А это как раз то, над чем мы и работали до возникновения другой, более срочной задачи.

Стоит сразу отметить, что когда мы ввели эту команду, то взяли самое верхнее локальное сохранение — «stash@{0}: "Bug Fix: Main page"». Оно берётся по умолчанию, так как у него индекс 0. Если нам нужно взять другое, придётся дописать к команде stash@{индекс_сохранения}:

$ git stash show stash stash@{1}
 index.html | 1 +
 style.css | 4 +++
 2 files changed, 5 insertions(+)

В нём оказались точно такие же изменения, но на самом деле это разные локальные сохранения. А ведь могло бы быть и так:

$ git stash show stash stash@{1}
 index.html | 8 +
 style.css | 2 +++
 2 files changed, 10 insertions(+)

Ещё не очень понятно, какие именно в этом сохранении изменения. Чтобы устранить эту неопределённость, воспользуемся параметром --patch, или -p, который покажет разницу между вашими файлами и изменёнными:

$ git stash show -p
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..d92368b
--- /dev/null
+++ b/style.css
@@ -0,0 +1,3 @@
+* {
+  color: black;
+  font-size: 16px;
+}
diff --git a/index.html b/index.html
index 9daeafb..ebdcbd2 100644
--- a/index.html
+++ b/index.html
@@ -1 +1,2 @@
+<link rel="stylesheet" href="style.css"/>

git stash apply

Теперь, когда мы нашли нужные изменения, пора их достать и применить к проекту. Для этого воспользуемся командой git stash apply. Она достанет из локального хранилища последнее сохранение:

$ git stash apply
On branch main
Changes not staged for commit:

    modified:   index.html
    modified:   style.css

Если мы хотим применить другое сохранение, то указываем его после слова apply:

$ git stash apply stash@{1}
On branch main
Changes not staged for commit:

    modified:   index.html
    modified:   style.css

git stash pop

После вызова команды git stash apply изменения всё ещё остаются в локальном архиве. Чтобы достать сохранение и полностью удалить его из хранилища, используем команду git stash pop:

$ git status
On branch main
nothing to commit, working tree clean
$ git stash pop
On branch main
Changes not staged for commit:

    modified:   index.html
    modified:   style.css

Dropped refs/stash@{0} (32b3aa1d185dfe6df7b3c3ccgb32cbf3e380ac6a)

Если теперь посмотреть список сохранений, последнего там уже не окажется:

$ git stash list
stash@{0}: WIP on main: 4532d67 our new homepage

Чтобы удалить конкретное сохранение, нужно указать его после слова pop:

$ git status
On branch main
nothing to commit, working tree clean
$ git stash pop stash@{1}
On branch main
Changes not staged for commit:

    modified:   index.html
    modified:   style.css

Dropped refs/stash@{0} (32b3aa1d185dfe6df7b3c3ccgb32cbf3e380ac6a)
$ git stash list
stash@{0}: "Bug Fix: Main page"

git stash branch

Допустим, мы всё же хотим создать новую ветку в репозитории со всеми изменениями из локального хранилища. Для этого есть команда git stash branch:

$ git stash branch new-main-page
Switched to a new branch 'new-main-page'
On branch new-main-page
Changes not staged for commit:

    modified:   index.html
    modified:   style.css

Dropped refs/stash@{1} (32b3aa1d185dfe6df7b3c3ccgb32cbf3e380ac6a)

И вот у нас уже готова новая ветка, в которую мы накатили все нужные изменения из локального хранилища. При этом сохранение в самом хранилище удалилось:

$ git stash list
stash@{0}: WIP on main: 4532d67 our new homepage

git stash drop

Бывает, что нужно удалить старые сохранения из локального хранилища, чтобы они не занимали лишнего места или просто не мешали. Это можно сделать с помощью команды git stash drop:

$ git stash list
stash@{0}: "Bug Fix: Main page"
stash@{1}: WIP on main: 4532d67 our new homepage
$ git stash drop stash@{1}
Dropped stash@{1} (17e2697fd8241df61651172b3d58c1f62aae7cdb)
$ git stash list
stash@{0}: "Bug Fix: Main page"

git stash clear

Наконец мы закончили работу над проектом на сегодня и запушили все коммиты, но список локальных сохранений до сих пор не пуст. Пора его полностью удалить. Применяем команду git stash clear:

$ git stash list
stash@{0}: "Bug Fix: Main page"
stash@{1}: WIP on main: 4532d67 our new homepage
$ git stash clear
$ git stash list

Что запомнить

  • git stash — это команда, которая позволяет записывать изменения в коде в локальное хранилище и возвращаться к ним позже. И всё это происходит на компьютере, а не в репозитории.
  • В это хранилище можно поместить сколько угодно записей. При этом над ними можно проводить разные действия — например, добавлять, удалять или просматривать их содержимое.
  • Базовые команды для git stash — это git stash save, git stash list и git stash apply. Они помогут вам сохранить изменения в хранилище, посмотреть список доступных сохранений и применить их к своему проекту.

Больше интересного про код в нашем телеграм-канале. Подписывайтесь!

Изучайте IT на практике — бесплатно

Курсы за 2990 0 р.

Я не знаю, с чего начать
Научитесь: Профессия Python-разработчик Узнать больше
Понравилась статья?
Да

Пользуясь нашим сайтом, вы соглашаетесь с тем, что мы используем cookies 🍪

Ссылка скопирована