"git ls-files" 是什么作用,它究竟如何从中删除文件?

23

它会显示来自本地仓库、暂存仓库、远程仓库还是其他地方的文件?

我一直看到一个文件,它在git ls-files中存在。 该文件已从远程仓库中删除。 之后,我尝试执行git pull。 然而,该文件仍然出现在这个命令列表中。 它不应该出现在这里,因为它在远程仓库中也不存在。


你的 git status 显示了什么? - Nghia Bui
@NghiaBui 它显示文件 X 以红色被删除(表示它正在被跟踪)。X 是不应该出现在 "git ls-files" 中的文件。这个文件在远程仓库中不存在。我尝试执行 git pull + git fetch + git reset --hard origin/branch_name,但这些都没有解决问题。 - Mugen
git help ls-files says
git-ls-files - Show information about files in the index and the working tree
- jthill
@jthill 是的。我已经阅读了这篇文章,但仍不清楚他们是在谈论本地存储库、暂存存储库还是本地文件列表。同时也不清楚如何删除显示在此列表中但既不在本地也不在远程存储库中的文件。 - Mugen
5个回答

35

摘要

你需要理解Git存储每个文件的至少三个活动副本,有时多达五个:一个在当前提交中,一个(或两个或三个!)在索引中,以及一个——你唯一可以看到和使用的——在你的工作树中。 git ls-files命令查看这些副本,然后根据您提供给git ls-files的标志告诉您其中一些内容。

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

长篇大论

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

git ls-files是否显示本地版本库中的文件?

有点类似,但:

暂存区,

Git没有暂存。每个版本库都有一些在不同的Git文档中称为索引暂存区的东西。 (在Git词汇表中还有一个已过时的第三个名称,缓存。)

远程版本库。

绝对不需要任何远程仓库,即其他具有自己仓库的Git,如果有的话,只有git fetchgit push会让您的Git调用他们的Git并与他们交换数据。 (好吧,git ls-remote执行git fetch的第一小部分,git pull 运行git fetch,因此这两个也会与远程交换数据。 但是,git ls-files不会。)

或者来自其他地方?

是的,有点像。这让我们回到了第一部分。因此,让我们将这三个术语片段视为Git词汇表中定义的内容。以下斜体(包括粗斜体)文本直接来自链接的文档:
  • 仓库(repository)

    一组引用(refs)以及一个包含所有从refs引用可达到的对象对象数据库,可能还伴随着来自一个或多个porcelain的元数据。仓库可以通过alternates机制与其他仓库共享对象数据库。

    当然,这里充满了更多术语。为了尝试将其解释清楚一些,他们在这里表明,仓库本身不包括索引和工作树:它大多由提交(及其内容)组成。当然,这需要我们定义“索引”和“工作树”,因此让我们继续阅读:

  • 索引(index)

    一个带有stat信息的文件集合,其内容存储为对象。索引是您的working tree的存储版本。老实说,它还可以包含第二个甚至第三个工作树版本,这些版本在merging时使用。

  • 工作树(working tree) (我通常称之为work-tree):

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

提交记录永久冻结

当您运行git commit时,Git会对所有文件进行快照 - 好吧,至少是所有跟踪的文件 - 并将其存储在一个提交中,同时还包括一些元数据,如您的姓名和电子邮件地址。这个提交基本上是永久性的 - 您可以通过费力地删除它们来摆脱提交,但出于方便起见,请将它们视为永久性的 - 并且完全、彻底、100%只读。这是有意为之的只读模式,因为这允许其他提交共享相同的文件副本,因此,如果您提交同一个文件一次、十次或甚至一百万次,仓库中实际上只有一个该文件的副本。只有当您将文件更改为新版本时,Git才必须提交一个新的、单独的副本。

提交记录是编号的,但不是按照漂亮简单的连续编号系统。也就是说,我们可能会将它们绘制成一系列简单的编号或字母形式:

... <-C4 <-C5 <-C6 ...

