TL; DR
Как работает git stash
, довольно просто.Это последствия , которые усложняются.В этом случае ваш неотслеживаемый файл находится в дополнительном коммите, который git stash show
не показывает.
Чтобы показать, что находится в этом дополнительном коммите, вы можете использовать:
git show stash^3
который будет показывать каждый такой файл как добавленный.
Long
Что делает git stash
, это делает два коммита, а иногда и три, и вы используете этот третий режим коммитов, ни один из которыхесть на любой ветке.Необходимые два коммита сохраняют содержимое index и содержимое рабочего дерева .Третий коммит, если он существует, сохраняет файлы без отслеживания, за исключением игнорируемых или без отслеживания и игнорирования (и без других файлов).
Следовательно, что означает в , фиксация равна двум(или три) моментальных снимка вместе с остальными метаданными, которые сопровождают каждый коммит, т. е. двумя (или тремя) блоками метаданных.За одним исключением, метаданные в этих блоках не особенно полезны и в основном могут быть проигнорированы.
Здесь также стоит упомянуть, как работает git status
, поскольку они связаны между собой.Вы должны знать о том, что Git предоставляет вещь, называемую по-разному index , staging area или (редко в наши дни) cache в зависимости от того, какая часть Git выполняет вызов.Поскольку речь идет в основном о git stash
, все, что я здесь скажу, это то, что git status
выполняет два сравнения: он сравнивает HEAD
с индексом, чтобы найти то, что он называет файлами, подготовленными для фиксации., затем отдельно сравнивает индекс с рабочим деревом, чтобы найти то, что он называет файлами, не подготовленными для фиксации .
Фон
Вы, вероятно, уже знаетечто каждый коммит Git содержит полный снимок всех ваших исходных файлов (ну, все файлы, которые были отслежены во время вашего коммита).Вы, вероятно, также знаете, что коммит содержит метаданных: , кто его сделал (имя пользователя и адрес электронной почты), когда (отметка даты и времени) и почему (сообщение в журнале), а также хеш-идентификатор объекта. родительский коммит: коммит, который предшествует этому.Каждый коммит получает свой собственный уникальный хэш-идентификатор, отличный от идентификатора любого другого коммита, но вычисленный таким образом, что каждый Git во вселенной согласится, что , что коммит получает , что хэш-идентификатор.
Поскольку каждый коммит запоминает хеш-идентификатор своего непосредственного предшественника, нам нужно знать только хеш-код последнего коммита в любой цепочке коммитов:
A <-B <-C (need C's hash ID)
У нас есть Git read commit C
, чтобы найти хэш-идентификатор B
, затем прочитайте B
, чтобы найти хэш-идентификатор A
.Имя ветки, например master
, просто содержит хэш-идентификатор последнего коммита в серии.
Чтобы сделать новый коммит, Git просто сохраняет все, что находится виндекс прямо сейчас , добавляет ваше имя и адрес электронной почты и т. д., устанавливает parent в текущий коммит и записывает все это как новый коммит,который генерирует новый и уникальный хэш-идентификатор новой фиксации.Мы будем называть этот новый коммит D
, а D
указывает на C
, поэтому нарисуем это как:
A--B--C <-- branch
\
D
Чтобы запомнить, что D
было добавлено в веткуbranch
, Git теперь заполняет новый хэш-идентификатор D
в имя branch
, давая нам:
A--B--C--D <-- branch
По разным причинам, в том числе для экономии местафайлы внутри коммитов находятся в специальной, только для чтения, только для Git, замороженной, сжатой форме.Только Git может использовать это, и никто, даже Git, не может изменить ни одного из них.Мне нравится называть эти файлы freeze -ried , хотя это не официальный термин Git.
Вам, конечно, нужны ваши файлы незамерзшие и повторно увлажненные.Эти файлы попадают в ваше рабочее дерево, где вы можете их видеть и работать с ними.Таким образом, обязательно есть две копии каждого файла: лиофилизированная в текущем коммите, плюс используемая версия в вашем рабочем дереве.
Git добавляет индексную / промежуточную область в качестве промежуточной точки приземления или запуска, хотя: в индексе также есть высушенная копия файла. В отличие от фиксированной версии, вы можете заменить эту в любое время. Вот что делает git add
: он замораживает-высушивает копию рабочего дерева и записывает ее в индекс, заменяя предыдущую копию индекса или создавая файл в индекса, если его там не было до этого.
Тот факт, что Git сохраняет то, что находится в index , а не в work-tree , объясняет, почему мы должны запускать git add
так часто. Это также делает git commit
чрезвычайно быстрым: нет необходимости сканировать все рабочее дерево, повторно сжимая каждый файл, чтобы увидеть, изменился ли он. Вы уже повторно сжали все важные файлы, когда git add
удалили их. Сублимированная индексная копия уже имеет правильный формат для нового коммита. Git может просто упаковать их, и все готово.
Отслеживаемые и неотслеживаемые файлы
Определение отслеживаемого файла очень просто: это любое имя файла, которое сейчас находится в индексе . Поскольку git commit
сохраняет то, что находится в индексе, отслеживаемая версия файла - это то, что будет в новом коммите. Неважно, что сейчас находится в рабочем дереве: файл просто должен быть в индексе. Какая бы форма там ни была, , что - это то, что будет совершено.
Поэтому неотслеживаемый файл определяется очень просто: это любой файл, имя которого находится в рабочем дереве, но отсутствует в индексе. (Если его нет ни в индексе , ни в рабочем дереве, его просто не существует. Сколько существует несуществующих файлов, которых сейчас нет в вашем индексе или рабочем дереве ? :-) Это на самом деле имеет счетный ответ на большинстве систем, но это такое огромное число, о котором не стоит слишком много думать: оно порядка 254 255 в Linux, например.) Не отслеживается файл может быть просто не отслежен, в этом случае Git будет иногда жаловаться на него, или не отслеживаться, а также игнорируется , что закрывает жалобы.
(Нет такого понятия, как отслеживаемый, но игнорируемый файл: если файл отслеживается, он просто не игнорируется по определению. * некоторые специальные биты состояния, которые можно установить для файлов в индексе , но давайте не будем вдаваться в подробности.)
git stash
коммитов
В середине документации git stash
они упоминают коммиты I
и W
, которые я обычно называю коммитами i
и w
. Задача команды stash - сделать эти коммиты без какого-либо изменения текущей ветви, а затем обновить refs/stash
вместо имени ветви, чтобы сохранить один из их хэш-идентификаторов. Этого должно быть достаточно, чтобы найти оба коммита.
Код хранения делает коммит i
почти обычным способом. Как мы видели выше, git commit
делает фиксацию, оборачивая высушенные замораживанием файлы, устанавливая метаданные с родительским элементом, являющимся текущим коммитом, записывая коммит и записывая хэш-идентификатор нового коммита в имя текущей ветви. Если мы просто остановим git commit
последний последний шаг и сохраним хеш-идентификатор где-нибудь еще, мы получим именно то, что нам нужно:
A--B--C--D <-- branch
\
i (git-stash will save i's hash ID somewhere)
Теперь git stash
нужно каким-то образом сохранить текущее рабочее дерево , и, если вы его попросили, сохраните и третий коммит. Давайте пока предположим, что нам не нужен третий коммит, и просто сделаем w
. Мы хотели бы настроить все так, чтобы w
содержал копии всех файлов рабочего дерева, которые отслеживаются . Для этого код stash создает второй временный индекс и копирует в него все версии всех файлов рабочего дерева. Он использует некоторый хитрый код, чтобы избежать ненужной повторной сушки, но в принципе это просто:
for (every file $f in the real index): copy $f into temporary index
Код stash затем делает коммит w
из этого временного индекса, используя i
и текущий коммит в качестве его (двух) родителей:
A--B--C--D <-- branch
|\
i-w (git-stash now has w's commit hash too)
Затем git stash
просто добавляет w
к refs/stash
, используя операцию в стиле push, если refs/stash
уже существует, или создает refs/stash
, если нет:
A--B--C--D <-- branch
|\
i-w <-- refs/stash
Мы вернемся к последним битам git stash
через мгновение.
Третий коммит в трехкомпонентном тайнике
Если вы решите сделать третий коммит, который я называю u
для "неотслеживаемых" файлов, код тайника записывает этот третий коммит до записи w
совершать. Чтобы выполнить фиксацию, git stash
перечисляет все неотслеживаемые файлы, включая (-a
/ --all
) или исключая (-u
/ --include-untracked
) игнорируемое подмножество неотслеживаемых файлов. Затем он выполняет трюк, аналогичный описанному для коммита w
: он создает временный индекс и копирует каждый из перечисленных файлов в этот временный индекс, из которого он делает коммит u
.
Код stash
дает родительского элемента u
commit no . Этот u
коммит просто болтается там, ни с чем
A--B--C--D <-- branch
|
i
u
Затем, с фиксацией i
и u
, git stash
возвращается к выполнению фиксации w
, используя временный индекс, как и ранее, и копируя в него файлы рабочего дерева в зависимости от их присутствия. в реальном / регулярном индексе. Затем, когда он делает w
коммит, он дает w
третий родитель, а именно u
коммит, который он только что сделал:
A--B--C--D <-- branch
|\
i-w <-- refs/stash
/
u
и записывает хеш-идентификатор w
коммита в refs/stash
, как и раньше.
Последний шаг создания тайника - очистка индекса и рабочего дерева
Сделав эти два или три коммита, git stash
теперь должен очистить индекс и рабочее дерево. По умолчанию здесь просто запускается git reset --hard
, который копирует все высушенные сублимацией файлы из текущего коммита в индекс, а затем в рабочее дерево. При использовании -u
или -a
для создания третьего коммита, git stash
также использует git clean
или его эквивалент для удаления любых файлов, которые он поместил в этот третий коммит.
(При использовании опции --keep-index
git stash
сбрасывает рабочее дерево в соответствии с индексом, которое оно оставляет в покое, чтобы индекс соответствовал фиксации i
. Любая очистка из -a
или -u
остается то же самое.)
Последствия тайника с тремя коммитами
Чтобы правильно восстановить (pop
или apply
) тайник, Git требует, чтобы файлы в индексе и / или рабочем дереве были "чистыми". Для двухкомпонентных хранилищ Git не делает это жестким требованием: он просто пытается объединить хранилище с текущим рабочим деревом. Эффект может быть большим беспорядком, и в некоторых случаях это трудно или невозможно полностью изменить. Это означает, что часто неразумно git stash apply
или git stash pop
, если git status
говорит что-либо, кроме , ничего не коммитить, рабочее дерево чистое; , но это ваш выбор.
Однако для трёхкомпонентных тайников Git более осторожен. Как ни странно, это может быть больше разочарование. В частности, Git старается не загромождать любые файлы рабочего дерева, которые существуют и не отслеживаются, и будут перезаписаны путем извлечения файлов из коммита u
. По сути, это означает, что вы часто должны запускать git clean
только для проверки тайника, сделанного с -a
или -u
.
Когда вы успешно извлечете такой тайник, Git будет иметь:
- Извлекает файлы коммита
u
в рабочее дерево (все они должны быть новыми и не отслеживаться, и в зависимости от состояния ваших .gitignore
файлов сейчас по сравнению с тогда, вероятно, аналогично игнорируются / не игнорируются как и раньше).
- Объединение файлов фиксации
w
с существующим рабочим деревом.
- Если вы использовали
--index
, применили результат сравнения i
против его родителя к существующему индексу, используя git apply --cached
.
Два из этих шагов такие же, как для любого тайника.
Если тайник прикреплен к коммиту D
, а индекс и рабочее дерево нетронуты и соответствуют D
, git stash apply --index
всегда будет успешным (за исключением любых ошибок git stash
). Следовательно:
git checkout $(git rev-parse refs/stash^1)
git reset --hard
git stash apply --index
будет корректно применять двухкомпонентный тайник и полностью восстанавливать состояние из git stash
, но для трехкомпонентного тайника необходимо добавить команду git clean
, используя -df
или -dfx
для удаления u
файлов.Обратите внимание, что и git reset --hard
, и git clean -dfx
могут быть разрушительными для работы, которая не сохраняется нигде в Git, поэтому рекомендуется убедиться, что такая работа где-то сохранена (возможно, как ни странно, с использованием git stash save -a
:-)).