Урок курса
Срочная задача, а текущая ещё не закончена
Git и GitHub с нуля: бесплатный курс с практикойПереключение на другую ветку с незакоммиченными правками — ситуация, которую мы уже встречали раньше: Git либо не даёт переключиться, либо переносит изменения в другую ветку, смешивая чужую работу. Stash — инструмент, который решает именно это: убрать незавершённую правку с дороги, не делая коммит раньше времени, и вернуть её когда нужно.
Stash как временное хранилище вне истории коммитов
Stash хранит незавершённые правки отдельно от обычной истории ветки: создание записи не продвигает main, и обычный git log текущей ветки её не показывает. При этом правки сохраняются относительно исходного HEAD, связь с базовым состоянием есть. Это локальная временная копия, а не опубликованный коммит работы.
Когда вы выполняете git stash push, Git берёт текущие tracked-изменения — то есть изменения уже отслеживаемых файлов в рабочей папке и в индексе — и упаковывает их во внутреннюю запись stash@{0}. Отслеживаемую часть рабочей папки и индекс Git возвращает к состоянию последнего коммита (HEAD). Если до stash у вас была только правка в уже отслеживаемом файле, то после git stash push git status покажет чистое рабочее дерево.
Однако на диске могут остаться файлы, которые Git не отслеживает — untracked и ignored. По умолчанию stash их не трогает: они лежат там, где лежали. Поэтому git status после stash не обязан быть полностью пустым, если в репозитории есть untracked-файлы.
Важно понимать три категории:
- Tracked-изменения (изменения файлов, которые уже есть в индексе) — сохраняются при обычном
git stash push. - Untracked-файлы (рабочие файлы, сейчас отсутствующие в индексе и не скрытые правилами игнорирования) — по умолчанию остаются на диске.
- Ignored-файлы (неотслеживаемые пути, скрытые правилами игнорирования) — остаются на диске в обеих формах, рассматриваемых здесь: обычном
git stash pushиgit stash push -u.
Это не правило для всех возможных опций stash.
Stash не привязан к ветке: запись можно применить в другой ветке. Но это не значит, что применение пройдёт без проблем — содержимое могло разойтись с текущим состоянием файлов, и тогда возникнет конфликт.
git stash push и git stash apply: полный цикл на примере README.md
Для отдельного опыта распакуйте весь git_practice.zip в новую пустую папку. Откройте терминал в её git_practice либо перейдите туда через cd "полный путь к новой папке/git_practice" со своим путём. Папки my_repo ещё нет. Создайте клон и настройте оба поля автора:
git clone remote/shared.git my_repo
cd my_repo
git config user.name "Student"
git config user.email "student@example.com"
git status
git log --oneline
Исходно чистая main, один коммит, README с заголовком «# Командный справочник», контактом «команда» и статусом «черновик». Других изменений, untracked-файлов и stash-записей в этом новом клоне нет. Запишите исходный ID вне клона. Не выполняйте git init.
В редакторе добавьте после исходного текста пустую строку и Автор: в процессе, сохраните файл с прежними окончаниями строк. Не делайте add или commit этой правки: работа ещё не закончена.
Шаг 1. Убедиться, что изменение есть
git status
Git покажет README.md как изменённый (modified). Коммитить рано — работа не закончена.
Шаг 2. Убрать правку во временное хранилище
git stash push
Git выведет что-то вроде Saved working directory and index state WIP on main: .... После этого:
git status
Покажет nothing to commit, working tree clean. Правка в README.md убрана — Git вернул файл к HEAD.
Шаг 3. Переключиться и сделать срочную правку
git switch -c hotfix
На hotfix README снова исходный. В редакторе добавьте в его конец пустую строку и Статус: исправлено, сохраняя исходные строки. Зафиксируйте срочное исправление и вернитесь:
git add README.md
git commit -m "Срочное исправление"
git status
git log --oneline
git switch main
hotfix теперь содержит отдельный новый коммит; main остаётся на исходном ID. Не объединяйте hotfix в main в этом опыте, не меняйте main и не создавайте других stash-записей до apply. После возврата README исходный и status чист. Команда возврата:
git switch main
Шаг 4. Вернуть припрятанную правку
git stash apply
Git восстанавливает изменения в рабочую папку. В данном опыте возвращается одна unstaged-строка Автор: в процессе; main остаётся на исходном коммите. В общем случае apply без дополнительных флагов не обещает восстановить прежнее распределение правок между индексом и рабочей папкой: проверяйте оба diff. Важно: apply не удаляет запись stash@{0} из хранилища — она остаётся там даже после успешного применения.
Шаг 5. Проверить результат
git status
git diff
git diff --staged
Откройте README.md и убедитесь, что добавленная строка («Автор: в процессе» в примере с учебным репозиторием) вернулась, неожиданных изменений нет, конфликтных маркеров нет. Успешное завершение apply ещё не означает, что содержимое точно то, что нужно — status и diff дают фактическую картину.
Шаг 6. Удалить запись, если она больше не нужна
git stash drop
Удаляет верхнюю запись stash@{0}. Выполнять это стоит только после того, как вы убедились в корректности результата. Если сомневаетесь или хотите сохранить резервную копию — запись можно оставить: её наличие не мешает работе.
Проверить, что история ветки main не получила лишних коммитов:
git log --oneline
Обычный log main по-прежнему содержит только исходный коммит. Срочный коммит остался в hotfix; временная правка README восстановлена как незакоммиченная. Запись stash хранится отдельно, а drop не коммитит возвращённую работу.
Флаг -u: три категории файлов и граница ignored
По умолчанию git stash push работает только с tracked-файлами. Что происходит с остальными — зависит от категории.
Tracked — файл уже есть в индексе (когда-то был добавлен через git add и попал в коммит или остался в индексе после add). Его изменения всегда включаются в stash при обычном git stash push. Обратите внимание: отслеживание определяется текущим состоянием индекса, а не тем, когда-то ли файл добавлялся. Если файл есть в индексе сейчас — он tracked.
Untracked — рабочий файл сейчас отсутствует в индексе и не скрыт ignore. Он мог раньше отслеживаться, затем быть снят с отслеживания и остаться на диске; прежний add не определяет его нынешнюю категорию. По умолчанию git stash push его игнорирует и оставляет на диске. Чтобы включить такие файлы в stash, нужен флаг -u:
git stash push -u
После этого untracked-файлы (которые не скрыты .gitignore) тоже попадут в хранилище и исчезнут с диска вместе с tracked-изменениями.
Ignored — файлы, которые соответствуют шаблонам .gitignore и не отслеживаются. Эти файлы не включаются в stash ни при обычном push, ни при push -u. В обеих рассматриваемых формах они остаются на диске.
Компактно разница выглядит так:
| Категория | Без флагов | С -u |
|---|---|---|
| Tracked (изменения) | ✓ сохраняется | ✓ сохраняется |
| Untracked (не в .gitignore) | остаётся на диске | ✓ сохраняется |
| Ignored (в .gitignore) | остаётся на диске | остаётся на диске |
Отдельный случай: если файл уже отслеживается (есть в индексе), но его путь совпадает с шаблоном .gitignore — он всё равно tracked. Правило игнорирования не прекращает отслеживание. Изменения такого файла попадут в stash при обычном push.
Конфликт при stash apply: запись сохраняется, разрешение — вручную
Если с момента git stash push ветка получила новые коммиты, которые изменили те же строки, что лежат в stash-записи, git stash apply может завершиться конфликтом. Механизм точно такой же, как при слиянии веток: Git расставляет маркеры прямо в файле.
Для отдельного конфликтного опыта создайте новый клон из новой распаковки ровно как в начале предыдущей секции, с обеими настройками автора и чистой main. Вместо добавления автора измените исходную строку на Статус: в работе, сохраните окончания строк и выполните git stash push. Не создавайте hotfix: на той же main измените восстановленную исходную строку на Статус: исправлено, затем выполните:
git add README.md
git commit -m "Срочное исправление"
git stash apply
git status
Обе версии меняют одну исходную строку. Apply создаёт конфликт; типичный блок в README:
<<<<<<< Updated upstream Статус: исправлено ======= Статус: в работе >>>>>>> Stashed changes
Соберите нужную итоговую строку, например Статус: исправлено, в работе, и удалите маркеры. Остальные исходные строки сохраните.
Ключевое поведение: при конфликте stash-запись не удаляется автоматически. stash@{0} остаётся в хранилище. Это сделано намеренно — вы не теряете данные даже если apply завершился с ошибками.
Порядок действий при конфликте:
- Открыть конфликтующий файл в редакторе.
- Убрать маркеры
<<<<<<<,=======,>>>>>>>и оставить нужное содержимое. - Добавить исправленный файл в индекс:
git add README.md
- Проверить итоговое состояние:
git status
git diff --staged
Убедиться, что маркеров нет, содержимое файла корректно.
- Только после подтверждения — удалить запись, если она больше не нужна:
git stash drop
Если результат вызывает сомнения, запись можно оставить — git stash drop никогда не вызывается автоматически. Хранить запись дольше необходимого не является ошибкой.
Задача 1
В двух независимых копиях одинаковое состояние: tracked README изменён, notes.txt не находится в индексе и не игнорируется, новый output.log не отслеживается и игнорируется. Что включается при stash push и stash push -u?
Задача 2
Apply вернул часть текста, но завершился конфликтом. Запись по-прежнему в stash list. Как обращаться с этой записью?
Задача 3
Самостоятельная практика:
Настоящая локальная практика Git на своём компьютере. Площадка не запускает команды и не проверяет ваш репозиторий.
Входные данные: Распакуйте git_practice.zip в новую папку и перейдите в git_practice. Клонируйте учебный remote:
git clone remote/shared.git my_repo
cd my_repo
git config user.name "Student"
git config user.email "student@example.com"
Каждую команду выполняйте отдельно; можно подставить свои значения автора. Clone не переносит локальные настройки автора, поэтому обе настройки нужны до первого нового коммита.
В репозитории ветка main, один коммит, README.md содержит «# Командный справочник», «Контакт: команда», «Статус: черновик». Исходный status чистый, других файлов и изменений в этом отдельном опыте нет.
Задача:
- Добавьте строку «Автор: в процессе» в конец
README.md(не коммитьте). - Убедитесь через
git status, что файл изменён. - Выполните
git stash push. Проверьте черезgit status, что рабочая папка чистая. - Создайте ветку
hotfixи переключитесь на неё (git switch -c hotfix). Добавьте строку «Статус: исправлено» в конецREADME.md, закоммитьте с сообщением «Срочное исправление». - Вернитесь на
main(git switch main). - Выполните
git stash apply. Проверьтеgit status,git diff,git diff --stagedи откройте README.md: строка «Автор: в процессе» должна вернуться, неожиданных правок и маркеров быть не должно. - Только после этой проверки, если временная копия больше не нужна, выполните
git stash drop. Убедитесь черезgit log --oneline, что историяmainне содержит stash-коммита.
Ожидаемый итог: На ветке main README.md содержит добавленную строку как незакоммиченное изменение; история main содержит только исходный коммит. Если после подтверждения результата вы выбрали drop, stash-хранилище пусто; при необходимости сохранить резервную запись её наличие не является ошибкой.
Платформа проверяет только ответ ниже; действия в своём репозитории и письменные ответы вы проверяете самостоятельно.
Контрольный случай:
В отдельном проекте на main исходный коммит содержит README.md без строки «План: проверить макет» и NOTES.md с текстом «Контакт: команда». Исходно status чистый. В README добавили указанную строку, выполнили git stash push и затем обычный git stash apply; применение прошло без конфликта. Далее в NOTES заменили контакт на «Контакт: редактор», выполнили только git add NOTES.md и git commit -m "Обновить контакт". После проверки README удалили запись через git stash drop. Других add или commit не было. Каков полный снимок двух файлов в последнем коммите main и положение строки плана?
Задача 4
Письменное объяснение:
Коллега говорит: «Зачем мне stash? Если нужно срочно переключиться, я просто сделаю быстрый коммит git commit -m "wip", переключусь, сделаю своё, а потом исправлю коммит через git commit --amend. Stash — лишнее усложнение». Объясните: (1) какое конкретное ограничение возникнет при попытке использовать --amend после возврата на исходную ветку с несколькими коммитами между wip и исправлением; (2) в чём преимущество stash для незавершённой работы, которую намеренно не хочется фиксировать в истории.
Платформа проверяет только ответ ниже; действия в своём репозитории и письменные ответы вы проверяете самостоятельно.
Контрольный случай:
В текущей ветке после собственного WIP уже создали ещё два коммита. Теперь хотят исправить именно старый WIP обычным commit --amend. Подходит ли этот способ?
Продолжить с сохранением прогресса
Перейдите к уроку в учебном кабинете.