每个提交都指向其直接前任。但它们的实际名称是丑陋的大哈希 ID。每个哈希 ID 都保证是唯一的,这就是为什么它们必须如此庞大、丑陋和随机外观的原因。每个哈希 ID 实际上是在提交内容上计算出的加密校验和,以便宇宙中的每个 Git 都将同意那个提交,而且只有那个提交,得到那个校验和。这是你甚至 Git 不能更改它的另一个原因:如果你从仓库数据库中取出一个提交,修改它并改变了一个单一位,然后将其放回数据库,你得到的是一个具有新的和不同的哈希 ID 的新提交。
所以提交是完全冻结的,永远不会改变。其中的文件也被永久冻结,并且以特殊的 Git-only 格式进行压缩。我喜欢称这些文件为“freeze-dried”。这意味着,它们非常适合存档,但对于完成任何新工作来说毫无用处...这意味着 Git 必须提供某种方式将这些 freeze-dried 文件转化为有用的形式。
工作目录提供有用的副本。
事情没有比这更简单的了:工作树具有实用形式,是您文件的重新生成副本。因为它们只是您计算机上的普通日常文件,所以您可以查看它们,使用它们,随意更改它们,并以其他方式处理它们。从技术上讲,它们根本不在存储库中——它们更像是就在其旁边。在典型的设置中,存储库本身位于工作树顶层的.git目录/文件夹中。
显然,如果有一个提交被提取到工作树中,现在必须有每个文件的两个副本:冷冻干燥的提交副本加上常规工作副本。Git可以在这里停止。Mercurial会在这里停止:如果您使用Mercurial而不是Git,则无需关心第三个副本,因为没有第三个副本。但是Git继续存储文件的更多副本。
索引/暂存区位于提交和工作树之间。
Git在这里所做的是在冻结的提交拷贝和工作树拷贝之间插入第三个文件的副本。这第三个副本是以提交文件格式(即预脱水)的,但由于不在提交中,它实际上并没有完全冻结:它可以随时被替换。这就是git add所做的事情:git add获取工作树中的普通文件副本,将其压缩成冻结格式,并替换索引中的副本。或者,如果文件根本不在索引中,则将其放入索引中。
这就是为什么你必须经常使用git add的原因。在Mercurial中,你只需要一次hg add一个文件。之后,你只需运行hg commit,Mercurial会查看所有它知道的文件,并将它们冻结到一个新的提交中。在大型代码库中,这可能需要很长时间。相比之下,Git已经拥有了它应该知道的所有文件,并且已经在索引中进行了脱水处理,因此git commit可以将这些脱水文件打包成一个新的冻结提交。这种速度的代价是git add,但如果你开始玩弄索引副本的聪明技巧——例如使用git add -p——你会得到比仅仅加速更多的好处。
正如Git词汇表在索引描述中提到的,索引在冲突合并期间扮演了一个更加重要的角色。当您执行合并操作时,无论是从git mergegit revertgit cherry-pick还是任何其他使用合并引擎的Git命令,如果操作不顺利,Git会将每个文件的所有三个输入放入索引中,这样您就可以获得三个file.ext的副本,而非只有一个。但只要您不处于合并过程中,索引中就只有一个副本。
通常,索引副本与HEAD冻结副本或工作树副本匹配,或两者都匹配。例如,在新的git checkout之后,所有三个副本都匹配。然后您在工作树中修改了file.ext:现在提交和索引匹配,但它们与工作树副本不同。然后您执行git add file.ext,现在索引和工作树匹配,但它们与冻结副本不同。然后您git commit以创建一个新的提交,该提交成为当前提交,所有三个副本再次匹配。
请注意,您可以修改工作树副本:
vim file.ext

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

git add file.ext

然后再次编辑它:

vim file.ext

那样,你就可以使这三个副本都不同。如果这样做,git status会显示你已经准备提交更改,因为索引副本与当前提交的副本不同,并且显示你有未准备提交的更改,因为工作树副本与索引副本不同。
工作树中可能包含索引中没有的文件。
索引最初只是当前提交的副本。然后Git还将这些文件复制到工作树中,以便您可以使用它们。但是,您可以在工作树中创建文件并且不对其运行git add。现在这些文件不在索引中,如果您运行git commit,则它们也不会出现在新提交中,因为Git从索引中构建新提交。
您还可以从索引中删除文件,而不从工作树中删除它们:
git rm --cached file.ext

删除索引副本。当然,它无法触及当前提交的冻结副本,但是如果现在创建一个新的提交,则新的提交将根本不包含file.ext。(先前的提交仍然包含,当然。)

任何当前位于工作树中且当前未在索引中的文件都是未跟踪的文件。它的未跟踪性源于它不在您的索引中。将该文件放入索引中,它就被跟踪了,无论您如何将其放入索引中。从索引中删除它,它就是未跟踪的,无论您如何将其从索引中移出。因此,这是索引的最后一个角色:确定哪些文件是已跟踪的,并因此将在下一次提交中。

