“git ls-files”究竟做了什么,我们如何从中删除文件?

Mug*_*gen 9 git

它是否显示来自本地存储库、临时存储库、远程存储库或其他地方的文件?

我经常看到“git ls-files”中存在的文件。该文件已从远程存储库中删除。之后我尝试做一个 git pull 。但是,该文件仍显示在此命令列表中。它不应该出现在这里,因为它也不存在于远程存储库中。

tor*_*rek 10

概括

你需要考虑一下 Git 存储每个文件至少三个,有时最多五个活动副本的想法:一个在当前提交中,一个(或两个或三个!)在index 中,还有一个——唯一的您可以在您的工作树中看到并使用它。该git ls-files命令查看这些副本,然后根据您提供给 的标志告诉您有关其中一些副本的信息git ls-files

如果没有每个文件的三到五个副本的想法,Git 中的很多东西将永远没有任何意义。(好吧,即使有了它,有些事情仍然很棘手,但这完全是另一个问题。)

我认为这里有两个问题。一个需要一些术语,然后另一个应该就位:

[ git ls-files]是否显示来自本地存储库的文件,

有点,但是:

临时存储库,

Git 没有临时存储库。每个存储库都有一些在不同的 Git 文档中被称为indexstaging area 的东西。(还有一个过时的第三个名称cache,它也出现在Git 词汇表中。)

远程仓库

绝对不是:不需要有任何远程仓库,即其他控释片有自己的仓库,在所有,如果有,只能git fetchgit push拥有你自己的Git调用了他们自己的Git和交换数据。(好吧,git ls-remote首先执行git fetch, 并git pull 运行 git fetch,因此这两个也与遥控器交换数据。但git ls-files没有。)

还是来自其他地方?

是的,有点。这让我们回到第一部分。因此,让我们采用Git 词汇表中定义的这三个术语。下面的斜体(包括粗斜体)文本直接来自链接的文档:

  • 存储库

    一组refs以及一个对象数据库,其中包含从 refs访问的所有对象,可能伴随着来自一个或多个瓷器的元数据。存储库可以通过替代机制与其他存储库共享对象数据库。 (所有链接都是他们的)

    这当然充满了更多的术语。为了稍微揭开它的神秘面纱,他们在这里说的是存储库本身包括索引和工作树:它主要由提交(及其内容)组成。当然,这需要我们定义“索引”和“工作树”,所以让我们继续:

  • 指数

    具有统计信息的文件集合,其内容存储为对象。索引是工作树的存储版本。说实话,它还可以包含工作树的第二个甚至第三个版本,在合并.

  • 工作树(我通常称之为工作树):

    实际签出文件的树。工作树通常包含HEAD提交树的内容,以及您所做但尚未提交的任何本地更改。

提交被永远冻结

当您运行 时git commit,Git 会为您的所有文件(好吧,无论如何,所有跟踪的文件)制作快照,并将其存储在提交中,以及一些元数据,例如您的姓名和电子邮件地址。这个提交大多是永久性的——你可以摆脱提交,通常会有一些困难,但为了方便起见,只需将它们视为永久性的——并且完全、完全、100% 只读。它是故意这样只读的,因为这允许其他提交共享相同的文件副本,因此如果您提交同一个文件一次、十次甚至一百万次,那么实际上只有一个该文件的副本在存储库。只有当您将文件更改为新版本时,Git 才必须提交一个新的、单独的副本。

提交被编号,但不是通过一个很好的简单顺序编号系统。也就是说,我们可以它们绘制为一系列简单的编号或字母的东西:

... <-C4 <-C5 <-C6 ...
Run Code Online (Sandbox Code Playgroud)

每个以后的提交都指向它的直接前一个。但它们的实际名称是丑陋的哈希 ID。每一个都保证是独一无二的,这就是为什么它们必须如此巨大、丑陋和随意。每个哈希 ID 实际上是一个加密校验和,根据提交的内容计算,因此宇宙中每个地方的每个 Git 都会同意提交,并且只有该提交,才能获得校验和。这是你——甚至 Git——无法更改它的另一个原因:如果你从存储库数据库中取出一个提交,修改它,甚至更改一个位,然后将它放回数据库,你得到的是一个新的 使用新的和不同的哈希 ID 提交。

所以提交是完全冻结的,永远。它们里面的文件也被永久冻结,并以一种特殊的 Git-only 格式压缩。我喜欢称这些文件为“冻干”。什么意味着,嘿,他们是伟大的存档,但他们是完全无用的获得任何工作做......和意味着Git的必须提供服用这些冻干文件和补水它们放入某种方式有用的形式。

工作树提供有用的形式副本

事情并没有比这更简单:工作树具有有用的形式,文件的再水化副本。因为它们只是您计算机上的普通日常文件,您可以查看、使用它们,随意更改它们,或者以其他方式使用它们。从技术上讲它们根本不在存储库中——它们就在它旁边。在典型的设置中,存储库本身位于.git工作树顶层的目录/文件夹中。

显然,如果您提取了一个提交来制作工作树,那么每个文件现在必须有两个副本:冻干提交的一个,加上常规工作的一个。Git可以到此为止。Mercurial确实到此为止:如果您使用 Mercurial 而不是 Git,您不需要担心第三个副本,因为没有第三个副本。但是 Git 继续存储更多的文件副本。

索引/暂存区位于提交和工作树之间

