TL; DR
Используйте git stash branch
до , создайте новую ветку и примените тайник:
git stash branch newbranch
Это всегда работает (ну, пока ваш текущий статус все чист: смотрите длинный раздел ниже) и является эквивалентом создания новой ветви, чей родитель является коммитом, который был текущим, когда вы сделали тайник, а затем запустил git apply --index
(это означает, что он восстанавливает индекс и рабочее дерево отдельно).
Буквальный ответ на вопрос
Все, что вам нужно сделать, это выяснить, идентифицируют ли какие-либо имена ветвей тот же коммит, что и родительский элемент рассматриваемого тайника, поэтому:
git for-each-ref --format='%(refname:short)' \
--points-at $(git rev-parse refs/stash~1) refs/heads
найдет возможные имена ветвей, если они есть. (Это опирается на относительно современные функции git for-each-ref
.)
Long
Каждый тайник сам по себе - всего два, а иногда и три коммита. git stash
делает то, что делает эти два (или три) коммита, но помещает их в no ответвление.
Слово branch в Git само по себе немного неоднозначно (см. Что именно мы подразумеваем под «веткой»? ), но здесь мы используем его в том смысле, что ветка имя , как master
или develop
, просто указывает на последний коммит в его ветке:
...--E--F--G <-- master
\
H <-- develop
Когда вы git checkout
называете ветку типа develop
, вы говорите Git:
- Извлечение содержимого этого коммита в индекс (место, где вы строите следующий коммит, также называемый промежуточной областью или иногда кеш )
- Извлеките из индекса индексированные, но все еще сжатые файлы Git-формы / сжатые и скопируйте их в рабочее дерево , где вы можете работать с ними.
Прикрепите имя HEAD
к имени ветки, чтобы Git знал, какой коммит вы уже извлекли:
...--E--F--G <-- master
\
H <-- develop (HEAD)
Теперь Git знает, что коммит, хеш которого H
, является текущим или HEAD
коммитом, на что указывает имя develop
.
Если в этот момент вы делаете обычный новый коммит (копируя обновленные или новые файлы из рабочего дерева обратно в область index / staging, а затем запускаете git commit
, чтобы заморозить их в новый коммит), Git пишет: новый коммит с текущим коммитом в качестве родителя, а затем обновляет имя ветви:
...--E--F--G <-- master
\
H--I <-- develop (HEAD)
Имя HEAD
остается присоединенным к имени ветви, но теперь имя ветви идентифицирует commit I
вместо commit H
.
Что делает git stash
, так это делает две фиксации - одну для индекса и одну для рабочего дерева - чтобы не имели имени ветви . Вместо этого Git использует имя refs/stash
, чтобы найти фиксацию w
(а w
находит как оригинальную фиксацию, так и фиксацию i
):
...--E--F--G <-- master
\
H <-- develop (HEAD)
|\
i-w <-- refs/stash
После сохранения коммитов i
и w
git stash
запускает git reset --hard
, чтобы вернуть индекс и рабочее дерево в соответствие с текущим коммитом (здесь H
).
В этом случае в данный момент времени родительский коммит тайника также указывается существующим именем ветви develop
. Но предположим, что вы сделали это git stash
, а затем пошли на другой набор изменений и сделали новый коммит I
? Тогда у вас есть:
...--E--F--G <-- master
\
H--I <-- develop (HEAD)
|\
i-w <-- refs/stash
Теперь нет имени ветви, указывающего на существующий, исторический коммит H
, хотя это единственный коммит, к которому гарантированно применяется refs/stash
.
Итак, если ваш индекс и рабочее дерево теперь чистые (т. Е. Соответствуют содержимому коммита I
, к которому прикреплен HEAD
), и вы теперь запускаете git stash branch recover
, что будет делать git stash
это прикрепить новое имя ветки, recover
, чтобы зафиксировать H
, и проверить этот коммит и сделать новую ветку текущей веткой, прикрепив к ней HEAD
:
...--E--F--G <-- master
\
H <-- recover (HEAD)
\
I <-- develop
и, в качестве последнего шага операции git stash branch
, примените и отбросьте тайник, чтобы индекс вернулся к тому же индексу, который был при извлечении H
и запуске git stash
, и работе -дерево вернулось к тому, как работало дерево, когда вы H
проверили и запустили git stash
. Теперь вы можете закончить git add
ing и git commit
, чтобы сделать новый коммит J
:
...--E--F--G <-- master
\
H--J <-- recover (HEAD)
\
I <-- develop
Что если develop
все еще указывает на фиксацию H
?
Если вы не не переместили предыдущую ветку, то теперь у вас есть новая ветка. То есть теперь вместо приведенного выше рисунка вы бы получили:
...--E--F--G <-- master
\
H <-- develop
\
J <-- recover (HEAD)
(я оставил новый коммит здесь как J
, чтобы было проще сопоставить его с предыдущей диаграммой). Там нет ничего плохого в этой настройке. Конечно, если вы не хотите ветку recover
здесь, вам не нужно использовать git stash branch
, но она все еще работает.
А как насчет трёхкомпонентного тайника?
Трехкомпонентные тайники сделаны git stash save -a
(--all
) или git stash save -u
(--include-untracked
). Применение (или выталкивание) такого тайника требует, чтобы файлы в третьем коммите не сталкивались с файлами в рабочем дереве. В общем, это означает, что рабочее дерево должно быть не только «чистым» в соответствии с git status
, но также и чистым в том смысле, в котором оно запускается с помощью git clean
с соответствующими параметрами. В этом конкретном случае даже git stash branch
может иногда давать сбой. Если это не удается, он оставляет тайник без присмотра.
Подробнее см. Почему в git stash pop говорится, что он не может восстановить неотслеживаемые файлы из записи stash?