Быстрая версия на вынос "TL; DR", чтобы можно было вернуться позже и изучить больше
git stash
вешает заначку — это своеобразная форма коммита слияния, которого нет ни на одной ветке — на текущий HEAD
коммит. Более поздний git stash apply
, когда вы выполняете какую-либо фиксацию — возможно, другую фиксацию — затем пытается восстановить изменения, вычисленные git, просматривая как висящий заначку, так и коммит, от которого он висит.
Когда вы закончите с изменениями, вы должны использовать git stash drop
, чтобы отпустить заначку из фиксации, на которой она была «спрятана». (И git stash pop
— это просто сокращение от «применить, затем автоматически удалить». Однако я рекомендую разделить два шага на тот случай, если вам не понравится результат «применить» и вы захотите повторить попытку позже.)
Длинная версия
git stash
на самом деле довольно сложный.
Говорят, что " git имеет гораздо больше смысла, когда вы понимаете X" для многих различных значений "X", что обобщается до "git имеет гораздо больше смысла, когда вы понимаете git". :-)
В этом случае, чтобы на самом деле понять stash
, вам нужно понять, как работают коммиты, ветки, индекс/промежуточная область, ссылочное пространство имен git и слияния, потому что git stash
создает очень своеобразный коммит слияния. на который ссылается имя за пределами обычных пространств имен — странный вид слияния, который вообще не находится «на ветке», — и git stash apply
использует механизм слияния git, чтобы попытаться «повторно применить» изменения, сохраненные при была сделана своеобразная фиксация слияния, опционально сохраняющая различие между поэтапными и нестадийными изменениями.
К счастью, вам не нужно во всем этом разбираться, чтобы использовать git stash
.
Здесь вы работаете над какой-то веткой (master
), и у вас есть некоторые изменения, которые еще не готовы, поэтому вы не хотите их фиксировать в этой ветке.1 Тем временем кто-то еще что-то добавил хорошо — или, по крайней мере, вы надеетесь, что это хорошо — в origin/master
на удаленном репозитории, поэтому вы хотите их забрать.
Допустим, вы и они оба начали с коммитов, оканчивающихся на - A - B - C
, т. е. C
— это последний коммит, который был у вас в репозитории, когда вы начали работать над веткой master
. Новый коммит "что-то хорошее" мы назовем D
и E
.
В вашем случае вы используете git pull
, и он терпит неудачу из-за проблемы «рабочий каталог не чист». Итак, вы запускаете git stash
. Это фиксирует ваши вещи для вас в своей особой странной манере тайника, так что ваш рабочий каталог теперь чист. Теперь вы можете git pull
.
С точки зрения отрисовки коммитов (график, подобный тому, что вы получаете с gitk
или git log --graph
), теперь у вас есть что-то вроде этого. Тайник — это маленький мешочек с i-w
, свисающий с коммита, на котором вы были «на», в вашей ветке master
, когда вы запускали git stash
. (Причина названий i
и w
заключается в том, что это части "i"ndex/staging-area и "work"tree тайника.)
- A - B - C - D - E <-- HEAD=master, origin/master
|\
i-w <-- the "stash"
Этот рисунок — то, что вы получите, если начали работать над master
и никогда не делали никаких коммитов. Таким образом, ваш последний коммит был C
. После создания тайника git pull
смог добавить коммиты D
и E
в вашу локальную ветку master
. Спрятанная сумка с работами все еще висит на C
.
Если вы сделали несколько собственных коммитов — мы назовем их Y
для вашего коммита и Z
просто для двух коммитов — результат «спрятать, а затем извлечь» выглядит следующим образом:
.-------- origin/master
- A - B - C - D - E - M <-- HEAD=master
\ /
Y - Z
|\
i-w <-- the "stash"
На этот раз, после того, как stash
повесил свою сумку на Z
, pull
— то есть просто fetch
, а затем merge
— должен был выполнить настоящее слияние, а не просто «ускоренную перемотку вперед». Так что это делает коммит M
, коммит слияния. Метка origin/master
по-прежнему относится к коммиту E
, а не M
. Теперь вы находитесь на master
при фиксации M
, которая представляет собой слияние E
и Z
. Вы "на один впереди" origin/master
.
В любом случае, если вы сейчас запустите git stash apply
, скрипт stash (это скрипт оболочки, который использует много низкоуровневых команд git "подключения") эффективно2 сделает следующее:
git diff stash^ stash > /tmp/patch
git apply /tmp/patch
Это отличает stash
, который называет w
— часть «рабочего дерева» тайника — с правильным родителем 3. Другими словами, он узнает, «что вы изменили» между правильным родительским коммитом (C
или Z
, в зависимости от ситуации) и спрятанным рабочим деревом. Затем он применяет изменения к текущей проверенной версии, которая имеет номер E
или M
, опять же в зависимости от того, с чего вы начали.
Между прочим, git stash show -p
на самом деле просто запускает ту же самую команду git diff
(конечно, без части > /tmp/patch
). Без -p
он запускает diff с --stat
. Поэтому, если вы хотите подробно посмотреть, во что git stash apply
сольется, используйте git stash show -p
. (Однако это не покажет вам, что git stash apply
может попытаться применить из индексной части тайника; это небольшое замечание, которое у меня есть со скриптом тайника.)
В любом случае, как только тайник будет правильно применен, вы можете использовать git stash drop
для удаления ссылки на тайник, чтобы его можно было удалить сборщиком мусора. Пока вы его не уроните, у него есть имя (refs/stash
, также известное как stash@{0}
), так что он остается "навсегда"... за исключением того факта, что если вы создадите новый тайник, скрипт stash
"оттолкнет" текущий тайник в reflog тайника (чтобы его имя стало stash@{1}
) и заставляет новый тайник использовать имя refs/stash
. Большинство записей reflog хранятся в течение 90 дней (вы можете настроить это по-другому), а затем истекает. Срок действия тайников не истекает по умолчанию, но если вы настроите это иначе, «отправленный» тайник может быть потерян, поэтому будьте осторожны с зависимостью от «сохранить навсегда», если вы начнете настраивать git по своему вкусу.
Обратите внимание, что git stash drop
«выталкивает» здесь стек тайника, перенумеровывая stash@{2}
в stash@{1}
и превращая stash@{1}
в простое stash
. Используйте git stash list
, чтобы увидеть стек тайника.
1В любом случае неплохо пойти дальше и зафиксировать их, а затем выполнить более позднее git rebase -i
, чтобы сжать или исправить дальнейшие вторые, третьи, четвертые, ..., n-е коммиты и/или переписать временные " контрольно-пропускной пункт». Но это не зависит от этого.
2Это немного сложнее, потому что вы можете использовать --index
, чтобы попытаться сохранить поэтапные изменения, но на самом деле, если вы посмотрите в сценарий, вы увидите фактическую последовательность команд git diff ... | git apply --index
. В этом случае он действительно просто применяет diff! В конце концов, он напрямую вызывает git merge-recursive
для слияния с рабочим деревом, позволяя внести те же самые изменения из других мест. Обычный git apply
потерпит неудачу, если ваш патч делает что-то, что "хороший материал" коммитов D
и E
также делает.
3При этом используется магический синтаксис git для именования родителей с небольшим предварительным планированием внутри скрипта stash
. Поскольку тайник представляет собой этот причудливый коммит слияния, w
имеет двух или даже трех родителей, но скрипт тайника настраивает его так, что «первым родителем» является исходный коммит, C
или Z
, в зависимости от ситуации. «Второй родитель» stash^2
— это состояние индекса во время коммита, показанное как i
в маленькой висящей сумке, а «третий родитель», если он существует, — это неподготовленные и, возможно, игнорируемые файлы из git stash save -u
или git stash save -a
.
Обратите внимание, что в этом ответе я предполагаю, что вы не тщательно подготовили часть своего рабочего дерева и что вы не используете git stash apply --index
для восстановления поэтапного индекса. Не делая ничего из этого, вы делаете коммит i
в значительной степени избыточным, так что нам не нужно беспокоиться об этом во время шага apply
. Если вы используете apply --index
или аналогичный и имеете промежуточные элементы, вы можете столкнуться с гораздо большим количеством угловых случаев, когда тайник не будет применяться чисто.
Те же предостережения, с еще большим количеством угловых случаев, применяются к тайникам, сохраненным с помощью -u
или -a
, которые имеют этот третий коммит.
Для этих особо сложных случаев git stash
предоставляет способ превратить тайник в полноценную ветвь, но я оставлю все это для другого ответа.
person
torek
schedule
05.12.2013