На самом деле, git stash
действительно сохраняет текущее рабочее дерево.Проблема в том, что способ , который он сохраняет, не соответствует вашим потребностям.Существует также вторичная проблема, которая может быть очень неприятной, но может быть довольно незначительной.Смотрите предостережение ниже.Но, в конце концов, вы можете использовать git stash
, чтобы делать то, что вы хотите.
Что нужно знать
Во-первых, помните, что Git не делает коммитыиз вашего рабочего дерева (вашего рабочего каталога) вообще - и что фиксация завершена снимки всех файлов.Это снимки, сделанные из индекса , а не из рабочего дерева.Файлы в новом коммите - это те, которые сейчас находятся в индексе .
(Помните также, что индекс - это то, что другие части Git называют областью подготовки , или иногда кеш . Он содержит одну копию каждого файла, который будет добавлен в следующий коммит. Эта копия изначально является копией, взятой из текущей commit, за исключением некоторых крайних случаев, отмеченных в Извлечение другой ветки, если в текущей ветке есть незафиксированные изменения .)
Если ваше рабочее дерево отличается от вашего индекса, и вы хотитеснимите ваше рабочее дерево, вам нужно git add
каждый файл, переписав копию в индекса, прежде чем вы сможете зафиксировать это.Это, конечно, разрушает любую тщательную подготовку, которую вы сделали в вашем индексе.
Но именно поэтому git stash
действительно делает два коммитов:
- Один коммит сохраняет ваше текущее состояние индекса, как новый коммит, которого нет ни в одной ветви.Теперь безопасно уничтожить состояние индекса.
- Второй коммит сохраняет ваше текущее рабочее дерево как коммит с двумя родителями: коммит индекса и текущий коммит.Чтобы получить , что сделанный коммит, Git заменяет всех файлов в индексе их вариантами рабочего дерева (потому что Git делает коммиты из индекса, а не из рабочего дерева). 1
(На самом деле существует третий необязательный коммит для хранения неотслеживаемых или неотслеживаемых плюс игнорируемых файлов. Если этот коммит существует, он является третьим родителем коммита рабочего дереваОбычно его просто не существует.)
Сделав эти два коммита, git stash
обновляет refs/stash
, чтобы запомнить хеш-идентификатор коммита рабочего дерева w
.Этот коммит запоминает хеш-идентификатор индекса фиксации i
, а также хеш-идентификатор текущего коммита:
...--o--o--T <-- your-branch (HEAD)
\ |\
\ i-w <-- refs/stash
\
o--A <-- b
Затем git stash
запускает git reset --hard
, так что ваш индекс и рабочее дерево идутвернуться к соответствующему коммиту T
.Я выделил еще один коммит A
, на который указывает другая ветвь b
.
1 Технически, git stash
делает коммит w
, используявторой, вспомогательный / временный индекс, на случай, если что-то пойдет не так.В этом случае он может просто отказаться от временного индекса.Выполнить фиксацию индекса i
очень просто, так как сантехническая команда git write-tree
выполняет всю работу.
Использование фиксации в Git
Запомните предостережение: git stash
по сути просто делает git add
для всех файлов, которые уже в индексе.Любые неотслеживаемые файлы, включая неотслеживаемые и игнорируемые файлы, вообще не находятся в коммите w
.Они просто сидят на вашем рабочем дереве.Это правда , даже если , если бы вы сделали git checkout A
, чтобы получить фиксацию A
, некоторые из этих файлов были бы скопированы в ваш индекс.(Конечно, в этом случае вы, как правило, видели бы жалобу о том, что Git сначала нужно перезаписать какой-нибудь неотслеживаемый файл.)
В любом случае, за исключением одного большого предостережения, коммит stash w
содержит снимок , именно такой снимок, который, как вы говорите, вы хотите добавить только после коммита A
.
Теперь, когда этот снимок существует, вы можете сказать Git сделать новый коммит B
у которого A
в качестве родителя и w
tree в качестве снимка.Для этого нужна одна команда Git:
git commit-tree -p refs/heads/b refs/stash^{tree}
То есть мы используем имя refs/heads/b
(ветвь b
, указывающее на коммит A
), чтобы сообщить Git, каким должен быть хеш-идентификатор parent для нашего нового коммита. Мы используем refs/stash^{tree}
, чтобы сообщить Git, каким должно быть дерево 1121 * (снимок) для нашего нового коммита. Git читает стандартный ввод для сбора сообщения журнала - если хотите, добавьте -m <message>
или -F <file>
для предоставления сообщения или отправьте его на стандартный ввод:
echo some message | git commit-tree -p refs/heads/b refs/stash^{tree}
Результат:
...--o--o--T <-- your-branch (HEAD)
\ |\
\ i-w <-- refs/stash
\
o--A <-- b
\
B
, где новый коммит B
имеет тот же моментальный снимок, что и stash commit w
.
Команда git commit-tree
выводит идентификатор хэша нового коммита. Вам нужно будет захватить это - возможно, в переменную оболочки - и затем, скорее всего, установить какое-то имя, например refs/heads/b
, чтобы запомнить этот коммит. Например:
hash=$(git commit-tree -p refs/heads/b refs/stash^{tree})
git update-ref -m "add stashed work-tree commit" refs/heads/b $hash
дает:
...--o--o--T <-- your-branch (HEAD)
\ |\
\ i-w <-- refs/stash
\
o--A--B <-- b
То есть новый коммит b
теперь является вершиной существующей ветви b
. Снимок в B
соответствует снимку w
; они автоматически передаются. сообщение в B
- это то, что вы дали git commit-tree
. Хэш-идентификатор из B
теперь хранится в b
, а родительский элемент B
- A
, так что этот новый коммит находится на ветви b
, как вы и хотели.
Теперь, когда все , что выполнено, вы захотите восстановить свой индекс и рабочее дерево, которое git stash
выбросило, но сначала сохранило в этих двух коммитах. Для этого используйте git stash pop --index
. --index
важен: он сравнивает ваш текущий индекс с i
и использует различия для восстановления вашего индекса. 2 Затем он сравнивает ваше текущее рабочее дерево с w
и использует различия для восстановления твое рабочее дерево от w
. Часть pop
затем отбрасывает коммиты i-w
и, если были другие спрятанные коммиты, заставляет refs/stash
запомнить правильный.
Следовательно, игнорируя все места, где все может пойти неправильно и все соответствующие проверки ошибок, следующая последовательность команд может делать то, что вы хотите, в зависимости от того, кто вы есть хочу:
git stash push # and make sure it does something
hash=$(echo automatic commit of work-tree |
git commit-tree -p refs/heads/b refs/stash^{tree})
git update-ref -m "add stashed work-tree commit" refs/heads/b $hash
git stash pop --index
Это полностью не проверено (и имеет некоторые плохие режимы сбоев, особенно тот, где git stash push
говорит, что нечего сохранять, и вообще отказывается что-либо делать).
2 Это неэффективный способ простого чтения i
непосредственно в индексе, но он достигает той же цели. То же самое относится к шагу w
.