git stash: тайник для незавершённой работы

Рейтинг: 62.3% · 11 голосов
Самый подробный курс по Git - от первого коммита до объектной базы и packfiles. Терминал и первый репозиторий, ветки и слияние, конфликты, удалённая работа и форджи (GitHub, GitLab), rebase и переписывание истории, спасение кода через reflog и fsck, stash и worktree, bisect и blame, подмодули и монорепо, хуки и подпись коммитов, внутреннее устройство и масштаб. Для новичков и профи. Актуально на 2026 (Git 2.54, дорожная карта 3.0).
Ответить
Аватара пользователя
Pavel_Git
Сообщения: 52
Зарегистрирован: 11 май 2026, 05:31

git stash: тайник для незавершённой работы

Сообщение Pavel_Git »

Оглавление курса (52)
  1. Что такое контроль версий и почему победил Git
  2. Установка Git на Windows, macOS и Linux и первая настройка
  3. Терминал и как спрашивать у Git: выживание в командной строке
  4. Ментальная модель Git: три состояния, коммит-снимок и граф
  5. Первый репозиторий: init, add, commit, status
  6. Просмотр изменений: status, diff и индексация по частям
  7. История проекта: git log, диапазоны и поиск
  8. .gitignore: что не должно попасть в репозиторий
  9. .gitattributes: окончания строк, бинарники и атрибуты
  10. Ветки как указатели: branch, switch и detached HEAD
  11. Слияние веток: fast-forward против трёхстороннего merge
  12. Конфликты слияния: анатомия и уверенное разрешение
  13. Практикум: твой первый день с Git от нуля до push
  14. Удалённые репозитории: remote, fetch, базовый pull и refspec
  15. git push: безопасная отправка и защита от перезаписи
  16. Клонирование: shallow, partial clone, bundle и backfill
  17. GitHub, GitLab и форки: pull request, rulesets и защита веток
  18. Рабочие процессы команды: GitFlow, GitHub Flow, trunk-based
  19. Merge queue и merge trains: безопасное вливание в trunk
  20. Культура code review и Conventional Commits
  21. git reset: три дерева и отмена индексации и коммитов
  22. git revert: безопасная отмена в общей истории
  23. Правка последнего коммита: git commit --amend
  24. git rebase: пересадка истории и золотое правило
  25. Интерактивный rebase: переписать серию коммитов
  26. git cherry-pick: перенос отдельных коммитов
  27. Stacked diffs: цепочка зависимых PR
  28. Массовое переписывание истории: git filter-repo и git replace
  29. git reflog: журнал, который помнит всё
  30. Восстановление утерянных коммитов, веток и файлов
  31. Откат опасных операций: reset --hard, сломанный rebase, плохой merge
  32. git stash: тайник для незавершённой работы (вы здесь)
  33. git worktree: несколько рабочих деревьев одного репозитория
  34. git bisect: бинарный поиск регрессии
  35. git blame, mailmap и археология кода
  36. Теги, релизы, archive и bundle
  37. Подмодули: внешние репозитории внутри проекта
  38. Subtree, монорепозитории и sparse-checkout
  39. Git LFS и большие файлы
  40. Вспомогательные команды: clean, mv, rm, restore
  41. Псевдонимы и кастомизация .gitconfig
  42. Хуки Git: автоматизация на клиенте и сервере
  43. Подпись коммитов и тегов: SSH, GPG и Sigstore
  44. Внутреннее устройство I: объектная база Git
  45. Внутреннее устройство II: ссылки, HEAD, notes и reftable
  46. Внутреннее устройство III: индекс, packfiles, gc и обслуживание
  47. От SHA-1 к SHA-256: переход на новый хеш
  48. Огромные репозитории: partial clone, FSMonitor, Scalar
  49. Git в CI/CD: shallow, кеш, merge_group и безопасность
  50. Куда движется Git: дорожная карта 3.0 и экосистема
  51. Профессиональный setup: собираем рабочее окружение Git
  52. 30+ типичных ошибок Git и как из них выбираться
Знакомая боль. Ты сидишь по локоть в рефакторинге, половина файлов разобрана, ничего не компилируется. И тут в чат прилетает: "прод лежит, фикс срочно, в ветке hotfix". Закоммитить этот недострой стыдно и потом мучительно разгребать. Потерять - жалко, два часа работы. Стандартный Git не даёт переключить ветку, если правки конфликтуют с тем, что в целевой ветке. Вот ровно для этого момента и придуман тайник - git stash. Он сгребает все твои незакоммиченные изменения, прячет их в сторонку, возвращает рабочую копию к чистому HEAD, и ты налегке прыгаешь куда надо. Сделал хотфикс - вернулся, достал заначку, продолжил с того же места.

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

