После git stash pop появились конфликтные маркеры, а изменения вернулись лишь частично. Сначала сохраним исходную запись и выясним, какие правки уже попали в рабочую папку. Затем разберём конфликт в отдельном учебном репозитории.

Зафиксируйте состояние до следующей команды

Если конфликт уже возник в рабочем проекте, остановитесь на чтении состояния. Повторный pop поверх незавершённого применения усложнит разбор: часть файлов могла восстановиться с первого раза. Понадобятся терминал, Git и редактор текста. Базовые status, diff, add и commit должны быть знакомы.

Начните с команд без изменения файлов:

git status --short
git stash list --format='%gd %H %s'
git diff
git diff --cached
git ls-files -u

git status показывает рабочую папку и индекс — набор изменений, подготовленных к коммиту. git diff показывает незакоммиченные изменения вне индекса, а git diff --cached сравнивает индекс с текущим коммитом. Во время конфликта вывод сложнее обычного: git ls-files -u поможет найти файлы с незавершённым слиянием.

Эти снимки отвечают на разные вопросы. Список stash показывает сохранённые записи, но по нему нельзя определить, что уже применилось к файлам. Рабочая папка показывает текущий результат, но не хранит полную историю ваших попыток.

Если текущая работа ценна, сначала выясните, где Git хранит её служебные данные:

git rev-parse --path-format=absolute --git-dir --git-common-dir

В обычном репозитории с каталогом .git сделайте копию всей папки вместе с этим скрытым каталогом средствами файловой системы. Храните её вне исходной папки. Одни текстовые патчи не сохраняют все виды работы: отдельно существуют неотслеживаемые файлы, двоичные данные и состояние индекса.

У связанного рабочего дерева Git — linked worktree — .git может быть файлом-указателем. Копия этого файла продолжает ссылаться на исходные административные данные и не создаёт независимый репозиторий. Для такого дерева сохраняйте рабочие файлы и отдельно фактические административные данные обоих путей из команды выше: собственные данные дерева и общие объекты со ссылками. Копию с прежними указателями не используйте для экспериментов как независимую: её команды могут обращаться к исходным данным.

У конфликтного репозитория копия полезнее ещё одной попытки спрятать всё в stash. Учебный стенд ниже создаёт обычный репозиторий с собственным каталогом .git.

Пока есть незавершённый конфликт, новый git stash push может отказать. Это ожидаемо: Git не может представить такой индекс как обычный набор подготовленных изменений. Разбирайте уже полученное состояние, сохранив копию, либо работайте с нужной записью в отдельной чистой папке.

Общие варианты применения stash разобраны в «Первой помощи с Git». Здесь пройдём более узкий маршрут: понять частичное применение и сохранить обе версии в конфликтном файле.

Выберите запись по содержимому и закрепите её

Не полагайтесь только на номер stash@{0}. Он обозначает верхнюю запись списка и может измениться после нового сохранения или удаления другой записи. Посмотрите описание, время и содержимое кандидата:

git stash list --date=local
git stash show --stat 'stash@{0}'
git stash show -p --include-untracked 'stash@{0}'

Кавычки вокруг stash@{0} делают команды предсказуемее в разных оболочках. Для конкретной записи запишите её идентификатор коммита. Если запись нужна для восстановления, можно дополнительно создать локальную резервную ссылку:

git rev-parse 'stash@{0}'
git branch backup/stash-restore 'stash@{0}'

Имя backup/stash-restore должно быть свободным: проверьте его через git branch --list backup/stash-restore. Если такое имя уже занято, выберите другое. Не переустанавливайте существующую ссылку принудительно.

Резервная ветка здесь указывает на коммит stash. Это технический якорь, а не готовая ветка для обычной разработки. Он удерживает сохранённый объект и позволяет обратиться к нему по устойчивому имени, даже если позиции в списке позже сместятся. Команда не меняет рабочие файлы.

У обычного stash первый родитель указывает на исходный коммит, второй сохраняет состояние индекса. Проверьте третьего родителя. Если запись включает неотслеживаемые файлы, они находятся в дополнительном родителе; без -u его может не быть.

В нашем стенде он есть, потому что мы сохраняем заметку с -u. Командой git show 'backup/stash-restore:settings.txt' можно прочитать настройки из stash, а командой git show 'backup/stash-restore^3:notes.txt' — сохранённый неотслеживаемый файл. Для другой записи сначала проверьте существование родителя через git rev-parse --verify 'backup/stash-restore^3'.

Если запись исчезла после успешного pop, текущий список уже не поможет найти её под прежним номером. Сначала сохраните нынешние файлы и любые известные идентификаторы, затем проверьте историю собственных команд и доступные записи журнала перемещений ссылок Git (reflog). Искать удалённые объекты нужно отдельно; результат зависит от сохранившихся ссылок и объектов. Не обещайте восстановление по одной универсальной команде.

