Ответ 1
TL; версия DR:
Вам нужно, чтобы каталог был чистым (в терминах git clean
) для правильного применения кошелька. Это означает, что вы выполняете git clean -f
или даже git clean -fdx
, что является своего рода уродливой вещью, потому что некоторые из неперехваченных или не отслеживаемых и проигнорированных файлов/каталогов могут быть элементами, которые вы хотите сохранить, а не удалять полностью. (Если это так, вы должны переместить их за пределами своего рабочего дерева вместо git clean
- отбросить их. Помните, что файлы, удаляемые git clean
, - это те, которые вы не можете получить из Git!)
Чтобы понять, почему, посмотрите на шаг 3 в описании "apply". Обратите внимание, что нет возможности пропустить ненужные и/или проигнорированные файлы в тире.
Основные факты о самом кошельке
Когда вы используете git stash save
с помощью -u
или -a
, stash script записывает свой пакетный портфель в качестве трехстороннего фиксации а не обычное двух-родительское.
Диаграммно, "сумка с чемоданом" обычно выглядит так: с точки зрения графика фиксации:
o--o--C <-- HEAD (typically, a branch)
|\
i-w <-- stash
o
- это любые старые обычные узлы фиксации, как и C
. Node C
(для Commit) есть письмо, чтобы мы могли назвать его: там, где висит "сумка для хранения".
Сам портфель представляет собой маленький треугольный мешок, висящий от C
, и он содержит две коммиты: w
- это фиксация дерева работы, а i
- фиксация индекса. (Не показано, потому что это просто сложно для диаграммы, является тот факт, что w
первый родительский C
, а второй его родительский i
.)
С --untracked
или --all
есть третий родительский элемент для w
, поэтому диаграмма выглядит примерно так:
o--o--C <-- HEAD
|\
i-w <-- stash
/
u
(эти диаграммы действительно должны быть изображениями, чтобы они могли иметь стрелки, а не ASCII-искусство, где стрелки трудно включить). В этом случае stash
- commit w
, stash^
- commit C
(еще еще HEAD
), stash^2
- commit i
, а stash^3
- commit u
, который содержит файлы "без следа" или даже "не отслеживаемые и проигнорированные" файлы. (На самом деле это не важно, насколько я могу судить, но я добавлю, что i
имеет C
в качестве родительского коммита, а u
- безболезненный или root-commit. для этой причины это просто то, что делает script, но это объясняет, почему "стрелки" (линии) такие же, как на диаграмме.)
Различные параметры в save
время
В режиме экономии времени вы можете указать любые или все из следующих параметров:
-
-p
,--patch
-
-k
,--keep-index
,--no-keep-index
-
-q
,--quiet
-
-u
,--include-untracked
-
-a
,--all
Некоторые из них подразумевают, переопределяют или отключают другие. Например, использование -p
полностью изменяет алгоритм, используемый script для создания stash, а также включает --keep-index
, заставляя вас использовать --no-keep-index
, чтобы отключить его, если вы этого не хотите. Он несовместим с -a
и -u
и будет выходить из строя, если дано какое-либо из них.
В противном случае между -a
и -u
, какой бы ни был установлен последний, сохраняется.
В этот момент script создается либо одна, либо две коммиты:
- один для текущего индекса (даже если он не содержит изменений), с parent commit
C
- с
-u
или-a
, безпользовательская фиксация, содержащая (только) либо невоспроизводимые файлы, либо все (незатребованные и проигнорированные) файлы.
stash
script затем сохраняет ваше текущее дерево работы. Он делает это с временным индексным файлом (в основном, новой промежуточной областью). С помощью -p
script считывает фиксацию HEAD
в новую промежуточную область, тогда эффективно 1 запускает git add -i --patch
, так что этот индекс завершается выбранными патчами. Без -p
он просто разворачивает рабочий каталог с сохраненным индексом для поиска измененных файлов. 2 В любом случае он записывает древовидный объект из временного индекса. Это дерево будет деревом для commit w
.
В качестве последнего шага создания шага, script использует только что сохраненное дерево, parent commit C
, фиксация индекса и корневой фиксации для необработанных файлов, если он существует, для создания окончательной фиксации stash w
. Тем не менее, script затем выполняет еще несколько шагов, которые влияют на ваш рабочий каталог, в зависимости от того, используете ли вы -a
, -u
, -p
и/или --keep-index
(и помните, что -p
подразумевает --keep-index
):
-
С
-p
:-
"Обратный патч" рабочего каталога, чтобы удалить разницу между
HEAD
и типом. По сути, это оставляет рабочий каталог только с теми изменениями, которые не были спрятаны (в частности, те, которые не заключены в commitw
, здесь все в commiti
). -
Только если вы указали
--no-keep-index
: запуститеgit reset
(без каких-либо параметров вообще, т.е.git reset --mixed
). Это очищает состояние "быть совершенным" для всего, не меняя ничего другого. (Конечно, любые частичные изменения, которые вы поставили перед запускомgit stash save -p
, сgit add
илиgit add -p
, сохраняются в commiti
.)
-
-
Без
-p
:-
Запустите
git reset --hard
(с помощью-q
, если вы тоже это указали). Это устанавливает дерево работы в состояние вHEAD
commit. -
Только если вы указали
-a
или-u
: запуститеgit clean --force --quiet -d
(с помощью-x
, если-a
, или без него, если-u
). Это удаляет все неиспользуемые файлы, включая неподготовленные каталоги; с-x
(т.е. в режиме-a
), он также удаляет все проигнорированные файлы. -
Только если вы указали
-k
/--keep-index
: используйтеgit read-tree --reset -u $i_tree
, чтобы "вернуть" сохраненный индекс как "изменения, которые необходимо зафиксировать", которые также отображаются в дереве работ. (--reset
не должен иметь эффекта, так как шаг 1 очистил дерево работы.)
-
Различные параметры в apply
время
Двумя основными подкомандами, восстанавливающими тайник, являются apply
и pop
. Код pop
запускает только apply
, а затем, если apply
преуспевает, запускается drop
, поэтому фактически существует только apply
. (Ну, есть также branch
, что немного сложнее, но в конце он также использует apply
.)
Когда вы применяете stash-любой "похожий на stash-объект", на самом деле, то есть все, что stash script может обрабатывать как сумка-стоп-ловушка, есть только две специфические опции:
-
-q
,--quiet
-
--index
(не--keep-index
!)
Другие флаги накапливаются, но в любом случае их все равно игнорируют. (Тот же код синтаксического анализа используется для show
, а здесь остальные флаги передаются на git diff
.)
Все остальное контролируется содержимым складок и статусом рабочего дерева и индекса. Как и выше, я буду использовать метки w
, i
и u
для обозначения различных коммитов в кошельке и C
для обозначения фиксации, из которой висит сумка.
Последовательность apply
выглядит так: если все идет хорошо (если что-то не срабатывает раньше, например, мы находимся в середине слияния или git apply --cached
не работает, ошибки script в этой точке)
- записать текущий индекс в дерево, убедившись, что мы не находимся в середине слияния
- только если
--index
: diff commiti
против commitC
, pipe togit apply --cached
, сохраните полученное дерево и используйтеgit reset
, чтобы отключить его - только если
u
существует: используйтеgit read-tree
иgit checkout-index --all
с временным индексом, чтобы восстановить деревоu
- используйте
git merge-recursive
, чтобы объединить дерево дляC
( "база" ) с тем, что было написано на шаге 1 ( "обновлено вверх по течению" ) и деревом вw
( "спрятанные изменения" )
После этого момента он немного усложняется:-), поскольку это зависит от того, прошел ли слияние на шаге 4. Но сначала немного расширьте это.
Шаг 1 довольно прост: script просто запускает git write-tree
, что не удается, если в индексе есть несвязанные записи. Если дерево записи работает, результатом является идентификатор дерева ($c_tree
в script).
Шаг 2 является более сложным, так как он проверяет не только параметр --index
, но также и $b_tree != $i_tree
(т.е. существует разница между деревом для C
и деревом для i
) и что $c_tree
!= $i_tree
(т.е. существует разница между деревом, выписанным на шаге 1, и деревом для i
). Тест для $b_tree != $i_tree
имеет смысл: он проверяет, есть ли какие-либо изменения для применения. Если нет изменений - если дерево для i
соответствует значению для C
- нет никакого индекса для восстановления, а --index
не нужен в конце концов. Однако, если $i_tree
соответствует $c_tree
, это означает, что текущий индекс уже содержит изменения, которые нужно восстановить с помощью --index
. Верно, что в этом случае мы не хотим git apply
те изменения; но мы хотим, чтобы они оставались "восстановленными". (Возможно, что пункт кода я не совсем понимаю ниже. Вероятнее всего, здесь есть небольшая ошибка.)
В любом случае, если шаг 2 должен запускаться git apply --cached
, он также запускает git write-tree
для записи дерева, сохраняя это в переменной script $unstashed_index_tree
. В противном случае $unstashed_index_tree
остается пустым.
Шаг 3 - это то, что происходит в "нечистом" каталоге. Если в stash присутствует фиксация u
, script настаивает на ее извлечении, но git checkout-index --all
не удастся, если какой-либо из этих файлов будет перезаписан. (Обратите внимание, что это делается с временным файлом индекса, который затем удаляется: шаг 3 вообще не использует обычную промежуточную область.)
(В шаге 4 используются три "волшебные" переменные среды, которые я не видел документально: $GITHEAD_t
предоставляет "имя" объединяемых деревьев. Для запуска git merge-recursive
script содержит четыре аргумента: $b_tree
--
$c_tree
$w_tree
. Как уже отмечалось, это деревья для базового commit C
, index-at-start-of-apply
и зафиксированная работа commit w
. string-names для каждого из этих деревьев, git merge-recursive
ищет в среде имена, сформированные путем добавления GITHEAD_
к необработанному SHA-1 для каждого дерева. script не передает никаких аргументов стратегии git merge-recursive
, ни пусть вы выберете любую стратегию, отличную от recursive
. Вероятно, она должна.)
Если у слияния есть конфликт, stash script запускает git rerere
(q.v.), и если --index
сообщает вам, что индекс не был восстановлен и завершен с статусом слияния-конфликта. (Как и в случае других ранних выходов, это предотвращает отбрасывание тэга pop
.)
Если слияние завершено успешно, выполните следующие действия:
-
Если у нас есть
$unstashed_index_tree
-ie, мы делаем--index
, и все остальные тесты на шаге 2 тоже прошли, тогда нам нужно восстановить состояние индекса, созданное на шаге 2. В этом случай простойgit read-tree $unstashed_index_tree
(без параметров) делает трюк. -
Если у нас нет чего-то в
$unstashed_index_tree
, script используетgit diff-index --cached --name-only --diff-filter=A $c_tree
для поиска файлов для добавления, запускаетgit read-tree --reset $c_tree
для слияния одного дерева с исходным сохраненным индексом и затемgit update-index --add
с именами файлов из более раннегоdiff-index
. Я не совсем уверен, почему он подходит к этим длинам (есть подсказка на странице руководстваgit-read-tree
, об избежании ложных ударов для модифицированных файлов, которые могут это объяснить), но что он делает.
Наконец, script запускает git status
(с выходом, отправленным в /dev/null
для режима -q
, не уверен, почему он вообще работает под -q
).
Несколько слов на git stash branch
Если у вас возникли проблемы с применением кошелька, вы можете превратить его в "реальную ветвь", что делает его гарантированным для восстановления (за исключением, как обычно, проблемы с приложением, содержащим фиксацию u
не применяя, если вы сначала очищаете неустановленные и, возможно, даже игнорируемые файлы).
Трюк здесь - начать с проверки commit C
(например, git checkout stash^
). Это, конечно, приводит к "отсоединенной HEAD", поэтому вам нужно создать новую ветку, которую вы можете комбинировать с шагом, который проверяет commit C
:
git checkout -b new_branch stash^
Теперь вы можете применить stash, даже с --index
, и он должен работать, так как он будет применяться к одному и тому же фиксатору. Шкатулка зависает от:
git stash apply --index
В этот момент все более ранние поэтапные изменения должны быть снова поставлены, а любые ранее не прошедшие этап (но отслеживаемые) файлы будут иметь свои неустановленные, но отслеживаемые изменения в рабочем каталоге. Теперь безопасно оставить бросок:
git stash drop
Использование:
git stash branch new_branch
просто выполняет указанную выше последовательность. Он буквально запускает git checkout -b
, и если это удастся, применяется тайник (с --index
) и затем его удаляет.
После этого вы можете зафиксировать индекс (если хотите), затем добавить и зафиксировать оставшиеся файлы, сделать два (или один, если вы оставите первый, индекс, фиксацию), "обычный", зафиксирует "регулярная" ветвь:
o-o-C-o-... <-- some_branch
\
I-W <-- new_branch
и вы конвертировали сумку i
и w
, которая совершает обычные, на ветке фиксирует i
и w
.
1 Вернее, он запускает git add-interactive --patch=stash --
, который напрямую вызывает perl script для интерактивного добавления, со специальным набором магии для стирки. Есть еще несколько магических режимов --patch
; см. script.
2 Здесь очень небольшая ошибка: git считывает $i_tree
, зафиксированное дерево индексов во временный индекс, но затем разграничивает рабочий каталог на HEAD
. Это означает, что если вы изменили какой-либо файл f
в индексе, а затем изменили его обратно в соответствии с версией HEAD
, дерево работы, хранящееся в w
в сумке-контейнере, содержит индексную версию f
вместо версии дерева f
.