Спрятать изменения git: базовый цикл push, list, pop

Самое первое, что надо выучить - как спрятать изменения git и как их вернуть. Команда называется git stash push, хотя исторически работает и просто git stash (это синоним push без аргументов).

Код: Выделить всё

$ git status -s
 M src/auth.py
 M src/config.py
$ git stash push -m "недопиленный рефакторинг авторизации"
Saved working directory and index state On feature-auth: недопиленный рефакторинг авторизации
$ git status -s
$
Рабочая копия чистая, статус пустой. Изменения никуда не делись - они в тайнике. Флаг -m задаёт сообщение, и это не косметика: когда заначек становится три-четыре, без подписей ты не вспомнишь, где что. Привыкай писать -m с первого дня.

Смотрим, что лежит в тайнике - git stash list:

Код: Выделить всё

$ git stash list
stash@{0}: On feature-auth: недопиленный рефакторинг авторизации
stash@{1}: WIP on main: 7a3f9c1 правка README
Это стек. stash@{0} - самая свежая заначка, наверху. stash@{1} - предыдущая, под ней. Запись WIP on main - это автоматическое сообщение, которое Git ставит, если ты вызвал stash без -m (WIP = work in progress, в работе).

Достаём заначку обратно - git stash pop:

Код: Выделить всё

$ git stash pop
On branch feature-auth
Changes not staged for commit:
        modified:   src/auth.py
        modified:   src/config.py
Dropped refs/stash@{0} (a91b3e7c8d2f4a6b9e1c0d5f8a7b2e4c6d9f1a3b)
git stash pop делает две вещи: накладывает изменения на рабочую копию И выбрасывает заначку из стека (строка Dropped). Стек сдвигается: то, что было stash@{1}, становится stash@{0}.

Изображение

git stash apply против pop: когда что

Вот граблина, на которую напарываются почти все. git stash pop удаляет заначку сразу после наложения. А что если pop упрётся в конфликт? Тогда поведение зависит от ситуации, и легко остаться в подвешенном состоянии. Поэтому есть git stash apply - он накладывает изменения, но заначку НЕ трогает, она остаётся в стеке.

Код: Выделить всё

$ git stash apply stash@{0}
On branch feature-auth
Changes not staged for commit:
        modified:   src/auth.py
$ git stash list
stash@{0}: On feature-auth: недопиленный рефакторинг авторизации
Заначка на месте. Это страховка. Мой практический совет: если ты накладываешь stash на ту же ветку, где прятал, и уверен - смело git stash pop. Если накладываешь на другую ветку, на изменившийся код, или вообще не уверен, что наложится чисто - сначала git stash apply, проверь результат, собери, прогони тесты, и только потом git stash drop, чтобы убрать заначку вручную:

Код: Выделить всё

$ git stash drop stash@{0}
Dropped stash@{0} (a91b3e7c8d2f4a6b9e1c0d5f8a7b2e4c6d9f1a3b)
Снести всё разом - git stash clear. Опасная команда, без подтверждения и без возможности отмены через интерфейс stash. Заначки исчезнут из списка. Спойлер: физически объекты ещё какое-то время живут в репозитории, и в конце урока я покажу, как вытащить их через fsck. Но рассчитывать на это как на штатный механизм нельзя.

А посмотреть, что именно лежит в заначке, не доставая её - git stash show:

Код: Выделить всё

$ git stash show stash@{0}
 src/auth.py   | 18 ++++++++++++------
 src/config.py |  3 ++-
 2 files changed, 13 insertions(+), 8 deletions(-)
$ git stash show -p stash@{0}
diff --git a/src/auth.py b/src/auth.py
index 3f9a1c2..7b4e8d0 100644
--- a/src/auth.py
+++ b/src/auth.py
@@ -41,9 +41,15 @@ def authenticate(user, token):
-    if token.expired:
+    if token is None or token.expired:
Без флагов show даёт сводку, как diffstat. С -p (от patch) - полный дифф, построчно. Привыкай к git stash show -p: это твой способ вспомнить, что вообще пряталось, прежде чем накатывать.

Механика: stash - это коммиты в refs/stash

Теперь главное, ради чего этот урок. stash - это не отдельная подсистема и не временный файл с патчем. Это настоящие коммит-объекты в твоей базе объектов, спрятанные под особой ссылкой.