现在我们可以清楚地看到git ls-files的作用

git ls-files的作用是读取所有内容:提交、索引和工作树。根据您提供给git ls-files的参数,它会打印出索引和/或工作树中某些或所有文件的名称:

git ls-files --stage

列出索引/暂存区中的文件以及它们的暂存槽编号。 (它不涉及HEAD提交和工作树中的副本。)或:

git ls-files --others

列出工作区中存在但不在索引中的文件名。这并不涉及HEAD提交中的副本。

git ls-files --modified

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

git ls-files

列出索引中的文件名称,不考虑HEAD提交或工作树中有哪些文件。


1
我不确定index是否保存了文件的副本,相反,index保存了“名称(40个字符长的sha1)”,该名称可以在.git/objects文件夹中找到。100644 802992c4220de19a90767f3000a79a31b98d0df7 0 README.md上面这行是从index中提取出来的,而不是文件README.md的副本。这只是工作树中文件的名称,哈希值是keygit用它来查找objects文件夹中的blob - Ivan Ruski
3
@IvanRuski:是的,索引包含哈希名称和内容的引用。但是,您本地文件系统中的文件可能只是名称和内容的引用。那么,您是否会说“我的目录里没有任何文件,它只有文件名”? :-) 这在技术上是正确的 - 但它并不能完成任何工作。有时了解这一点很有用,但大多数情况下,我们只是说我们的目录中有文件。 - torek
2
非常感谢您提供这个出色的答案。我之前对这些主题都有所了解,但是您的帖子真正将它们联系在一起了! - Xunnamius
2
@grenix:git ls-files 会显示 Git 索引中的文件(如果有的话)。如果你运行了 git clone -n(不检出),那么索引将为空,因此这将不显示任何内容。否则,它们将是 Git 在检出期间推入其索引中的文件,这些文件将是与您的工作树中出现的文件集相同的文件集。请注意,在检出之后,您可以删除一些或所有这些工作树文件,而不会影响索引副本。Git 会抱怨一下,但您仍然可以创建包含所有文件的新提交! - torek
1
@grenix:是的,不过记得检查错误情况(HEAD可能是对不存在分支名称的符号引用)。通过HEAD读取提交哈希ID,然后检查相应的树对象。从shell中,您可以使用git ls-tree -r HEAD来执行此操作。Git可以创建符号链接,但通常不会创建目录;空目录情况仅适用于gitlinks(子模块部分)。 - torek
显示剩余4条评论

2

我想分享一下:

参考已接受的答案https://dev59.com/N1MI5IYBdhLWcg3wcK_K#56242906以及与https://stackoverflow.com/users/1256452/torek的讨论:

如果问题是,如果我检出了一个特殊的提交,如何找出哪些文件/对象应该在那里,另一个答案可能是:

git ls-tree -r -l HEAD

Torek也提到了“(HEAD可能是指向不存在的分支名称的符号引用)”,但我现在还不理解。
更一般地说:
git ls-tree -r -l commit-hash

这也适用于使用switch -n(无检出)克隆的存储库。
只是想知道输出的奇迹在哪里记录。
从克隆命令中提取存储库:git clone -n https://github.com/nvie/gitflow.git
100755 blob fd16d5168d671b8f9a8a8a6a140d3f7b5dacdccd    git-flow
100644 blob 55198ad82cbfe7249951aa75f1373a476997d33a    git-flow-feature
100644 blob ba485f6fe4b7d9c35bc01d2a6bd4ae201bccc9bd    git-flow-hotfix
100644 blob 5b4e7e807423279d5983c28b16307e40dfdb51d7    git-flow-init
100644 blob cb95bd486deb7089939362705d78b2197893f578    git-flow-release
100644 blob cdbfc717c0f1eb9e653a4d10d7c4df261ed40eab    git-flow-support
100644 blob 8c314996c0ac31f1396c48af5c6511124002dab7    git-flow-version
100644 blob 33274053347f4eec2f27dd8bceca967b89ae02d5    gitflow-common
120000 blob 7b736c183c7f6400b20ea613183d74a55ead78b5    gitflow-shFlags
160000 commit 2fb06af13de884e9680f14a00c82e52a67c867f1  shFlags

我的理解:

