撤消 git 结帐..

Undo git checkout.. with a twist

首先,让我澄清一件事:虽然有很多关于撤消 git 结帐的问题,但这不是(至少据我所知)重复的问题。

现在让我解释一下我的用例: 我正在使用稀疏结帐功能来获得一个工作副本,其中不包含中央远程仓库中的所有文件。

现在假设我想将一个文件添加到我的工作副本,但我犯了一个错误并签出了错误的文件。

我想还原我的工作副本,就像该文件从未检出过一样。

即:我想从我的工作副本中删除该文件,但我希望从远程存储库中删除该文件。 我一直在到处寻找,但仍然没有找到一种方法来做我想做的事。

您实际上不需要做任何事情。您 可以 做一些事情,但这不是必需的,如果您不小心提取的文件没有造成任何问题,您可能应该把它留在那里。

这可能需要一些解释。

I am using the sparse-checkout feature to have a working copy which does not contain all the files in the central remote repo.

虽然您的工作副本可以省略一些文件,但您的 存储库 不能省略这些文件。所以你已经有了它们。稀疏结帐选项唯一能做的就是防止它们出现在您的工作树中。

您可能已经知道这一点,但让我们回顾一下有关 Git 的一些项目,以确保我们拥有共同的词汇表:

  • A Git repository 本质上由两个数据库组成。 (通常大得多)主数据库保存提交和其他支持 Git 对象。第二个数据库通常要小得多,它包含名称——分支名称、标签名称和其他类似名称——并且对于每个名称,都有一个对应的对象哈希 ID。对于分支名称,这些哈希 ID 总是 commit 哈希 ID;其他名称有时可以包含一些其他内部 Git 对象的哈希 ID。

    两个数据库都很简单key-value stores。每个都有一个临时的 Git-特定的实现,尽管现成的数据库可以工作(尽管它会更慢且更难使用和管理,或者至少,这是使用私有数据库的借口)。

    主数据库中的所有对象(包括所有提交)都是完全只读的。这是因为密钥是散列 ID,而散列 ID 是对内容(存储在该密钥下的值)应用加密校验和算法的结果。 Git 在提取内容时进行验证:内容必须散列回密钥。这会检测(但无法纠正)任何数据库损坏。

  • 然后,提交是主数据库中的对象。它们有两个部分:一个快照(所有文件的快照,这些文件在创建快照时的形式)和一些元数据。我们将在这里跳过所有细节,因为它们无关紧要,但这样做的效果是每次提交都会存储每个文件。这包括您故意不通过稀疏检出检出的文件。

  • Git 使 new 从 Git 调用的 index 提交,或者暂存区,或缓存。现在最后一个术语很少见,主要出现在各种 Git 命令的 --cached 标志参数中。这三个名称描述了一个中间数据结构,Git 用于多种用途:

    • 密切关注您的工作树(缓存方面),并且
    • 为建议的下一个快照(临时区域方面)存储文件名和模式。

    在冲突合并期间扩展索引时会出现第三个目的,但我们将在此处跳过它,因为这与手头的问题无关。

  • 最后,在您的 工作树 中,Git 从提交中提取文件。通常 Git 从提交中提取 所有 文件。这里的实际做法是Git先把所有的文件复制到Git的索引中。这会为缓存部分创建 space,并创建名称和模式部分并存储 blob 对象哈希 ID 以表示文件的实际内容。

Git 需要此索引来保存提交中的 all 文件,即使在使用稀疏签出时也是如此。所以 Git 的索引总是包含每个文件。这需要相对较少的 space 因为实际的 内容 在大数据库中存储为 blob 对象。但是,如果您 使用稀疏检出,Git 然后将每个索引条目文件扩展为一个工作树副本,该副本是一个实际的、可读可写的文件,而不仅仅是数据库中的一些内部 blob 对象。

我们需要真实的文件来完成任何实际工作。如果我们需要做的只是保留文件以供 git diff 使用并进入新的提交等,我们实际上不必 读写 它们,我们可以将它们保留为内部 blob 对象,所以这就是 Git 对 没有 签出的所有提交所做的。

因此,这就是稀疏结帐进入画面的地方。我们只是告诉 Git:哦,顺便说一句,当你开始从索引中提取所有文件时,跳过其中一些。 为了告诉 Git,在索引和工作树之间的低级接口,我们 Git 在缓存数据中设置了一位。该位称为 skip-worktree 位,我们可以通过以下方式显式设置或清除它:

git update-index --skip-worktree path/to/file

或:

git update-index --no-skip-worktree path/to/file

请注意,这对大数据库中任何实际存储的对象没有影响,对我们工作树中(或不在我们的工作树中)的任何文件也没有实际影响。它只是设置或清除 索引条目 上的位。为此,索引条目必须存在。

