Прилетает code review, а три файла недоправлены, и двинуться может только один. Спрятать всё в stash и перепечатать остальные два — ритуал, по которому никто не скучает.
TL;DR: git stash push -m "зачем" -- <путь> прячет ровно указанные пути и оставляет остальное грязное дерево в покое. Изменения возвращаются через git stash pop (или один файл из любого stash вытаскивается через git restore --source stash@{0} -- <путь>). Форма с pathspec пришла в git 2.13, вышедший в мае 2017 — любой тулчейн последних восьми лет её имеет.
git stash push -m "app.conf prod tweak" -- app.conf
git stash list
# stash@{0}: On main: app.conf prod tweak
git stash pop Прячь файл, а не дерево.
Как спрятать в stash только один файл в git?
Голый git stash выметает всё рабочее дерево — каждая отслеженная правка уходит в stash, всё возвращается к HEAD. Это неверная форма, когда грязное дерево смешанное: один файл готов к передаче, остальное честно недоделано. Ответ — глагол push с pathspec, согласно документации git-stash:
# до: три грязных файла
$ git status --short
M app.conf
M notes.md
M main.py
# спрятать только app.conf
$ git stash push -m "app.conf prod tweak" -- app.conf
Saved working directory and index state On main: app.conf prod tweak
$ git status --short
M notes.md
M main.py app.conf вернулся к закоммиченному состоянию и осел в stash@{0}; notes.md и main.py не шелохнулись. Сообщение -m необязательно, но окупает свои три секунды, как только git stash list покажет больше одной записи — stash с именем «WIP» плохо стареют.
Две важные детали:
- Pathspec идёт после
--. Всё после двойного тире — пути, а не флаги. Та же конвенция, что иgit checkout -- <путь>, и она не декоративна: в старых версияхgit stash push -- main.pyиgit stash push main.pyразличались — голый путь мог быть прочитан неверно. - Невыслеженным файлам нужен
-u. У новорождённого файла нет отслеженной истории, которую можно спрятать, поэтому обычныйpushего пропускает.git stash push -u -- newfile.pyвключает его;-aидёт дальше и выметает ещё и игнорируемые.
Как спрятать часть одного файла?
Когда сам файл наполовину готов — три хороших hunk, один позорный — интерактивный поток режет его на куски:
git stash -p # или: git stash push -p Git проходит по каждому hunk и спрашивает Stash this hunk [y,n,q,a,d,j,g,/,e,p,?]?. Отвечайте y для hunk-ов, которые идут в stash, n для тех, что остаются. Результат — stash только с одобренным, остальное файла остаётся грязным в дереве. Та же hunk-за-hunk машина крутит и git add -p, так что рефлекс переезжает напрямую.
git stash push -- <путь> | git stash -p | |
|---|---|---|
| Зерно | целые файлы | отдельные hunk-и |
| Темп | одна команда, скриптуется | интерактивно, hunk за hunk |
| Повторяемость в CI | да (пути как аргументы) | нет — нужен человек у промпта |
| Лучше всего для | «этот файл, остальное нет» | «эта правка, та — нет» |
| С | git 2.13 (май 2017) | доступно давно |
Практическое правило из модели самой документации: pathspec, когда граница — сам файл, -p, когда она внутри него.
Как вернуть спрятанный файл?
git stash pop восстанавливает stash@{0} и удаляет запись. Это нормальный путь — но pop работает по принципу «всё или ничего» на stash, а при конфликте прерывается, сохраняя запись. Два более тонких варианта:
# 1. применить без удаления (безопасно повторяемо)
git stash apply stash@{0}
# 2. вытащить ОДИН файл из stash, запись остаётся
git restore --source stash@{0} -- app.conf
# старое написание той же операции (до 2.23):
git checkout stash@{0} -- app.conf Форма restore/checkout отвечает на случай «спрятал три правки вместе, а нужна теперь одна» — копирует спрятанное содержимое этого пути в рабочее дерево и оставляет stash@{0} стоять. Внимание: она перезаписывает копию в рабочем дереве; если текущие правки на этом пути дороги, сначала diff:
git diff stash@{0} -- app.conf Stash-ы хранятся как настоящие коммиты на стеке, похожем на reflog — потому stash@{0} принимает любой commit-ish синтаксис, и случайно сброшенный stash можно вытащить из reflog, пока сборщик мусора его не съел.
Когда stash — неправильный инструмент?
Stash — это черновик, не ветка: нет имени в git branch, нет review, нет показанного по умолчанию diff, а записи молча громоздятся до забвения. Если работа в процессе должна пережить смены контекста между машинами или днями, коммитьте её в ветку — приложенная история лучше безымянной кучи. Когда те припаркованные комиты должны будут сесть в другом месте, перевезёт их git cherry-pick. А если цель — отменить, а не припарковать, действует дерево решений из отменить последний коммит.
Честная книга поломок — всё из документированного поведения:
| Симптом | Причина | Фикс |
|---|---|---|
| Файла нет после stash | невыслеженные пути пропускаются | git stash push -u -- <путь> |
error: Your local changes ... would be overwritten на pop | путь изменился после stash | закоммитьте или спрячьте новое состояние, потом pop |
| Возвращённые правки исчезли | pop наткнулся на конфликт и прервался | разрешите, потом git stash apply |
| «Который это был stash?» | безымянная куча stash-ей | сообщение -m, каждый раз |
Майская заметка 2017 года, добавившая pathspec в git stash push, отметила восемь лет, а большинство мышечной памяти git stash старше неё. Стоит переучиться: выметание всего дерева — сегодня частный случай, не режим по умолчанию.
FAQ
Как спрятать в stash только один файл в git?
git stash push -m "заметка" -- путь/к/файлу — форма с pathspec прячет только указанные пути и не трогает остальные изменённые файлы. Нужен git 2.13 или новее (май 2017).
Как достать отдельный файл из stash?
git restore --source stash@{0} -- путь/к/файлу (или старая форма git checkout stash@{0} -- путь/к/файлу). Копирует спрятанную версию в рабочее дерево, не удаляя запись stash.
Почему git stash пропустил мой новый файл?
Невыслеженные (untracked) файлы не входят в обычный stash. Добавьте -u: git stash push -u -- путь/к/файлу. Игнорируемые файлы требуют -a.
— mrsaynothing
— mrsaynothing
Заметки об ИИ, Linux и self-hosting.
Следующий гайд — на почту
Одно письмо на пост. Починил — пошёл дальше.
что это?Field Notes #1: агенты управляют сайтом. Машина выпускает. Я одобряю.
Нравятся заметки? Я зарабатываю этим на жизнь. нанять меня