哈希似乎是“blob校验和”(不是提交哈希)。如果一个提交中有多个文件,则相同的校验和可能会出现多次。例如,100644的最后三个尼布尔看起来像是Linux文件访问属性(rw-r--r--)。如果对象不是常规文件,则前三个尼布尔不是100。在实际生活中,gitflow-shFlags是一个符号链接,shflags是一个子模块目录。

编辑: 刚刚偶然发现https://github.com/git/git/blob/master/Documentation/technical/index-format.txt(GOOGLE:git --index-info,STACKOVERFLOW:git索引到底包含什么?

32-bit mode, split into (high to low bits)

  4-bit object type
  valid values in binary are 1000 (regular file), 1010 (symbolic link)
  and 1110 (gitlink)

  3-bit unused

  9-bit unix permission. Only 0755 and 0644 are valid for regular files.
  Symbolic links and gitlinks have value 0 in this field.

如果您将这些字节解释为八进制值,那么:
100644: 1'000' 000'110'100'100 --> 对象类型是常规文件
120000: 1'010' 000'000'000'000 --> 对象类型是符号链接
160000: 1'110' 000'000'000'000 --> 对象类型是gitlink
天啊:为什么直接从git手册中提取这样的信息如此困难?
下一个问题:'gitlink'是什么?它只与git子模块相关联吗?

1
模式是一个谜,除非你注意到它们是从Linux/Unix的“inode”模式派生而来。gitlink模式是特殊的,确实用于子模块。 - torek

2
你的情况下git ls-files工作正常。由于你的git status显示X文件已从工作目录中删除,这意味着该文件仍存在于索引中。这就是为什么git ls-files会显示X,因为该命令显示了索引内容。

现在,你需要从索引中删除该文件,只需运行:

git rm --cached <pathToXFile>

我已经在本地删除了文件并尝试将更改推送到git。 git add(已删除的文件)。 git commit -m(关于删除此文件的一些消息)。 git push(由于服务器问题而失败)。 现在要回溯,我必须使用“git reset HEAD ^ --soft(保存更改,返回上一个提交)(https://dev59.com/Omkw5IYBdhLWcg3wDWTL#10169389)。在此添加此评论以帮助任何其他可能陷入困境的人。 - Mugen

1
在 Git 2.35 (2022 年第一季度) 中,"git ls-files" 学会了 "--sparse" 选项,以帮助调试。
它与 稀疏索引,在执行 git sparse checkout 命令后 一起使用。
查看 提交 408c51f提交 c2a2940提交 3a9a6ac提交 7808709提交 5a4e054(于2021年12月22日由Derrick Stolee (derrickstolee)提交)。
(由Junio C Hamano -- gitster --合并于提交 3c0e417,2022年1月10日)

ls-files: 添加 --sparse 选项

签名作者:Derrick Stolee

现有的 'git ls-files(man)' 调用者期望的是文件名,而不是目录。在这种情况下,最好展开稀疏索引以显示其中包含的所有文件。

然而,专家用户可能希望检查索引本身的内容,包括哪些目录是稀疏的。
添加 --sparse 选项以允许用户请求此信息。

在测试过程中,我注意到像 --modified 这样的选项并不影响那些在稀疏检出定义之外的文件的输出。

git ls-files现在在其手册页面中包括以下内容:

--sparse

如果索引是稀疏的,则显示稀疏目录而不扩展到包含的文件。
稀疏目录将显示为带有尾随斜杠的形式,例如对于稀疏目录"x",将显示为"x/"。


0
我经常看到一个在“git ls-files”中存在的文件。该文件已从远程存储库中删除。之后,我尝试进行了git pull。
如果您不想将其添加到索引中,请将其删除。通常使用git rm --cached或者如果您还希望它从工作树中消失,只需使用git rm
在您工作时,通常会发现一些需要修复但并不是当前任务的愚蠢小错误。Git使处理这类事情非常容易:从维护基础上检出一个bugfix分支,仅提交该修复,然后返回到您正在做的事情并合并该修复。
如果可能(而且通常如此微不足道,Git会默默地完成),Git会在最不干扰您正在进行的其他更改的情况下完成此操作。

你会发现在其他情况下,Git处理正在进行的工作的方式避免了无用的繁琐操作。重要的是,这就是Git处理正在进行的工作的方式:它会一直停留在索引中,直到你决定如何处理它。只要你不告诉Git放置其他东西,Git就会默默地携带你添加的内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接