TL;DR
在使用--index
之前,您需要执行git reset --hard HEAD
(或任何等效命令)。所有常规的硬重置警告都适用。
Long
我在评论中链接了一个问题How do I properly git stash/pop in pre-commit hooks to get a clean working tree for tests?,其中展示了如何进行最终的恢复(或等效操作),以及一些相关的注意事项。但是,针对所提出的问题——具体而言是如何强制Git在应用存储时使用快进模式——答案是不行的,实际上,这个问题甚至没有意义:快进是与存储和还原不同的概念。1
Git存储只是一组提交(如果不使用--all
或--include-untracked
选项,则为两个,否则为三个),具有特殊的排列方式。这些提交保存了:
git stash
执行时的索引(使用git write-tree
);
git stash
执行时的工作树内容(使用相当复杂的代码);
- 此列表中的最后一个,但实际上是最先完成的,如果使用了
--all
或--include-untracked
,则是未跟踪的文件,包括被忽略的文件(--all
)或未跟踪的文件,但不包括被忽略的文件(--include-untracked
)。
然后,Git将工作树重置为与HEAD
提交匹配的内容,并且如果使用了--all
或--include-untracked
,则也会删除第三个提交中存储的文件。但是,当您使用--keep-index
时,Git会将工作树重置为匹配索引内容。
名为refs/stash
的引用被修改为指向工作树提交。该提交以其父提交作为HEAD
提交(父级#1)、索引提交(父级#2)和(如果存在)未跟踪文件提交(父级#3)。索引的父提交为HEAD
提交。未跟踪文件提交没有父提交(是一个根提交):
...--o--o--o <-- refs/heads/somebranch (HEAD)
|\
i-w <-- refs/stash
/
u
更常见的情况是,没有使用 u
的相同操作。
当 git stash
重置到 HEAD
(即没有使用 --keep-index
)时,你只需要运行 git stash pop --index
来撤销 git stash
的操作即可(注意:不要使用 --keep-index
!)。这将以相同的选项和参数运行 git stash apply
2,如果成功且没有合并冲突,则在同一藏匿中运行 git stash drop
。
应用程序可以同时使用索引提交和工作树提交来恢复你正在处理的内容,但默认情况下,它会忽略索引提交。添加 --index
告诉 Git 应用索引提交(转换为与当前索引内容的差异),使用 git apply --index
。如果失败,git stash
将停止并且不执行任何操作。在这种情况下,我建议使用 git stash branch
将藏匿变成一个新分支,尽管 git stash
仅建议不使用 --index
3。
无论如何,Git 然后尝试将工作树提交应用于当前工作树4。如果你在没有使用 --keep-index
的情况下隐藏,并且没有更改当前的工作树,则这将始终成功:当前索引和工作树将与 HEAD
提交匹配,因此这将保持当前索引不变并将所有差异应用于工作树提交本身,从而恢复隐藏的工作树。
此时的问题是,你确实使用了 --keep-index
,因此当前工作树与你设置的索引匹配,而不是与 HEAD
提交匹配。因此,在应用藏匿之前(带或不带 --index
),必须先重置工作树以匹配 HEAD
提交,即 git reset --hard
。你想要的索引和工作树状态都在即将应用的藏匿中,因此只要当前索引和工作树没有被预提交 / 推送代码修改,就是安全的。
一旦完成,对藏匿提交的 git apply --index
将恢复索引和工作树(除了链接的问题中的错误!)。
脚注
这些脚注是有意打乱顺序的,因为脚注 1 太长了。
git stash apply
命令的参数默认为
refs/stash
。如果您给它任何参数,则行为会更加复杂:在最近的Git版本中,如果您给它一个全数字参数
n,它将检查
stash@{n}
,否则它将使用您提供的任何参数。它将此字符串传递给
git rev-parse
以确保它转换为有效的哈希ID,并且当附加
:
、
^1
、
^1:
、
^2
和
^2:
时,这些也将转换为有效的哈希ID。如果该字符串同时产生具有
^3
和
^3:
的有效哈希ID,则也会记住它们。这些共同形成了
w_commit
、
w_tree
、
b_commit
、
b_tree
、
i_commit
和
i_tree
,以及如果存在,则为
u_commit
和
u_tree
。有关此操作的详细信息,请参见
gitrevisions文档。
简而言之,您传递给git stash apply
的任何参数都必须具有合并提交的形式,至少有两个父级。Git不会检查是否存在额外的父级超出潜在的三个,也不会检查此合并提交是否真的是一个存储:它只是假设如果它具有正确的父级集,您打算将其用作存储。
3这对于不想单独存储索引并在git stash apply
或git stash pop
上使用--index
的Git新手可能足够明智。但是,一旦您理解了索引,它显然是错误的:您想要将存储的索引相对于当前索引的更改还原到当前索引,而不是完全忽略它们!如果适当,提交您当前的索引,然后提交您当前的工作树,然后将存储转换为分支并提交其工作树,即可获得构建正确最终结果所需的所有内容。
4技术细节:该应用程序使用git merge-recursive
,这是实现git merge -s recursive
的方法,并在发生冲突时设置一些秘密环境变量以设置冲突标记上的名称。合并基础是制作存储库时的HEAD
提交,当前树是写入当前(未存储时)索引的结果,正在合并的项目是工作树提交,更准确地说,是其树。这利用了某些合并可以使用未提交的更改的事实。前端git merge
命令禁止带有未提交更改的合并尝试,因为当存在问题时,结果可能非常混乱。
1快进概念也比通常看到的要复杂一些。也就是说,在合并时我们看到它-请参见`git merge`和`git merge --no-ff`之间的区别是什么?,但它实际上是指更新引用,例如分支名称。如果新的提交哈希值具有旧的提交哈希值作为祖先,则分支名称更新为快进,即只有当git merge-base --is-ancester $old_hash $new_hash
返回零退出状态时。
当git merge
执行其中一个快进操作时,这意味着Git已将HEAD
提交更改为指向新哈希,并根据需要更新了索引和工作树。如果您将其快进到存储库中的工作树提交,则会将奇怪的技术合并工作树提交暴露给Git的其他部分,在那里它至少会非常令人困惑。
请注意,git fetch
和git push
也执行快进操作,或者使用--force
,允许对分支和(对于fetch)远程跟踪名称进行非快进更改。推送的接收方通常需要快进,因为这意味着更新的分支名称包含它以前具有的所有提交以及一些附加提交。强制的非快进更新会丢弃分支中的提交(无论是否添加新提交)。有点神秘的git fetch
输出以三种方式记录了远程跟踪名称是快进还是强制:
$ git fetch
remote: Counting objects: 1701, done.
remote: Compressing objects: 100% (711/711), done.
remote: Total 1701 (delta 1363), reused 1318 (delta 989)
Receiving objects: 100% (1701/1701), 975.29 KiB | 3.65 MiB/s, done.
Resolving deltas: 100% (1363/1363), completed with 284 local objects.
From [url]
3e5524907..53f9a3e15 master -> origin/master
61856ae69..ad0ab374a next -> origin/next
+ fc16284ea...4bc8c995a pu -> origin/pu (forced update)
9125ddae1..9db014fc5 todo -> origin/todo
* [new tag] v2.18.0 -> v2.18.0
* [new tag] v2.18.0-rc2 -> v2.18.0-rc2
请注意在记录更新到
origin/pu
的行前面的
+
,以及添加的单词
(forced updated)
。这是三种方法中的两种。但请注意两个缩写的提交哈希之间的点号:所有其他非强制更新的行都显示两个点,但此更新显示三个点。这是因为我们可以使用相同的三个点语法使用
git rev-list
或
git log
查看添加和删除的提交。
$ git log --oneline --decorate --graph --left-right fc16284ea...4bc8c995a
> 4bc8c995a (origin/pu) Merge branch 'sb/diff-color-move-more' into pu
|\
| > 76db2b132 SQUASH????? Documentation breakage emergency fix
| > f2d78d2c6 diff.c: add white space mode to move detection that allows indent changes
| > a58e68b88 diff.c: factor advance_or_nullify out of mark_color_as_move
[massive snippage]
< fc16284ea Merge branch 'mk/http-backend-content-length' into pu
|\
| < 202e4a2ff SQUASH???
| < cb6d3213e http-backend: respect CONTENT_LENGTH for receive-pack
< | 4486a82e5 Merge branch 'ag/rebase-p' into pu
< | a84cc85f3 Merge branch 'nd/completion-negation' into pu
[much more snippage]
--left-right
选项与三个点的语法一起使用,告诉Git标记提交来自哪一侧。在这种情况下,
>
提交现在位于pickup分支上,而
<
提交已被从该分支中删除。这些特定的删除提交现在完全没有引用,并将很快被垃圾收集。