Git 在这里所做的是在冻干提交副本和工作树副本之间插入每个文件的第三个副本。第三个副本是提交文件格式——即预脱水——但由于不在提交中,它实际上并没有完全冻结:它可以随时更换。这就是这样git add做的:git add从工作树中获取文件的普通副本,将其压缩为冻干格式,并替换索引中的副本。或者,如果文件根本不在索引中,它会将副本放入索引中。

这就是为什么你必须一直git add归档。在水银,你只有hg add一个文件一次。之后,您只需运行hg commit,Mercurial 查看它知道的所有文件,并将它们冻结到一个新的提交中。在大型存储库中,这可能需要很长时间。相比之下,Git 已经拥有它应该知道的所有文件,并且已经在索引中脱水,因此git commit可以将这些脱水的文件打包到一个新的冻结提交中。这种速度的代价是git add,但是如果您对索引副本玩弄巧妙的技巧(例如,使用),git add -p您将获得的好处不仅仅是加速。

正如Git 词汇表在对索引的描述中提到的那样,索引在冲突合并期间扮演着扩展的角色。当您执行合并操作时——无论是 from git merge、 fromgit revertgit cherry-pick或任何其他使用合并引擎的 Git 命令——并且它不会顺利进行,Git 最终会将每个文件的所有三个输入都放入索引中,因此只需一份副本file.ext,您就会得到三份。但是只要您不在合并过程中,索引中就只有一个副本。

通常索引副本匹配HEAD冻结副本,或匹配工作树副本,或两者兼而有之。例如,在一个 fresh 之后git checkout,所有三个副本都匹配。然后file.ext在工作树中进行修改:现在提交和索引匹配,但它们与工作树副本不同。然后你git add file.ext,现在索引和工作树匹配,但它们与冻结副本不同。然后你git commit做一个新的提交,它成为当前的提交,所有三个副本再次匹配。

请注意,您可以修改工作树副本:

vim file.ext
Run Code Online (Sandbox Code Playgroud)

然后将更新的复制到索引中:

git add file.ext
Run Code Online (Sandbox Code Playgroud)

然后再次编辑它

vim file.ext
Run Code Online (Sandbox Code Playgroud)

这样,您可以使所有三个副本都不同。如果你这样做,git status会说你有更改暂存提交,因为索引副本与当前提交副本不同,说你有更改暂存以提交,因为工作树副本与索引不同复制。

工作树可以包含根本不在索引中的文件

索引最初只是当前提交的副本。Git 然后还将这些文件复制到工作树,以便您可以使用它们。但是您可以在工作树中创建文件而不是git add在它们上运行。这些文件现在不在索引中,如果您运行git commit,它们也不会在新提交中,因为 Git索引构建新提交。

您还可以索引中删除文件,而无需从工作树中删除它们:

git rm --cached file.ext
Run Code Online (Sandbox Code Playgroud)

删除索引副本。当然,它不能触及当前提交的冻结副本,但是如果您现在进行提交,则新提交根本不会包含file.ext在其中。(当然,之前的提交仍然有效。)

任何文件,该文件在你的工作树,现在,是不是在你的索引现在,是一个未被跟踪的文件。它的未跟踪性来自它不在您的索引中的事实。将该文件放入您的索引中并对其进行跟踪,无论您如何将其放入索引中。将它从您的索引中删除并且它不会被跟踪,无论您是如何从索引中取出它的。所以这是索引的最后一个角色:确定哪些文件被跟踪,因此将在下一次提交中。

现在,我们可以清楚地看到什么git ls-files

什么git ls-files确实是阅读一切:提交,索引工作树。根据你提供什么论据 git ls-files,它然后打印是在索引中的一些名字或所有文件和/或在工作树:

git ls-files --stage
Run Code Online (Sandbox Code Playgroud)

列出索引/暂存区中的文件,以及它们的暂存槽号。(它没有说明HEAD提交和工作树中的副本。)或者:

git ls-files --others
Run Code Online (Sandbox Code Playgroud)

列出工作树中但不在索引中的(名称)文件。(它没有说明HEAD提交中的副本。)或者:

git ls-files --modified
Run Code Online (Sandbox Code Playgroud)

列出索引中的文件(名称)并且HEAD提交中的副本不同(或根本不在HEAD提交中)。没有选择:

git ls-files
Run Code Online (Sandbox Code Playgroud)

列出索引中的(文件名)文件,不考虑HEAD提交或工作树中的文件。

  • @IvanRuski:是的,索引包含哈希名称和对内容的引用。但本地文件系统中的文件可能是一个名称和对内容的引用。那么你是否会说“我的目录中没有任何文件,它只有文件名”?:-) 这在技术上是正确的,但它并没有完成任何“工作”。有时了解这一点很有用,但大多数情况下,我们只是说我们的目录中包含文件。 (3认同)
  • 感谢您的出色回答。我已经分别了解了这些主题,但您的帖子确实将它们联系在一起! (2认同)
  • @grenix:`git ls-files` 将显示 Git 索引中的文件(如果有)。如果您运行“git clone -n​​”(不签出),索引将为空,因此不会显示任何内容。否则,它们将是 Git 在签出期间推入其索引的文件,这将是出现在工作树中的同一组文件,是的。请注意,您可以在签出后删除部分或全部工作树文件,而不会影响索引副本。Git 会有点抱怨,但您仍然可以进行包含所有文件的新提交! (2认同)