执行 git stash --include-untracked 时丢失跟踪文件

Lost tracked files when doing git stash --include-untracked

当我git status时,有跟踪和未跟踪的文件。那天早些时候,我刚刚得知 git stash --include-untracked 会存储未跟踪的文件。当时它对我有用。所以我认为 git stash --include-untracked 会保存跟踪和未跟踪文件的更改。但是当我 git stash apply 时,只剩下未跟踪文件的更改。跟踪文件的更改丢失。

这里有些可疑,但可能不是藏匿处本身

git stash --include-untracked,可以简称为 git stash -u,使 3 次提交用于存储。

前两个与往常一样:一个用于保存您 运行 git stash 时索引中的任何内容,另一个用于保存 [=255] 中的任何内容=]——但只跟踪文件——当时。换句话说,持有索引的 i 提交持有 git write-tree 的结果,而 w 提交持有(相当于)git add -u && git write-tree 的结果(尽管隐藏代码以困难的方式做到这一点,或者在过去的 shell 脚本存储中做到了。

如果你 运行 git stash 没有 --all--include-untracked,这就是藏匿处的全部内容:它会有i(索引状态)和 w(work-tree 状态)的两个提交,它们都将当前提交 C 作为它们的第一个 parent。提交 w 的第二个 i parent:

...--o--o--C   <-- HEAD
           |\
           i-w   <-- stash

如果你添加-u-a,但是,你会得到一个三个-commit stash : 提交 w 获取第三个 parent,我们可以称之为 u 的提交,它包含未跟踪的文件。第三个parent没有自己的parent(是孤儿/root-commit),所以现在画的是:

...--o--o--C   <-- HEAD
           |\
           i-w   <-- stash
            /
           u

这个新提交的有趣之处在于,它在 work-tree 中的效果也是如此:*提交 u 包含 个未跟踪的文件.**

请记住,提交是所有(跟踪的)文件的完整快照。提交 u 是由——在临时索引中——丢弃所有tracked文件,而是跟踪部分或全部未跟踪 个文件。此步骤要么仅添加 untracked-but-not-ignored 个文件 (git stash -u),要么添加 所有 个文件 (git stash -a)。然后Git写入commitu,使用git write-tree将临时索引变成树放入commitu,使得commitu包含 所选文件。

现在这些选定的文件已提交 ugit stash 将它们从 work-tree 中删除。实际上,它过去只是 运行 git clean 和适当的选项。新发烧友 C-coded git stash 仍然做同样的事情(但是,人们可能希望,错误更少;见下文)。

这与它对 i and/or w 中的文件所做的类似:它有效地执行 git reset --hard,因此 work-tree的跟踪文件匹配 HEAD 提交。 (也就是说,除非您使用 --keep-index,否则它会执行此操作,在这种情况下,它会重置文件以匹配 i 提交。)此时的 git reset 对 [=177 没有影响=]untracked 文件,这些文件在 git reset 的范围之外,并且对当前 b运行ch 没有影响,因为重置故意将其保留在 HEAD

虽然在提交 u 中隐藏了一些未跟踪的文件,但是,git stash 然后 从 work-tree 中删除 这些文件。这在以后(也许也是立即)非常重要。

注意:git stash push 与路径规范结合使用时存在错误,这可能会影响所有内容,但尤其会影响使用 -u-a,其中某些版本的 Git 删除了 太多 文件。也就是说,您可能 git stash 只是文件的一部分,但是 Git 会 git reset --hardgit clean 所有 文件,或者文件太多。 (我相信今天这些都已修复,但总的来说,我根本不建议使用 git stash,尤其是花哨的 pathspec 变体。删除实际上没有隐藏的未跟踪文件是特别令人震惊的行为,并且 版本的 Git 可以做到这一点!)

你描述了一个 apply 时间的问题,但可能不是通常的问题

这是你说的:

I thought git stash --include-untracked would save both tracked and untracked files' change.

一如既往,Git 不保存 更改 ,它保存 快照

But when I 'git stash apply`, there is only untracked files' change left. The tracked files' change are lost.

应用普通 (no-untracked-files) 存储可以通过两种方式之一完成,具体取决于您是否使用 --index 标志。没有 --index 的变体更容易解释,因为它实际上只是 忽略 i 提交。 (带有 --index 标志的变体首先在 diff 上使用 git apply --index,如果失败,建议您尝试不使用 --index。如果您 想要 --index 的影响,这是一个糟糕的建议,你应该忽略它。不过,对于这个答案,让我们完全忽略 --index 选项。)

注意:这不是--keep-index标志,而是--index标志。 --keep-index 标志仅在 创建 存储时适用。 --index 标志在 应用 存储时应用。

要应用 w 提交,直接 Git 运行s git merge-recursive。作为用户,这不是您应该做的事情,当 git stash 这样做时,这也不是那么明智,但这就是它的作用。效果很像 运行ning git merge,除了如果你有未提交的更改在您的索引 and/or work-tree 中,可能无法以任何自动方式 return 进入此状态。

如果您从 "clean" 索引和 work-tree 开始,但是——也就是说,如果 git statusnothing to commit, working tree clean——这个合并操作几乎与一个常规的 git mergegit cherry-pick,在很多方面。 (请注意,git mergegit cherry-pick 都要求 至少在默认情况下是干净的。)合并操作 运行 与 merge base 设置为提交 w 的 parent,current--ours 提交为当前提交通常,另一个或 --theirs 提交正在提交 w.

也就是说,假设您的提交图现在看起来像这样:

       o--o--A--B   <-- branch (HEAD)
      /
...--o--o--C
           |\
           i-w   <-- stash
            /
           u

这样您就可以提交 B。应用存储的合并操作执行 three-way 合并,C 作为合并基础,w 作为 --theirs 提交,当前的 commit/work-tree 作为--ours 提交。 Git 比较 CB 以查看 我们 更改了什么,以及 Cw 以查看 他们改变了,结合了两组的不同。

这就是合并到 B 的方式 运行, 前提是 Git 可以首先 un-stash 提交 u。此时通常的问题是Git 不能 un-stash u.

请记住,提交 u 完全(且仅)包含您创建存储时存在的 未跟踪 文件,然后 Git 被删除使用 git clean(和适当的选项)。 work-tree.中必须仍然缺少这些文件,如果不存在,git stash apply将无法提取来自 u 的文件将不会继续。

由于未跟踪的文件是未跟踪的,因此很难知道它们是否发生了变化

But when I 'git stash apply`, there is only untracked files' change left. The tracked files' change are lost.

您谈论未跟踪文件中的更改

Git 当然不会存储更改,因此您无法通过这种方式找到它们。如果文件未被跟踪,它们现在也不在索引中。所以:您如何知道它们 已更改? 您需要一些其他文件集来与它们进行比较。

提取提交 u 的步骤应该是 all-or-nothing:它应该提取所有 u 文件,或者不提取。如果它确实提取了所有 u 文件,git stash apply 应该继续尝试合并,有点像 git cherry-pick -n(除了 cherry-pick 也写入索引),提交w 在藏匿处。那应该给你留下提取的 u 文件 合并的 w-vs-C 更改,在你的 work-tree.

如果 C-vs-work-tree 与 C-vs-w 之间存在冲突,您应该在 work-tree,并且您的索引应该像往常一样针对冲突合并进行扩展。

如果您可以为您的问题制作一个重现器,那么这里可能会提供大量的清晰度。

我发现这个问题是因为我发现自己处于同样的情况 - 我从另一个 SO 答案中复制并粘贴了 git stash -u 作为一种据称隐藏我未跟踪的文件的方式,然后当我这样做时感到震惊 git stash apply 并且它们没有恢复。

@torek 有两个 and 答案,它们解释了当您 git stash -u 时 git 在做什么以及为什么文件没有恢复。在他们的帮助下,我想我已经拼凑了恢复它们所需的操作。

这将取决于您在此期间没有再次藏匿或做其他 git 事情,如果您有,那么下面的一些可能需要调整。

如果您阅读了@torek 的任何回答,您就会知道 git stash -u 实际上进行了三次提交 - 其中一次有您未跟踪的文件,但它不是被 [=13= 应用的那个] 并且不会显示在 git stash list.

您应该可以通过以下方式找到它:

git show stash^3

如果这看起来像是您未跟踪的文件,那么您很幸运!

现在只需:

git cherry-pick --no-commit $(git rev-parse stash^3)

这将恢复未跟踪的文件...尽管暂存为“要提交的更改”

git reset 到 un-stage 他们)