Ответ 1
/Я сохраняю первый длинный ответ ниже (может попытаться перенести его на новый вопрос позже), но теперь, когда есть "воспроизвести пример", я пройду через это. Позвольте мне сделать здесь список пунктов.
-
git stash
всегда фиксирует индекс и рабочий каталог. Можно подумать, что--keep-index
заставляет его зависеть больше или изменяет способ обработки спрятанного значения наpop
. Это не так! Обаgit stash apply
иgit stash pop
смешивают разделенное изменение индекса по умолчанию. Добавление--keep-index
не меняет этого. Только аргумент--index
дляapply
иpop
пытается избежать их смешивания. -
"Рабочий каталог", который
git stash
экономит суммы, по сути, на изменение от текущегоHEAD
. Это означает, что если индекс имеет изменение отHEAD
, но в текущем рабочем каталоге этого нет, в команде "WIP on branch..." не сохраняется никаких изменений. (Это, я думаю, ошибка вgit stash
. Я отправил тестовый пример и возможное исправление в список рассылки git. Для "нормальных" случаев это нормально, но если вы разделили некоторые части, а затем хотите восстановить ваше точное состояние позже с помощьюgit stash branch
, оно перестает работать с каталогом, и это вызывает вашу проблему здесь.) -
Применение stash пытается внести изменения в текущее состояние, которое отражает изменения в состоянии сшивания. Это может быть сложно, потому что текущее состояние не обязательно похоже на то, что было, когда вы сохранили тайник.
Вот что делает git-gui
. В то время, когда вы запускаете его, у вас есть это (фактические числа фиксации, конечно, будут меняться). Немаркированный "WIP on master" является "первым" типом, теперь [email protected]{1}
.
$ git stash list
[email protected]{0}: WIP on master: c93c8fe tobeamended123
[email protected]{1}: WIP on master: c93c8fe tobeamended123
$ git log --decorate --oneline --graph --all '[email protected]{1}'
* 3d01942 (refs/stash) WIP on master: c93c8fe tobeamended123
|\
| * 6be9135 index on master: c93c8fe tobeamended123
|/
| * de8038c WIP on master: c93c8fe tobeamended123
| |\
|/ /
| * 3db6cfc index on master: c93c8fe tobeamended123
|/
* c93c8fe (HEAD, master) tobeamended123
* 828d5cf base123
Теперь в git gui
, когда вы выберете "modify last commit", он находит ref для фиксации HEAD (c93c8fe
, в моем случае). На самом деле он ничего не делает (пока). Но как только вы нажмете на f3
, чтобы его отключить, он делает что-то: он захватывает предыдущую версию f3
(я не уверен, что использует gui под собой, моя догадка будет HEAD^
copy) и материал это в индекс. Если вы просмотрите f3
, в нем все еще есть лишняя строка, но если вы git show :0:f3
, чтобы увидеть версию в индексе, она больше не имеет этой строки.
Обратите внимание, что никакие ссылки не были изменены из-за кликов с помощью мыши, а новых коммитов не было. Все действия произошли внутри индекса.
Затем вы вернулись в командную строку и запустили:
$ git stash save --keep-index
Это сделало третью пару коммитов, одну с индексом и одну с текущим каталогом. Версия индекса имеет дополнительную строку в f1
и f2
и не имеет дополнительной строки в f3
. Текущая версия каталога должна (как она думает) иметь дополнительную строку во всех трех файлах, но, увы, это не так, потому что git stash save
сравнивает текущий dir vs HEAD
commit, а дополнительная строка находится в HEAD
commit, поэтому он не находится в скрытой версии.
К сожалению, вы использовали этот аргумент --keep-index
, поэтому теперь версия рабочего каталога такая же, как и сложенная версия индекса. Файл f3
больше не имеет дополнительной строки.
С этого момента проблема сохраняется (изменение не прошло, --keep-index
перебросило его). Разумеется, вы можете восстановить его из первоначальной фиксации ( "tobeamended123" ). Но в этом случае в этом случае все пошло не так: командная строка stash
сохранила индекс, а затем сравнила рабочий каталог с HEAD
, который не изменился, поэтому не сохранил (без изменений) до f3
.
Я не вижу катастрофы, но вижу что-то запутанное, что, по-моему, смущает вас. Я не знаю, почему вы использовали --keep-index
выше. (На самом деле, я не уверен, какой прецедент --keep-index
может быть предназначен для 1 и мне кажется, что apply
и pop
должны, вероятно, по умолчанию --index
, но это совсем другое дело.) И вы сделали четыре тотальных "заталкивания", и только "выскочили", оставив троих.
[ 1 Я нашел предполагаемый прецедент, прямо там, в документации: для проверки того, что сейчас находится в индексе, перед его выполнением. Но подождите, huhwha?, --keep-index
делает это, на stash
ref. В любом случае, вы можете просто сделать это, используя git checkout -b test-stash
, чтобы он был безопасно разделен, пока вы не удовлетворены этим. Если вы протестируете его, и он терпит неудачу, и вам нужно его изменить, это будет иметь конфликты. Если вы протестируете, и он работает, вы можете просто перетащить/ускорить-слияние сработавшего коммита в свою более раннюю ветвь.]
Короткий ответ "tl; dr"
Запустите git stash list
. Вы увидите список:
[email protected]{0}: WIP on master: ab0d18d Setup of alarms ...
[email protected]{1}: WIP on master: ...
элементы. Используйте git stash apply --index '[email protected]{n}'
(--index
необязательно), чтобы попытаться применить каждый сохраненный штамп по имени и номеру, не выбирая ни один из них. Это стек, с [email protected]{0}
самым последним нажатым и (по этой точке) [email protected]{3}
нажатым первым (длиннее-назад).
Применить-без-поп означает, что вы можете git reset --hard
вернуться к master
и подготовиться к git stash apply
другому типу. (Убедитесь, что вы запускаете всю последовательность с помощью чистого рабочего каталога, возможно, добавив еще один git stash
, хотя это может снова запутаться.:-))
Если вы сделали особенно большой беспорядок, вы можете использовать use git stash branch name '[email protected]{n}'
. Это большой, быстрый, эффективный молот, основным недостатком которого является то, что вам нужно придумать название ветки. (Вы можете git stash show
заполнить, чтобы увидеть, что в них, чтобы помочь вам придумать имена.) Не позволяйте этому пугать вас, так как вы всегда можете переименовать ветвь или даже удалить ее позже. См. Подробное описание того, как это работает.
Когда вы все закончите со всеми вашими записями, используйте git stash clear
, чтобы стереть их все.
Относительно git commit --amend
vs git stash
На самом деле они несколько независимы. commit --amend
работает в цепочке фиксации, основанной на любой ветке, в которой вы находитесь. Скажем, вы находитесь на master
, и цепочка выглядит так (в git log --graph --oneline --decorate
или gitk
):
* 67dec43 (HEAD, master) "amendme" commit
* 9c37840 previous commit
Вы редактируете и git add
некоторые вещи - я изменю файл f3
и добавлю его, а затем запустите git commit --amend
. Это принимает индекс и совершает новое коммитирование, но новый родитель фиксации - это один назад, где master
был, т.е. previous commit
выше. Теперь вывод журнала выглядит следующим образом:
* 68c51f3 (HEAD, master) replacement for "amendme" commit
* 9c37840 previous commit
То, что вы не видите (потому что на нем нет метки ветвления), заключается в том, что 67dec43
все еще там (пока он не истечет и не получит мусор), но если вы скажете git log
, чтобы посмотреть там, он будет:
$ git log --graph --decorate --oneline master 67dec43
* 68c51f3 (HEAD, master) replacement for "amendme" commit
| * 67dec43 "amendme" commit
|/
* 9c37840 previous commit
У вас есть ветвь, отступающая от "предыдущей фиксации", с меткой master
на новой замене фиксации и фиксацией "исправления" на немаркированной ветке.
Позвольте сделать это снова, с наложением на место на этот раз. Я начинаю с "известного плохого" файла в f3
в "исправлении". Затем я помещаю второй (но еще не правильный) f3
и запускаю git stash
. Наконец, я исправляю f3
"для реального" и использую --amend
. В stash содержится ссылка на теперь немаркированную ветку, потому что stash - это новая фиксация (действительно, две). Вот несколько последних шагов:
$ git log --graph --decorate --oneline
* 3c97241 (refs/stash) WIP on master: 67dec43 "amendme" commit
|\
| * f3a50e9 index on master: 67dec43 "amendme" commit
|/
* 67dec43 (HEAD, master) "amendme" commit
* 9c37840 previous commit
* 84408ef base
$ echo 'better changes for f3' > f3
$ git add f3
$ git commit --amend -m 'replacement for "amendme" commit'
$ git log --graph --decorate --oneline --all
* c1f1042 (HEAD, master) replacement for "amendme" commit
| * 3c97241 (refs/stash) WIP on master: 67dec43 "amendme" commit
| |\
| | * f3a50e9 index on master: 67dec43 "amendme" commit
| |/
| * 67dec43 "amendme" commit
|/
* 9c37840 previous commit
* 84408ef base
Если вы попытаетесь применить stash, будет конфликт (потому что stash меняет файл f3
, с моей промежуточной версией, "не совсем плохой, но не лучше" ):
$ git stash apply
git stash apply
Auto-merging f3
CONFLICT (content): Merge conflict in f3
$ git reset --hard master
HEAD is now at c1f1042 replacement for "amendme" commit
$ git stash apply --index
Auto-merging f3
CONFLICT (content): Merge conflict in f3
Index was not unstashed.
$ git reset --hard master
HEAD is now at c1f1042 replacement for "amendme" commit
Это то же самое, что и любой другой конфликт при выполнении коммитов, например cherry-pick
или merge
, и вы разрешаете их одинаково.
Если вам нравится, вы можете наклеить ярлык ветки или тега на "исправление":
$ git branch master-old 67dec43
$ git log --graph --oneline --decorate --all
* c1f1042 (HEAD, master) replacement for "amendme" commit
| * 3c97241 (refs/stash) WIP on master: 67dec43 "amendme" commit
| |\
| | * f3a50e9 index on master: 67dec43 "amendme" commit
| |/
| * 67dec43 (master-old) "amendme" commit
|/
* 9c37840 previous commit
* 84408ef base
и теперь он легко доступен для справки. Затем вы можете проверить это и git stash pop --index
этот конкретный штамп; это гарантированно работает (следовательно, pop
безопасен, хотя вы можете захотеть apply
в любом случае, пока не выполните несколько из них). См. Также "Использование git stash branch
" ниже, что автоматизирует это.
Как работает stash, длинная версия
Позвольте немного отступить. Я хочу показать упрощенный пример с тремя файлами.
Сделайте make temp dir и git repo и передайте начальную точку с тремя файлами с одной строкой:
$ mkdir /tmp/tt; cd /tmp/tt; git init
... # create files f1, f2, f3; git add ...
$ git commit -m base
[master 84408ef] base
3 files changed, 3 insertions(+)
create mode 100644 f1
create mode 100644 f2
create mode 100644 f3
$ ls
f1 f2 f3
$ cat f1 f2 f3
this file stays the same
this file changes in the index
this file changes in the WIP
Теперь сделаем изменения:
$ echo more for f2 >> f2; git add f2
$ echo more for f3 >> f3
В этот момент f2
изменяется и выполняется:
$ git diff --cached
diff --git a/f2 b/f2
index 78991d3..3a2f199 100644
--- a/f2
+++ b/f2
@@ -1 +1,2 @@
this file changes in the index
+more for f2
и f3
изменен, но не поставлен:
$ git diff
diff --git a/f3 b/f3
index d5943ba..188fe9b 100644
--- a/f3
+++ b/f3
@@ -1 +1,2 @@
this file changes in the WIP
+more for f3
Здесь diff --cached
показывает поэтапный материал (в индексе) и diff
без --cached
показывает неустановленный материал.
Теперь пусть git stash
(по умолчанию op - save
). stash
добавит две транзакции к репо. Первый - это всего лишь материал, поставленный до сих пор (если ничего не поставлено, stash
заставляет в no-changes commit), а второй - слияние, этого-плюс-work-dir. Итак:
$ git stash
Saved working directory and index state WIP on master: 84408ef base
HEAD is now at 84408ef base
$ git log --graph --oneline --decorate --all
* 753a6c8 (refs/stash) WIP on master: 84408ef base
|\
| * 36b23f2 index on master: 84408ef base
|/
* 84408ef (HEAD, master) base
Этот первый, index on master
, имеет мое изменение на f2
:
$ git show 36b23f2
[snip]
diff --git a/f2 b/f2
index 78991d3..3a2f199 100644
--- a/f2
+++ b/f2
@@ -1 +1,2 @@
this file changes in the index
+more for f2
Вторая имеет оба изменения (f2
и f3
), но является фиксацией слиянием, поэтому git show
показывает комбинированный diff, только показывающий f3
:
$ git show 753a6c8
[snip]
diff --cc f3
index d5943ba,d5943ba..188fe9b
--- a/f3
+++ b/f3
@@@ -1,1 -1,1 +1,2 @@@
this file changes in the WIP
++more for f3
(Помимо этого: если вы хотите сравнить любое слияние M
с каждым родителем, используйте git show -m M
. Например, git show -m 753a6c8
сначала различает 753a6c8^1
-vs- 753a6c8
, тогда 753a6c8
^ 2- VS- 753a6c8
.)
Что с этим --keep-index
вещью?
Обычно, после того, как вы выполните git stash
, у вас есть чистый каталог, так что ничего не нужно "перепроверять":
$ git status
# On branch master
nothing to commit, working directory clean
$ git stash
No local changes to save
Но вы запросили тайник --keep-index
. Это все еще делает обычную запись, но затем извлекает содержимое фиксации индекса, помещая его как в рабочий каталог, так и в индекс. Позвольте всплывать текущую шкатулку, посмотрите на состояние (git status
- pop
автоматически status
, а затем git log --graph --oneline --decorate --all
), и убедитесь, что мы вернулись к состоянию работы, но ничего не поставлено на этот раз:
$ git stash pop
...
$ git log --graph --oneline --decorate --all
* 84408ef (HEAD, master) base
Теперь переустановите f2
и переделайте stash save
, но на этот раз с --keep-index
:
$ git add f2
$ git stash save --keep-index
Saved working directory and index state WIP on master: 84408ef base
HEAD is now at 84408ef base
Выглядит так же, как раньше... но не совсем:
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: f2
#
Используйте git log --graph --oneline --decorate --all
, и вы увидите в основном то же самое (с другими хэшами коммит), как и раньше: stash
зафиксировал индекс, а затем совершил слияние-объединение дерева работ. Но на этот раз он также повторно извлек индекс, так что теперь у вас есть "изменения, которые нужно совершить". Это просто f2
, а не f3
.
Это означает, что вы можете (несколько бессмысленно) git stash save
снова. И ты это сделал! Позвольте использовать эту функцию для создания логарифма-одной строки (я использую ее много), как до, так и после:
$ git log --graph --oneline --decorate --all
* 7efe9a6 (refs/stash) WIP on master: 84408ef base
|\
| * 76c840e index on master: 84408ef base
|/
* 84408ef (HEAD, master) base
$ git stash save
Saved working directory and index state WIP on master: 84408ef base
HEAD is now at 84408ef base
$ git log --graph --oneline --decorate --all
$ git lola
* eb383e0 (refs/stash) WIP on master: 84408ef base
|\
| * aba15e6 index on master: 84408ef base
|/
* 84408ef (HEAD, master) base
Выглядит одинаково до и после, сначала краснеть. Но посмотрите на идентификаторы SHA-1. Они изменились! Перед вторым git stash save
, refs/stash
с именем commit 7efe9a6
. Теперь он называет eb383e0
!
reflog (или, обратите внимание, он становится сложным)
Хорошо, это не так уж плохо, но теперь вам нужно узнать о "рефлоге". Куда отправился другой stash
? Ответ в том, что он "толкнул" и исчез в рефлоге. Там тоже небольшая лишняя морщина: "кошелек" не является регулярной ветвью или тегом. Вместо этого он находится в refs/stash
. Итак, вот один из способов увидеть это, используя git log -g
, что означает "посмотреть на логги":
$ git log -g --oneline refs/stash
eb383e0 refs/[email protected]{0}: WIP on master: 84408ef base
7efe9a6 refs/[email protected]{1}: WIP on master: 84408ef base
Aha, там они, как 7efe9a6
, так и eb383e0
. У них есть "полные имена пользовательских форм" (например, refs/[email protected]{1}
), которые немного больны для использования. К счастью, stash
работает (если вы не назовёте ветку stash
), чтобы получить "самый верхний" {0}
, и вы можете написать [email protected]{1}
для другого. Или мы можем пойти на полномасштабную автоматизацию:
$ git log -g --pretty=format:%H refs/stash
Это выгружает их полные хэши, которые мы можем использовать в качестве аргументов для git log --graph --decorate
, чтобы получить это:
$ git log --graph --oneline --decorate $(git log -g --pretty=format:%H refs/stash)
* eb383e0 (refs/stash) WIP on master: 84408ef base
|\
| * aba15e6 index on master: 84408ef base
|/
| * 7efe9a6 WIP on master: 84408ef base
| |\
|/ /
| * 76c840e index on master: 84408ef base
|/
* 84408ef (HEAD, master) base
Чтобы просто посмотреть, что еще "там", в репо.
(Или, конечно, вы можете использовать gitk
, как вы, чтобы их видеть. gitk
достаточно умен, чтобы искать блокировки stash.)
(Кроме того, вы также можете использовать git reflog show refs/stash
. Подкоманда reflog show
запускает git log -g --oneline
.)
Вернемся к нашей проблеме, снова
Теперь, когда мы сделали первый git stash save --keep-index
, а затем бессмысленный git stash save
, теперь что?
Итак, мы можем git stash pop
получить последний (самый верхний стек):
$ git stash pop
# On branch master
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: f2
#
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/[email protected]{0} (eb383e050d150a8ce5b69a3662849ffdd7070c89)
Что случилось с f3
? Как мы отмечали ранее, второй git stash save
сохранил только "сохраненный индекс", т.е. Только измененный f2
. Нам нужно вернуться к первому титу.
$ git stash pop
error: Your local changes to the following files would be overwritten by merge:
f2
Please, commit your changes or stash them before you can merge.
Aborting
Это не очень помогает, не так ли?: -)
Если вы не уверены, что вы делаете, сейчас самое подходящее время для создания ветки "сохранить материал" (вы всегда можете удалить ее позже). Просто git checkout -b help-me-spock
или что-то еще, добавить и зафиксировать. Этот материал теперь находится на ветке и легче отслеживать. Но мы знаем, что делаем, и что у нас есть f2
в другом тайнике. Поэтому мы можем просто стереть это:
$ git reset --hard
Теперь мы вернемся к состоянию, которое у нас было бы, если бы мы выполнили только один git stash save
, без --keep-index
: мы находимся на master
, при этом рабочий каталог очищен, и один сохраненный файл сохраняется, Мы можем git stash list
it, git stash show
и т.д. Итак, теперь:
$ git stash pop --index
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: f2
#
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: f3
#
Dropped refs/[email protected]{0} (7efe9a65c44156921bbbcb6a3df4edc5cb44492b)
и у нас все есть. (Или, без --index
, stash
будет просто применять все изменения к рабочему каталогу, а не восстанавливать индекс и work-dir.)
Использование git stash apply
Самое приятное в git stash pop
заключается в том, что оно применяется, а затем удаляет самую верхнюю запись. Досадная вещь, она применяется, а затем бросает запись. Если вы используете git stash apply
вместо этого, он зависает на нем.
Кроме всего прочего, это очень удобно, если вы пропустили --index
как --keep-index
(я делал это более одного раза, набрав это), или оставьте его, а потом решите, что было бы неплохо его использовать. Вы можете git reset --hard
и повторно выполнить apply
.
Если вы закончили с записью, git stash drop entry
удалит ее из журнала. Например, предположим, что вы выполняете git stash apply --index '[email protected]{1}'
, а затем решите все это хорошо и захотите add
и/или commit
, а затем забудьте об этом тире. Затем вы можете git stash drop '[email protected]{1}'
. Недостатком является то, что это повторяет остальные: что было [email protected]{2}
становится [email protected]{1}
и так далее. Мне иногда легче держать их вокруг и использовать git stash clear
, чтобы избавиться от всех из них сразу, в конце.
Подождите минуту, что с этими --index
-es?
По умолчанию git stash apply
и git stash pop
берут сохраненный индекс ( "изменения, поставленные для фиксации" ) и незавершенное производство ( "изменения не поставлены для фиксации" ), и вставляют их в действие как работу -прогресс. Часто это прекрасно, но если вы тщательно поставили несколько бит и оставили других неуправляемыми, вам вполне может понадобиться все это. Аргумент --index
для apply
(и pop
) пытается это сделать. Иногда это оказывается "слишком тяжелым". В этом случае у вас есть два варианта: оставьте --index
или используйте git stash branch
.
Использование git stash branch
Я упоминал выше, в разделе измененных коммитов против stashes, что вы можете добавить новую метку ветки в фиксацию, на которой есть набросок, а затем apply
или даже pop
соответствующий stash, с --index
, и он всегда будет работать. Причина проста: stash - это фиксация слияния индекса и WIP, соответствующая фиксации, на котором они включены. Если вы проверите, что commit (как "отсоединенный HEAD" ), индекс и WIP будут применяться чисто.
Итак, предположим, что вы добавляете новое имя ветки в рассматриваемом коммите и переходите к новой ветке (git checkout -b newname
). Теперь примените (и вытащите) stash, используя --index
: теперь вы находитесь в точно таком же состоянии, что и при первом запуске git stash save
, за исключением того, что ветка имеет другое имя. И это то, что делает git stash branch
: вы даете ему новое имя ветки и указываете, какой кошелек использовать (по умолчанию это refs/stash
, A.K.A. [email protected]{0}
). Он использует эту запись stash для поиска родительского коммита, прикрепляет туда название ветки и затем выполняет git stash pop --index
.
На этом этапе вы можете использовать git status
, git diff --cached
, git diff
и т.д., чтобы узнать, что в индексе, а что нет, решить, что еще добавить, а затем git commit
добавить новый материал к новой ветки, которую вы создали.