然后,我们可以通过以下方式实现稀疏结帐:

  • 选择一个提交;
  • 将该提交读取到索引中,但尚未创建工作树;
  • 设置我们喜欢的所有跳过工作树位;和
  • 检查我们工作树的索引。

Git 中有低级命令可以执行此操作。我们使用稀疏校验 功能 而不是使用那些低级命令的原因是,对每个文件执行此操作是一件非常痛苦的事情。因此,稀疏签出功能只是让 git checkout 自动执行此操作 我们告诉 Git 哪些文件应该出现在我们的工作树中,哪些应该进入 Git 的索引,但设置了 skip-worktree 位。

现在让我们回到 git commit 并记下它的实际工作原理。当我们 运行 git commit 时,我们告诉 Git 进行新的提交。 Git此时不使用我们的working tree我们可以先运行git status得到一个listing,或者我们可以让git commit 运行 git status(默认情况下它会这样做:如果我们不想要它,我们必须明确地抑制它)并使用以下内容填充我们的提交消息模板结果,但无论如何,提交不会提交 from 我们的工作树。1 它来自索引——它已经存在每个文件,包括那些提取到我们的工作树的文件。

这意味着当您使用稀疏签出时,您仍然使用每个文件。只是所有文件都在 Git 的索引中,您(和程序)无法看到或更改它们。您的 工作树 省略了某些文件的扩展的普通文件格式,因此您无法查看或更改它们。它包含其他文件的扩展的普通文件格式,因此您可以查看和更改它们——但如果您确实更改了它们,您仍然需要运行git add 将它们复制回索引。2 Git 毕竟是要根据 index[= 中的内容构建下一次提交169=],不是你的工作树中的东西!

考虑这一点的一个好方法是索引包含您提议的下一次提交。由于索引有 all 文件(从当前提交中获取),因此工作树中的内容并不重要。 这就是为什么您无需执行任何操作的原因。您可以将工作树文件留在那里,即使您不打算对它执行任何操作。只要它在 Git 的索引中,它就会出现在新的提交中 无论它是否在您的工作树 中。所以不要费心删除它。


1当使用 git commit --onlygit commit --include with pathspecs 时,提交代码首先创建一个额外的 temporary索引,然后更新临时索引,就像通过 git add 一样,然后从临时索引进行新的提交。然后,当且仅当提交成功时,它才会调整实际索引。我们将跳过所有这些细节,但请注意,即使在这些模式下,提交也是从 an 索引构建的。只是 Git 不是使用“the”索引,而是使用临时辅助索引。

2这并不重要,但是 git add 步骤通过将工作树副本压缩回内部 Git 对象来工作,产生一个 blob 哈希 ID。这会自动立即对任何现有的匹配 blob 进行重复数据删除,以便存储库数据库仅在以前从未见过的内容时才会增长。 Git 然后将哈希 ID 填充到索引中,以便现在更新索引。


如果工作树文件妨碍您怎么办?

假设工作树文件太大以至于它填满了一个小的(SSD?)驱动器。您不需要它,它 的。您现在如何从稀疏结帐中删除它,而不从未来的提交中删除它?

如果你通读了上面的机制描述,答案是显而易见的——至少,高水平答案; Git 命令集可能仍然有点晦涩难懂(尽管我确实提到了它们)。您只需要从您的工作树中删除该文件的副本。这部分非常简单。您不需要任何特殊命令。用于删除文件的常规日常计算机命令,无论是 rm 还是 DEL 或其他,都有效,因为您的工作树是一组常规的日常文件。所以只是 rm bigfile 或其他什么。

但是,一旦您这样做,git status 就会开始抱怨:它会说该文件的工作树副本已经消失。更糟糕的是,一揽子 git add 操作可能 删除 index 副本,3 所以从此指出你可能需要小心 git add 命令。这是您要使用 Git 命令的地方:

git update-index --skip-worktree bigfile

这会设置我之前提到的 skip-worktree 位,稀疏结帐代码使用该位。 skip-worktree 位只是告诉各种 Git 命令,包括 git status 和 blanket en-masse git add 命令,应该完全忽略工作树副本或缺少副本。只保留索引中的任何内容,在索引中。

因此,这两个命令——日常的“删除文件”命令和带有 --skip-worktree 标志的 git update-index 命令——足以从你的工作树中删除文件而不影响Git 索引中的副本。索引副本将进入未来的提交,因为它应该。请记住,提交是删除重复文件,因此这只是重新使用早期提交的副本,并且基本上没有 space.

因此选择权在您:什么都不做(因为不需要做任何事情),或者不使用 Git 命令删除文件,如果 git status 收到投诉,设置跳过工作树位。


3为了使这个有意义,将 git add 视为意思 使某些文件的索引副本与该文件的工作树副本相匹配文件。如果已删除工作树副本,则会删除索引条目。