Skip to main content

Що таке git stash і коли його використовувати?

git stash тимчасово зберігає незакомічені зміни з робочого дерева і індексу в стек, очищує робоче дерево до стану поточного коміту і дозволяє застосувати їх пізніше.

Теорія

Коротко

  • Уявіть папку для чернеток: записки складаєш туди, стіл чистий для термінової задачі, потім дістаєш записки і все на своєму місці
  • Зберігає і staged, і unstaged зміни однією командою; додай -u щоб захопити і untracked файли
  • Базовий цикл: stash, переключитись або зробити pull, pop
  • pop застосовує stash і видаляє його зі стека; apply тільки застосовує, запис залишається
  • Коли незакомічені зміни заважають тобі перейти або зробити pull - stash. Якщо зміни вже стабільні - просто закомітти.

Швидкий приклад

bash
# На гілці 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 файлів:

bash
npm install new-dep # Створює untracked файли git stash # Ігнорує їх за замовчуванням git checkout other # Ці файли залишаються або зникають в дивний спосіб

Виправлення: git stash -u. Або переконайся що .gitignore покриває те що не потрібно відстежувати.

Stash, потім git pull --rebase без pop:

bash
git stash git pull --rebase origin main # Переписує базовий коміт git stash pop # Stash створювався на старій базі, конфлікти майже гарантовані

Stash зберігає diff відносно базового коміту на момент збереження. Після rebase база змінилась. Pop перед rebase, або rebase спочатку і pop після.

Припускати що pop завжди чисто прибирає запис при конфлікті:

bash
git stash pop # CONFLICT (content): Merge conflict in file.js # Applying stash failed, stash remains

Коли pop натрапляє на конфлікт, він застосовує зміни з маркерами але НЕ видаляє запис зі стека. Безпечніший шлях: git stash apply, вирішити конфлікти вручну, потім git stash drop.

Видалення не того stash за індексом:

bash
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-ив зміни утиліт, коли прилітає проблема у продакшені.

bash
# 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 лежить у стеку.

bash
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

bash
# Неправильний порядок 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-проблем зникнуть самі.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?