Здесь есть что-то подозрительное, но это, вероятно, не сам тайник
git stash --include-untracked
, который можно кратко записать git stash -u
, делает три коммитов для тайника.
Первые два - это те же два, что и обычно: один для хранения всего, что было в индексе в то время, когда вы запустили git stash
, а другой для хранения того, что было в рабочем дереве - но только для отслеживаемых файлов - вто время. Другими словами, фиксация i
, содержащая индекс, содержит результат git write-tree
, а фиксация w
содержит результат (эквивалент) git add -u && git write-tree
(хотя код stash делает это сложным образом, илиделал в старые времена в хранилище сценариев оболочки).
Это все, что было бы в хранилище, если бы вы запустили git stash
без --all
или --include-untracked
: у него было бы двакоммиты для i
(состояние индекса) и w
(состояние рабочего дерева), оба из которых имеют текущий коммит C
в качестве первого родителя. Commit w
имеет i
в качестве второго родителя:
...--o--o--C <-- HEAD
|\
i-w <-- stash
Если вы do добавите -u
или -a
, однако вы получите three -commit stash: commit w
получает третьего родителя, фиксацию, которую мы можем назвать u
, которая содержит неотслеживаемые файлы. У этого третьего родителя нет своего родителя (это сирота / корневой коммит), поэтому чертеж теперь выглядит так:
...--o--o--C <-- HEAD
|\
i-w <-- stash
/
u
Интересная вещь об этом новом коммите и его эффекте в рабочем дереветакже: * Коммит u
содержит только неотслеживаемых файлов. **
Помните, что фиксация - это полный и полный снимок всех (отслеживаемых) файлов. Фиксация u
производится во временном индексе , отбрасывая все отслеживаемых файлов и, вместо этого, отслеживая некоторые или все неотслеживаемых файлов. Этот шаг добавляет только файлы без отслеживания, но не игнорирования (git stash -u
) или все файлы (git stash -a
). Затем Git записывает коммит u
, используя git write-tree
, чтобы превратить временный индекс в дерево для фиксации u
, чтобы коммит u
содержал только выбранных файлов.
Теперь, когда эти выбранные файлы находятся в коммите u
, git stash
удаляет их из рабочего дерева . На практике раньше просто запускали git clean
с соответствующими параметрами. Новый поклонник C-кодированный git stash
по-прежнему делает аналог (но, можно надеяться, с меньшим количеством ошибок; см. Ниже).
Это похоже на то, что он делает для файлов в i
и /или w
: он эффективно выполняет git reset --hard
, так что отслеживаемые файлы рабочего дерева соответствуют фиксации HEAD
. (То есть он делает это, если вы не используете --keep-index
, в этом случае он сбрасывает файлы в соответствии с фиксацией i
.) git reset
на данный момент не влияет на неотслеживаемых файлов,которые выходят за рамки git reset
и не влияют на текущую ветвь, поскольку сброс преднамеренно сохраняет это в HEAD
.
При этом некоторые неотслеживаемые файлы были сохранены в коммите u
, однако, git stash
затем удаляет эти файлы из рабочего дерева. Это очень важно позже (и, возможно, также сразу).
Примечание: произошла ошибка при объединении git stash push
со спецификациями пути, которая потенциально затрагивает все, но особенно затрагивает варианты хранения, созданные с помощью-u
или -a
, где некоторые версии Git удаляют слишком много файлов. То есть, вы можете git stash
просто подмножество ваших файлов, но тогда Git будет git reset --hard
или git clean
всех файлов или слишком много файлов. (Я полагаю, что все они исправлены сегодня, но в целом я вообще не рекомендую использовать git stash
, и особенно не причудливые варианты pathspec. Удаление неперехваченных файлов, которые на самом деле не были сохранены, является особенно вопиющим поведением, и некоторые версии Git делают это!)
Вы описываете apply
временную проблему, но, возможно, не обычную
Вот что вы сказали:
Я думал, что git stash --include-untracked
сохранит изменения как отслеженных, так и неотслеживаемых файлов.
Как всегда, Git не сохраняет изменений , он сохраняет снимков.
Но когда я делаю git stash apply, меняются только неотслеживаемые файлы. Изменения в отслеживаемых файлах будут потеряны.
Применение стандартного тайника (без отслеживания файлов) выполняется одним из двух способов, в зависимости от того, используется ли флаг --index
. Вариант без --index
легче объяснить, поскольку буквально игнорирует коммит i
. (Вариант с флагом --index
сначала использует git apply --index
в diff, и, если это не удается, предлагает попробовать без --index
. Если вы хотите с эффектом --index
, этоужасный совет, и вы должны его игнорировать. Однако для этого ответа давайте полностью проигнорируем параметр --index
. *
Примечание: это не флаг --keep-index
, а скорее--index
флаг. Флаг --keep-index
применяется только тогда, когда создает тайник. Флаг --index
применяется, когда применяет тайник.
Чтобы применить коммит w
, Git напрямую запускает git merge-recursive
. Это не то, что вы должны когда-либо делать как пользователь, и когда git stash
делает это, это не совсем так мудро, но это то, что он делает. Эффект во многом похож на выполнение git merge
, за исключением того, что если у вас есть незафиксированные изменения в вашем индексе и / или рабочем дереве, может оказаться невозможным вернуться в это состояние каким-либо автоматическим способом.
Однако, если вы начнете с «чистого» индекса и рабочего дерева, то есть если git status
говорит nothing to commit, working tree clean
, то эта операция слияния почти точно такая же, как и обычная git merge
или git cherry-pick
, во многих отношениях. ,(Обратите внимание, что и git merge
, и git cherry-pick
требуют , чтобы все было чисто, по крайней мере по умолчанию.) Операция слияния выполняется с базой слияния , установленной в родительский элемент commit w
, текущий или --ours
коммит является текущим коммитом как обычно, а другой или --theirs
коммит является коммитом w
.
То есть предположим, что ваш коммитГрафик теперь выглядит так:
o--o--A--B <-- branch (HEAD)
/
...--o--o--C
|\
i-w <-- stash
/
u
, так что вы находитесь на коммите B
. Операция слияния для применения stash выполняет трехстороннее слияние с C
в качестве базы слияния и w
в качестве --theirs
коммита, и с текущим коммитом / рабочим деревом в качестве --ours
коммита. Git diffs C
против B
, чтобы увидеть, что мы изменили, и C
против w
, чтобы увидеть, что они изменились, и объединяет два набора различий.
Так будет выполняться слияние с B
, при условии , что Git может сначала разблокировать коммит u
. Обычная проблема на этом этапе заключается в том, что Git не может un-stash u
.
Помните, что commit u
содержит точно (и только) unracked файлы, которые присутствовали при создании тайника и которые Git затем удалял с помощью git clean
(и соответствующих опций). Эти файлы все еще должны отсутствовать в рабочем дереве. Если они не отсутствуют, git stash apply
не сможет извлечь файлы из u
и не будет продолжать работу.
Поскольку неотслеживаемые файлы не отслеживаются, трудно понять, изменились ли они
Но когда я 'git stash apply`, остается только изменение неотслеживаемых файлов. Изменения в отслеживаемых файлах потеряны.
Вы говорите о изменениях в неотслеживаемых файлах.
Git, конечно, не сохраняет изменения, поэтому вы можете 'найти их таким образом. И если файлы не отслеживаются, они тоже не включены в индекс. Итак: откуда вы знаете, что они изменены? Вам нужен какой-то другой набор файлов, с которым их можно сравнить.
Шаг, который извлекает коммит u
, должен быть полностьюили-ничего: он должен либо извлечь все файлы u
, либо нет. Если он извлекает все u
файлы, git stash apply
должен продолжить попытки слияния, как бы с помощью git cherry-pick -n
(за исключением того, что cherry-pick также пишет в индекс), зафиксировать w
в тайнике. Это должно оставить вас с извлеченными u
файлами и объединенными w
-vs- C
изменениями в вашем рабочем дереве.
Если существуют конфликты между C
-vs-work-tree и C
-vs- w
, у вас должны быть маркеры конфликта, присутствующие в рабочем дереве, и ваш индекс должен быть расширен как обычно дляконфликтное слияние