Когда ты делаешь git stash push, Git создаёт два коммита (а если есть что прятать сверх отслеживаемых файлов - три):
  • Коммит индекса (staged). Снимок твоего индекса на момент заначки - то, что было в git add.
  • Коммит рабочей копии (WIP). Это и есть голова заначки, то, на что указывает stash@{0}. У него хитрая родословная: первый родитель - коммит, на котором стоял HEAD; второй родитель - тот самый коммит индекса. Дерево этого WIP-коммита = состояние твоей рабочей копии.
  • Коммит untracked (опционально, при -u или -a). Снимок неотслеживаемых файлов, цепляется третьим родителем.
Убедимся руками. refs/stash - это обычная ссылка, точно так же, как refs/heads/main:

Код: Выделить всё

$ cat .git/refs/stash
a91b3e7c8d2f4a6b9e1c0d5f8a7b2e4c6d9f1a3b
$ git cat-file -p stash@{0}
tree 6d8f1a3b9e2c4a7b0d5f8a3c6b9e1d4f7a2c5b8e
parent 7a3f9c1e4d2b8a6f0c9e3d1b5a8f2c7e4d6b9a1c
parent c4d9f1a3b6e8d2f4a7b9e1c0d5f8a3c6b9e1d4f7
author Khovanskiy <dev@cyberlake.ru> 1749900000 +0300
committer Khovanskiy <dev@cyberlake.ru> 1749900000 +0300

On feature-auth: недопиленный рефакторинг авторизации
Видишь два parent? Первый (7a3f...) - это HEAD на момент заначки. Второй (c4d9...) - снимок индекса. Это не случайность, это и есть конструкция тайника. Дерево WIP-коммита (6d8f...) хранит твою рабочую копию целиком. Поэтому stash переживает что угодно: смену веток, сборку мусора (пока на него есть ссылка), перезагрузку машины. Это полноценные объекты в .git/objects.

А где же история заначек, весь стек? В reflog ссылки refs/stash. stash@{0}, stash@{1} - это синтаксис обращения к reflog: "запись 0 в журнале refs/stash", "запись 1". Сам файл .git/refs/stash хранит только верхушку. Загляни:

Код: Выделить всё

$ cat .git/logs/refs/stash
0000... a91b3e7c On feature-auth: недопиленный рефакторинг авторизации
a91b3e7c 4f8d2a1b WIP on main: правка README
Вот почему git stash drop по сути правит reflog, а git stash clear - обнуляет журнал. И вот почему "удалённую" заначку можно найти: коммит-объект остаётся висеть в базе как недостижимый, пока его не подберёт сборка мусора.

Частичный stash, untracked и stash в новую ветку

Базу освоили. Теперь приёмы уровня профи.

Частичный тайник - git stash push --patch. Иногда спрятать надо не всё, а пару кусков. Флаг -p (или --patch) запускает интерактивный режим: Git показывает каждый ханк и спрашивает, прятать его или нет.

Код: Выделить всё

$ git stash push -p -m "только эксперимент с кешем"
diff --git a/src/cache.py b/src/cache.py
@@ -10,6 +10,8 @@
Stash this hunk [y,n,q,a,d,j,/,e,?]? y
Отвечаешь y - спрятать ханк, n - оставить в рабочей копии, q - выйти. Так ты раскладываешь грязную рабочую копию на "это в заначку, а это допилю сейчас".

Сохранить индекс - git stash push --keep-index. Флаг --keep-index прячет всё, но то, что было в git add, оставляет и в рабочей копии тоже. Классический сценарий: ты собрал staged-набор для коммита и хочешь прогнать тесты ровно на нём, без посторонних правок. Прячешь всё с --keep-index, гоняешь тесты на чистом staged-наборе, потом возвращаешь остальное.

Неотслеживаемые файлы. По умолчанию stash НЕ трогает untracked-файлы (новые, ещё не добавленные в git add). Это любимые грабли: спрятал, переключился, а новый файл models/new.py так и валяется в рабочей копии и мешает. Лечится флагами:
  • -u (--include-untracked) - прячет и неотслеживаемые файлы тоже.
  • -a (--all) - прячет ещё и игнорируемые (те, что под .gitignore). Применяй осторожно: легко утащить в заначку build/, node_modules/ и прочий мусор на гигабайты.

Код: Выделить всё

$ git stash push -u -m "фича + новый модуль"
Saved working directory and index state On feature-auth: фича + новый модуль
Тайник сразу в новую ветку - git stash branch. Бывает, спрятал заначку на feature-auth, а потом ту ветку перебазировали, и теперь stash pop даёт сплошные конфликты. Спасение - git stash branch:

Код: Выделить всё

$ git stash branch experiment-cache stash@{0}
Switched to a new branch 'experiment-cache'
On branch experiment-cache
Changes not staged for commit:
        modified:   src/cache.py
Dropped refs/stash@{0} (a91b3e7c8d2f4a6b9e1c0d5f8a7b2e4c6d9f1a3b)
Что произошло: Git создал новую ветку от того коммита, на котором делалась заначка (помнишь первый parent?), переключился на неё, наложил stash и убрал его из стека. Конфликтов нет, потому что база - ровно тот коммит, на котором ты прятал. Идеально, когда заначка "не хочет" ложиться на текущую ветку.

git stash export и import: перенос тайника между клонами (Git 2.51)

Исторически stash был приговорён жить на одной машине: refs/stash - локальная ссылка, push/fetch её не переносят. Хочешь перенести недострой с рабочего ноута на домашний - изворачивайся через git stash show -p > patch.diff и переноси патч руками, теряя разделение индекс/рабочая копия.

С Git 2.51 это в прошлом. Появились git stash export и git stash import. Идея: упаковать стек заначек в цепочку обычных коммитов, которую можно гонять штатными push/fetch.

Код: Выделить всё

$ git stash export --to-ref refs/stashes/laptop-wip
$ git push origin refs/stashes/laptop-wip
На второй машине:

Код: Выделить всё

$ git fetch origin '+refs/stashes/*:refs/stashes/*'
$ git stash import refs/stashes/laptop-wip
И весь твой стек заначек воспроизводится в локальном refs/stash, с сохранением разделения индекс/рабочая копия/untracked. Под капотом export строит специальное представление: каждая запись стека получает дополнительного родителя, ссылающегося на предыдущую запись. Так весь стек выстраивается в линейную цепочку коммитов, которую fetch и push гоняют как обычную ветку. Есть и git stash export --print - он печатает object id цепочки в stdout, не записывая никакую ссылку, удобно для скриптов. Заметь: export по умолчанию не выгребает заначки из локального стека, он их именно копирует в цепочку. Это твой штатный способ "перенести незавершёнку между клонами", больше не нужно костылять через diff-файлы.

Конфликты при pop и автостэш при rebase и pull

Что если pop наложился с конфликтом? Это нормально: заначка - это дифф, а код под ней мог уехать.

Код: Выделить всё

$ git stash pop
Auto-merging src/auth.py
CONFLICT (content): Merge conflict in src/auth.py
The stash entry is kept in case you need it again.
Ключевая строка - последняя: The stash entry is kept. При конфликте git stash pop НЕ удаляет заначку, даже хотя это pop. Это умышленно: пока ты не разрулил конфликт, выбрасывать данные нельзя. Разрешаешь конфликты руками (правишь файлы, git add), а потом - внимание - заначку придётся снести вручную через git stash drop, автоматически она не уйдёт.

Теперь автостэш. У rebase и pull есть встроенный мини-stash для удобства - флаг --autostash. Допустим, у тебя грязная рабочая копия, а надо подтянуть изменения:

Код: Выделить всё

$ git pull --rebase --autostash
Created autostash: 3f8a1c2
...
Applied autostash.
Git сам спрятал твои правки, сделал rebase, вернул правки обратно. Без --autostash он бы просто отказался: "у тебя незакоммиченные изменения". Включи это поведение глобально, чтобы не думать:

Код: Выделить всё

$ git config --global rebase.autostash true
$ git config --global pull.rebase true
Важно: autostash тоже может упереться в конфликт при возврате. Тогда твоя автозаначка сохраняется в обычном стеке (git stash list её покажет), и ты доразруливаешь руками. Так что autostash - не магия, а тот же stash, просто вызванный за тебя.

Восстановление удалённого тайника через fsck

Сделал git stash clear или drop, а через пять минут понял, что зря. Не паникуй. Коммит-объекты заначки ещё живут в базе как недостижимые (unreachable), пока их не собрал git gc. Ищем их через git fsck:

Код: Выделить всё

$ git fsck --no-reflogs | grep commit
dangling commit a91b3e7c8d2f4a6b9e1c0d5f8a7b2e4c6d9f1a3b
Строка dangling commit - кандидаты. Проверяем каждый через git show, и когда нашли нужный WIP-коммит - возвращаем как заначку:

Код: Выделить всё