Воспроизведите конфликт в новой папке

Следующий сценарий создаёт отдельный репозиторий с вымышленными настройками. Запускайте его из любого места: mktemp сам выделит новую папку. Рабочий проект скрипт не читает.

Сохраните файл setup.sh:

#!/bin/sh
set -eu
lab=$(mktemp -d "${TMPDIR:-/tmp}/stash-lab.XXXXXX")
printf 'Учебная папка: %s\n' "$lab"
cd "$lab"
git init -b main
git config user.name 'Training Author'
git config user.email 'training@example.invalid'
git config commit.gpgsign false
printf 'theme=light\nlimit=10\n' > settings.txt
git add settings.txt
git commit -m 'Add initial settings'
printf 'theme=dark\nlimit=10\n' > settings.txt
printf 'Проверить контраст\n' > notes.txt
git stash push -u -m 'Dark theme draft'
printf 'theme=system\nlimit=20\n' > settings.txt
git add settings.txt
git commit -m 'Use system theme and larger limit'
printf '\nСтенд готов. Выполните: cd "%s"\n' "$lab"
git status --short
git stash list

Запустите:

sh setup.sh

Локальная настройка commit.gpgsign false отключает подпись только в созданном учебном репозитории: стенду не нужен доступ к вашему ключу. В конце появится путь к учебной папке. Перейдите в неё командой cd, которую напечатал скрипт. Все дальнейшие команды относятся именно к этой папке. Убедитесь, что git log --oneline показывает два учебных коммита, а git stash list — запись Dark theme draft.

На исходном коммите настройка темы имела значение light, а лимит — 10. В stash лежат dark и заметка. Затем в ветке тему поменяли на system и подняли лимит до 20. Обе стороны тронули одну строку. Теперь Git не сможет выбрать тему автоматически.

Перед применением сделайте резервную ссылку и изучите патч:

git branch backup/stash-restore 'stash@{0}'
git stash show -p --include-untracked 'stash@{0}'
git -c stash.index=false stash apply 'stash@{0}'

Последняя команда завершится с ненулевым кодом и сообщением о конфликте settings.txt. Такой результат предусмотрен стендом. Запись stash остаётся в списке, потому что мы выбрали apply, и она понадобится для сравнения.

В этом опыте -c stash.index=false задаёт режим только для одной команды: Git не пытается восстановить прежнее распределение правок между индексом и рабочими файлами. Флаг --index включает такую попытку; настройка stash.index=true тоже может включить её без флага. Поэтому режим задаём явно. Сначала исправим сами файлы, затем подготовим нужные изменения к коммиту.

Разведите конфликт и успешно восстановленные файлы

После ошибки выполните:

git status --short
git ls-files -u
cat settings.txt
cat notes.txt
git stash list

settings.txt содержит конфликтные маркеры. При этом notes.txt уже появился и содержит строку Проверить контраст. После такого сбоя Git оставляет рабочую папку в состоянии частичного применения, поэтому каждый затронутый файл следует рассмотреть отдельно. Это особенно важно после pop: при конфликте запись сохраняется, а часть правок может уже находиться в рабочей папке.

Содержимое settings.txt будет похоже на это:

<<<<<<< Updated upstream
theme=system
limit=20
=======
theme=dark
limit=10
>>>>>>> Stashed changes

Границы конфликта и подписи зависят от ситуации и настроек Git; ориентируйтесь на собственный файл. Верхняя часть в этом воспроизведении относится к текущей ветке, нижняя — к отложенным изменениям. Маркеры отделяют версии, но не подсказывают требуемое бизнесом поведение.

В нашем учебном результате нужна тёмная тема из stash и новый лимит из ветки. Откройте файл редактором и замените конфликтный фрагмент целиком:

theme=dark
limit=20

Сохраните файл. Все маркеры должны исчезнуть. Проверять только наличие строки theme=dark недостаточно: случайно оставленный разделитель тоже попадёт в коммит как обычный текст.

Теперь проверьте изменения:

git diff --check
git diff -- settings.txt
git add settings.txt
git diff --cached -- settings.txt
git status --short

git add после редактирования сообщает Git, что конфликт в файле разрешён. Это ещё не подтверждение правильности значений. Поэтому перед коммитом смотрим подготовленный результат и выполняем проверку самого приложения, если у него есть тесты или проверка конфигурации.

У notes.txt в стенде особая роль: он был неотслеживаемым и вернулся таким же. Мы оставим его отдельно и закоммитим только settings.txt:

git commit -m 'Restore dark theme with current limit'

