Що таке git stash і коли його використовувати?
git stash тимчасово зберігає незакомічені зміни з робочого дерева і індексу в стек, очищує робоче дерево до стану поточного коміту і дозволяє застосувати їх пізніше.
Теорія
Коротко
- Уявіть папку для чернеток: записки складаєш туди, стіл чистий для термінової задачі, потім дістаєш записки і все на своєму місці
- Зберігає і staged, і unstaged зміни однією командою; додай
-uщоб захопити і untracked файли - Базовий цикл: stash, переключитись або зробити pull, pop
popзастосовує stash і видаляє його зі стека;applyтільки застосовує, запис залишається- Коли незакомічені зміни заважають тобі перейти або зробити pull - stash. Якщо зміни вже стабільні - просто закомітти.
Швидкий приклад
# На гілці feature з незакоміченими змінами
git stash push -m "WIP: валідація форми входу"
# Output: Saved working directory and index state WIP: валідація форми входу
git status
# On branch feature, nothing to commit, working tree clean
git stash pop
# Відновлює все як було
# Output: Dropped refs/stash@{0}От і весь патерн. Stash очищує дерево, ти робиш що потрібно, pop повертає все назад.
Коли використовувати
Stash добре підходить для кількох конкретних ситуацій:
- Посеред фічі прилетіло термінове виправлення: stash на feature-гілці, переходиш на main, фіксиш і комітиш, повертаєшся, pop
- Незакомічені зміни блокують
git pull: stash, pull, pop (конфлікти вирішуєш після, якщо з'являться) - Переключення надто швидке для нормального коміту: stash з описовим повідомленням замість напівготового коміту з "WIP" у назві
Не варто використовувати stash якщо очікуєш конфлікти при застосуванні - краще створи гілку. І якщо зміни вже стабільні - просто закомітти.
Як git stash працює всередині
Git створює два коміти під капотом. Перший фіксує стан індексу (staged файли) як дерево. Другий фіксує diff робочого дерева поверх нього. Обидва зберігаються в .git/refs/stash як reflog-стек, де stash@{0} завжди найновіший запис.
git stash push викликає git commit-tree для збереження цих станів, потім скидає HEAD, індекс і робоче дерево до базового коміту. git stash pop відтворює diff і прибирає запис з reflog якщо немає конфліктів.
Саме тому виникають конфлікти при pop: збережений diff створювався відносно одного базового коміту, але до моменту pop база могла змінитись.
Типові помилки
Забути -u при наявності untracked файлів:
npm install new-dep # Створює untracked файли
git stash # Ігнорує їх за замовчуванням
git checkout other # Ці файли залишаються або зникають в дивний спосібВиправлення: git stash -u. Або переконайся що .gitignore покриває те що не потрібно відстежувати.
Stash, потім git pull --rebase без pop:
git stash
git pull --rebase origin main # Переписує базовий коміт
git stash pop # Stash створювався на старій базі, конфлікти майже гарантованіStash зберігає diff відносно базового коміту на момент збереження. Після rebase база змінилась. Pop перед rebase, або rebase спочатку і pop після.
Припускати що pop завжди чисто прибирає запис при конфлікті:
git stash pop
# CONFLICT (content): Merge conflict in file.js
# Applying stash failed, stash remainsКоли pop натрапляє на конфлікт, він застосовує зміни з маркерами але НЕ видаляє запис зі стека. Безпечніший шлях: git stash apply, вирішити конфлікти вручну, потім git stash drop.
Видалення не того stash за індексом:
git stash list
# stash@{0}: WIP: нова фіча
# stash@{1}: WIP: старий експеримент
git stash drop stash@{1} # Індекси зміщуються при кожному push або popЗавжди запускай git stash list безпосередньо перед видаленням. Якщо записів багато, git stash branch new-branch stash@{0} надійніший ніж жонглювання індексами.
Де зустрічається в реальних проектах
- Контрибʼютори React stash-ають незакінчені зміни перед
git pull upstreamщоб merge залишався чистим (описано в CONTRIBUTING.md) - Контрибʼютори Node.js stash-ають незакомічені бенчмарки під час pull коли прилітають CI-фікси
- Розробники розширень VS Code stash-ають часткові TypeScript зміни між переключеннями гілок
git stashпротиgit worktree: stash для швидких переключень контексту; worktree краще коли потрібні дві гілки відкриті паралельно і надовго
Питання на співбесіді
Q: Яка різниця між git stash apply і git stash pop?
A: apply застосовує stash але залишає його в стеку. pop застосовує і видаляє. Використовуй apply коли не впевнений що застосування пройде без конфліктів - так залишається запасний варіант.
Q: Як git stash обробляє untracked файли?
A: За замовчуванням ігнорує їх. Прапор -u або --include-untracked включає untracked файли. Прапор -a або --all включає ще й файли з .gitignore.
Q: Що відбувається якщо git stash pop натрапляє на конфлікт?
A: Git залишає маркери конфліктів у файлах, а запис залишається в стеку. Pop видаляє запис тільки при чистому застосуванні. Вирішуєш конфлікти, робиш stage файлів, потім git stash drop.
Q: Чи можна stash-ати під час rebase або merge-конфлікту?
A: Під час активного merge-конфлікту git stash завершиться з помилкою. Використовуй git stash branch new-branch stash@{0} щоб створити гілку зі стану stash замість прямого застосування.
Q: Як stash зберігається всередині? Поясни граф комітів.
A: Два коміти: один для стану індексу (i), другий для diff робочого дерева поверх нього (w). Обидва вказують на HEAD на момент stash. Reflog у .git/logs/refs/stash веде стек. stash@{0} вказує на w, батько w - це i, батько i - оригінальний HEAD.
Q: Який найбезпечніший підхід коли git stash pop постійно конфліктує?
A: git stash branch new-branch stash@{0} - створює гілку точно в тому коміті де був зроблений stash, застосовує його там (без розбіжності баз) і видаляє якщо все чисто. Потім merge або rebase цю гілку звичайним способом.
Приклади
Переключення гілок посеред фічі
Редагуєш Login.jsx і stage-ив зміни утиліт, коли прилітає проблема у продакшені.
# Staged утиліти, unstaged зміни Login.jsx
git stash push -u -m "WIP: валідація форми входу"
# -u захоплює і untracked конфіг-файли
git checkout main
git checkout -b hotfix/auth-token
git commit -m "Fix: протермінований токен не очищував сесію"
git push origin hotfix/auth-token
git checkout feature/user-auth
git stash pop
# Login.jsx і утиліти відновились точно як булиЯкщо після pop з'явиться конфлікт, Git позначить файл маркерами <<<<<<<. Вирішуєш вручну, робиш stage, потім git stash drop.
Конфлікт stash після змін в upstream
Це спрацьовує навіть у досвідчених. Staged файл A, unstaged файл B, untracked файл C. Хтось пушить зміну в B поки твій stash лежить у стеку.
echo "A" > A.txt && git add A.txt
echo "B" > B.txt
echo "C" > C.txt # untracked
git stash push -u -m "часткова робота"
# Тим часом на remote: B.txt отримує новий коміт
git pull
git stash pop
# CONFLICT (content): Merge conflict in B.txt
# Applying stash failed, stash remains
# НЕ запускай git stash drop ще
# Вирішуєш B.txt вручну, потім:
git add B.txt
git stash drop # Тепер безпечноA.txt і C.txt відновились чисто. Конфлікт тільки по B.txt, бо і remote коміт, і stash торкались цього файлу. Бачив як розробники панікують від "stash remains" думаючи що дані втрачені - нічого не втрачено, Git просто чекає вирішення конфлікту перед очищенням.
Правильний порядок: stash і rebase
# Неправильний порядок
git stash
git pull --rebase origin main # База переміщується без твоїх змін
git stash pop # Stash тепер вказує на стару базу, конфлікти майже гарантовані
# Правильний порядок
git stash
git fetch origin
git rebase origin/main # Спочатку rebase
git stash pop # Pop на нову базуStash зберігає diff відносно базового коміту на момент збереження. Rebase переміщує цю базу. Дотримуйся правильного порядку і більшість stash-проблем зникнуть самі.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.