$ git stash apply a91b3e7c8d2f4a6b9e1c0d5f8a7b2e4c6d9f1a3b
Можно подать stash apply прямо object id коммита-заначки, и он развернёт его как обычную заначку. Это работает именно потому, что stash - это коммиты: пока объект цел, его можно восстановить. Поэтому: не делай git gc --prune=now в панике, сначала найди потерянное.

Типичные грабли
  • Забыл -u, untracked-файлы остались в рабочей копии и мешают переключению. Привыкай к git stash push -u, когда создаёшь новые файлы.
  • git stash pop при конфликте оставляет заначку - и народ думает, что pop сломался. Нет, это by design, дропни вручную после разрешения.
  • Несколько заначек, все без -m, в списке сплошные WIP on main. Через день не разберёшь. Всегда -m.
  • git stash clear необратим через интерфейс stash. Перед clear лучше export или хотя бы git stash list скопировать.
  • stash -a утаскивает игнорируемое - можно случайно спрятать build/ на гигабайты и раздуть репозиторий. Применяй -a осознанно.
  • Заначка - это короткоживущий тайник, а не система версий. Если правки живут дольше дня - заведи нормальную ветку (git stash branch как раз для этого).
Мини-лаба: повтори руками
  • Заведи песочницу: mkdir stash-lab, git init -b main, создай файл a.txt с парой строк, git add, git commit.
  • Поменяй a.txt и создай новый b.txt (untracked). Сделай git stash push -m "проба". Проверь git status - b.txt остался? Да, untracked не спрятался.
  • Верни заначку: git stash pop. Теперь спрячь с git stash push -u -m "проба2" и убедись, что b.txt тоже ушёл в тайник.
  • Загляни внутрь: git cat-file -p stash@{0}. Найди оба parent и tree. Сколько parent при -u?
  • Сделай git stash branch lab-branch stash@{0}. Где оказались твои правки? На какой ветке ты теперь?
  • Создай новую заначку, дропни её через git stash drop, затем найди через git fsck --no-reflogs и восстанови git stash apply <id>.
  • Бонус: git stash export --to-ref refs/stashes/test, затем git log refs/stashes/test - посмотри на цепочку коммитов, в которую упаковался стек.
Контрольные вопросы
  • Сколько коммит-объектов создаёт git stash push без флагов и сколько с -u? Что хранится в каждом и кто кому родитель?
  • Чем git stash pop отличается от git stash apply и в каком случае pop НЕ удаляет заначку?
  • Где физически живёт стек заначек и почему stash@{0} и stash@{1} - это синтаксис обращения к reflog, а не к разным файлам?
  • Зачем нужен git stash export и чем он принципиально лучше переноса через git stash show -p > patch.diff?
Итог

git stash - это не временный буфер, а аккуратно спрятанные коммиты под ссылкой refs/stash, со стеком в её reflog. Понял это - и тайник перестаёт быть чёрным ящиком: ты видишь два-три коммит-объекта, понимаешь, почему pop при конфликте бережёт заначку, умеешь достать "удалённое" через fsck. Базовый цикл push -m, list, show -p, pop против apply, drop закрывает 90 процентов задач. Частичный stash через -p, --keep-index, перенос в ветку через stash branch и перенос между машинами через export/import (Git 2.51) - это уже профессиональный уровень. И помни главное правило: тайник для срочного переключения на час, а не хранилище на неделю. Живёт дольше дня - заводи ветку.
👍3 ❤️4 🔥1 😄 🤔
Аватара пользователя
Zbbra4
Сообщения: 1
Зарегистрирован: 13 май 2026, 18:08

Re: git stash: тайник для незавершённой работы

Сообщение Zbbra4 »

Всю жизнь делал git stash pop и удивлялся, почему при конфликте заначка не пропадает. Оказывается это by design, а не баг. Спасибо, дошло.
👍 ❤️ 🔥 😄 🤔
Аватара пользователя
nostracosa
Сообщения: 1
Зарегистрирован: 14 май 2026, 11:21

Re: git stash: тайник для незавершённой работы

Сообщение nostracosa »

А export/import реально удобная штука, перетащил недострой с рабочего ноута домой одной командой вместо возни с patch файлами. На 2.51 уже работает.
👍1 ❤️ 🔥1 😄 🤔1
Ответить
← Предыдущая глава
Откат опасных операций: reset --hard, сломанный rebase, плохой merge
Следующая глава →
git worktree: несколько рабочих деревьев одного репозитория

Все главы курса «Git профессионально: от первого коммита до внутреннего устройства»

Поделиться темой: ✈ Telegram VK
Похожие запросы: git stash: спрятать незавершённые изменения

Вернуться в «Git профессионально: от первого коммита до внутреннего устройства»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость