git stash и применить

Я новичок в git и не совсем понимаю, как работает тайник.

Допустим, я работаю над мастером ветки и пытаюсь git pull и получаю сообщение об ошибке, что мои локальные изменения будут перезаписаны и должны быть сохранены или зафиксированы. Если я не внес никаких изменений и не запустил git stash, а затем выполнил git pull и успешно обновился, что произойдет, когда я git stash apply?

В общем, если кто-то изменяет файлы, а я запускаю git pull, что происходит, когда я run git stash apply? он перезаписывает файлы, которые были только что обновлены, независимо от того, были ли они подготовлены, когда я их спрятал? Он перезаписывает каждый файл, который я только что обновил с помощью git pull, с файлами, которые были спрятаны?


person neridaj    schedule 05.12.2013    source источник
comment
Из книги Git: git-scm.com/book/en/Git-Tools -Хранение   -  person Maic López Sáenz    schedule 07.12.2013


Ответы (3)


Быстрая версия на вынос "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
comment
Это один из лучших ответов, которые я когда-либо видел на SO, и другие ваши ответы кажутся столь же полными. Спасибо. Однако один вопрос: при применении тайника git будет информировать вас о конфликтах? (То есть изменений, сделанных в D или E, которые перезаписываются вашими спрятанными изменениями?) - person Chris Middleton; 13.08.2014
comment
@AmadeusDrZaius: шаг применения (на самом деле, все эти вещи внутри git) используют то, что я называю механизмом слияния. Только некоторые команды предоставляют параметры (--strategy и -X), другие используют настройки по умолчанию; значения по умолчанию останавливаются с ошибкой при конфликтах. Конечно, git может сообщить вам только о конфликтах, которые он видит, поэтому в целом вам всегда нужно проверять результаты, даже если git ими доволен. - person torek; 13.08.2014
comment
Если stash возвращается к последнему HEAD, который я извлек, зачем мне использовать pull --rebase, как показано в некоторых сообщениях, таких как stackoverflow.com/a/30209767/ 577052 ? Не должно быть никаких изменений для перебазирования, поскольку они также спрятаны, или это не так? - person Bernhard Döbler; 26.03.2017
comment
@BernhardDöbler: я не понимаю предпосылку вопроса (возвращается к последней извлеченной части HEAD). Сам по себе тайник не имеет ничего общего с git fetch; git stash save просто создает несколько коммитов, которые вообще не относятся к ветке, а затем сбрасывает (с опциями, так что здесь не все так просто) индекс и рабочее дерево. Rebase тоже не имеет к этому никакого отношения: git rebase копирует коммит. Коммиты для копирования выбираются с использованием текущей ветки. Место назначения для новых копий и ограничитель берутся из аргументов git rebase или из настроек восходящей ветки текущей ветки. - person torek; 26.03.2017

команда stash git запоминает, откуда берется тайник:

   git stash list

выход

   stash@{0}: WIP on master.color-rules.0: 35669fb [NEW] another step toward initial cube

Где можно посмотреть на каком SHA1 он был сделан. Поэтому, если вы git stash, git pull, git stash apply и у вас возник конфликт, тайник не удаляется (это произойдет только в том случае, если вы сбросите или если приложение было успешным). Таким образом, вы всегда можете получить SHA1 из списка git stash и

   git checkout 35669fb
   git stash apply

и это гарантированно работает. Я рекомендую использовать параметр -b и указать имя ветки для этого восстановления.

При этом мой любимый рабочий процесс — ВСЕГДА оформлять заказ под новым «личным» именем, чтобы избежать таких проблем.

person Sebastien    schedule 05.12.2013
comment
git stash branch <newbranch> объединяет все три шага (проверьте версию, к которой применяется тайник, создайте новую ветку и примените тайник с помощью --index, а затем удалите тайник после успешного применения). - person torek; 06.12.2013

Как правило, незафиксированные изменения — это всегда плохо. Либо ваши изменения хороши, затем зафиксируйте их, либо они плохи, чем отбросьте их. Выполнение любых операций git с незафиксированными изменениями может вызвать проблемы, и git не сможет вам помочь, так как git не знает ни о чем, что вы не зафиксировали.

Сказав это, вернемся к вашему вопросу. ;)

Git вообще довольно умен. Когда вы применяете свой тайник, он пытается объединить ваши изменения с другими изменениями. В большинстве случаев это просто работает.

Если изменения действительно конфликтуют, потому что вы изменили одни и те же строки по-другому, git сообщит вам об этом, и вам придется решать конфликт самостоятельно. - Даже в этом случае git поможет вам, имея git mergetool, который запустит подходящую команду, чтобы показать вам конфликты и разрешить их один за другим.

person michas    schedule 05.12.2013
comment
Обсуждение (первый абзац этого ответа), возможно, лучше подходит для комментариев, а не для ответа. - person Maic López Sáenz; 07.12.2013