Список stash пока сохраняйте. Страховочную запись удобно удалять после проверки всех восстановленных файлов, включая те, что вы пока не хотите коммитить.

Проверьте результат по независимым признакам

Сравните итог с обеими версиями:

git show HEAD:settings.txt
git show 'backup/stash-restore:settings.txt'
git status --short
git ls-files -u
git stash list

Первая команда должна показать dark и 20, вторая — сохранённые dark и 10. У git ls-files -u вывода быть не должно: незавершённых конфликтов больше нет. Статус покажет неотслеживаемый notes.txt. Это ожидаемая оставшаяся работа, которую мы проверяем отдельно.

Сохраните следующий check.sh вне учебного репозитория. Затем перейдите в учебную папку в терминале и запустите скрипт, указав абсолютный путь к нему:

#!/bin/sh
set -eu
# Выполнять в учебном репозитории после разрешения конфликта.
test "$(git config user.email)" = 'training@example.invalid'
test "$(git show HEAD:settings.txt)" = "$(printf 'theme=dark\nlimit=20')"
test -f notes.txt
test "$(cat notes.txt)" = 'Проверить контраст'
test -z "$(git ls-files -u)"
test "$(git stash list --format=%s | wc -l | tr -d ' ')" -eq 1
printf 'PASS: итог закоммичен, notes.txt сохранён, конфликт снят, stash существует\n'

Проверка сверяет содержимое коммита, заметку, индекс и наличие stash. Если она завершилась ошибкой, найдите первый неуспешный test: он указывает, какое обещание пока не выполнено. Скрипт рассчитан на этот стенд, а не на произвольный репозиторий.

Найдите свой случай в таблице

Что вы видите Возможная причина Следующий безопасный шаг
No stash entries found В этой папке список пуст или запись уже удалена Уточнить репозиторий, сохранить текущую работу и известный идентификатор
Команда отказывается перезаписать локальные изменения Текущие файлы пересекаются с восстанавливаемыми Сохранить текущую работу отдельно, проверить diff и чистоту выбранной папки
needs merge В индексе остался конфликт Изучить status и ls-files -u, разрешить уже начатую операцию
Некоторые файлы вернулись, другие конфликтуют Применение дало частичный результат Сравнить файлы и завершить конфликт, не повторять pop вслепую
Новый файл отсутствует При сохранении он был неотслеживаемым и -u не использовали Проверить содержимое stash и другие копии; отсутствующий файл запись не содержит
Индекс отличается от ожидаемого Применили без --index либо индекс восстановить не удалось Проверить diff --cached, подготовить нужные файлы осознанно
Запись осталась после pop Применение завершилось конфликтом Сохранить запись до проверки результата
Номер записи поменялся В списке появились или исчезли записи Использовать проверенный идентификатор или резервную ссылку

Для игнорируемых файлов -u недостаточно: он включает неотслеживаемые файлы, но сохраняет обычные правила игнорирования. В примере notes.txt не игнорируется, поэтому его восстановление проверяемо. В рабочем проекте сначала посмотрите, что именно входит в запись, и избегайте массового сохранения неизвестных больших файлов или секретов.

Сохраните возможность вернуться к исходнику

У применения stash нет универсальной команды «отменить всё последнее», которая заодно угадает ваши прежние незакоммиченные изменения. После смешения двух наборов правок Git не знает, какой итог вы считаете правильным. Поэтому копия исходной папки и устойчивое имя записи так полезны до исправлений.

Другой маршрут — git stash branch <имя> <запись>. При успешном применении записи вида stash@{...} эта команда может удалить её из списка. Используйте её осознанно, из подходящего чистого состояния, с резервной ссылкой и проверенным именем новой ветки. В уже конфликтной папке сначала разберите текущий индекс. Для нашего учебного маршрута дополнительная ветка не нужна.

Не удаляйте stash ради чистого списка. Сначала сравните все нужные файлы и убедитесь, что есть коммит или другая проверенная копия результата. Если решите удалить запись, заново найдите её актуальную позицию по хешу и описанию; старый stash@{0} к этому времени может обозначать другую работу.

Для самостоятельной проверки повторите стенд и выполните git -c stash.index=false stash pop 'stash@{0}'. Убедитесь, что при конфликте запись осталась в списке, а заметка уже появилась. Затем во второй новой учебной папке измените только лимит в текущей ветке и проверьте, сможет ли Git объединить изменения без вашего выбора темы. До запуска запишите ожидаемый итог обеих строк.

Если хочется укрепить базовую работу с историей и индексом, выберите подходящий раздел в курсах HTML Academy. Текущий учебный репозиторий пока оставьте: сравнение HEAD, stash и рабочей папки полезно повторить ещё раз после перерыва.