你在这里有两个不同的问题。您的fatal: Could not read from remote repository
错误完全独立于您在索引中设置或清除的任何标志位。这意味着:
- 对于您用于访问另一个存储库(
http
、https
、ssh://
或git://
)的任何协议,与另一个 Git 的连接失败,或者
- 连接成功,但据称拥有另一个 Git 存储库的另一个主机说“我在这里看不到 Git 存储库”(在 URL 的其余部分)。
如果连接本身失败,您通常应该在fatal:
告诉您发生情况的行之前看到附加信息,例如,无法将主机名解析为 IP 地址、需要但缺少用户名和/或密码等。使用它来诊断不良连接。如果连接成功,但主机说“这里没有 Git 存储库”,请使用您可以获得的有关主机的任何信息来找出存储库的位置(例如,您可以直接登录主机并四处寻找吗?)。
现在,至于assume-unchanged
和skip-worktree
位,恐怕这有点复杂和技术性。您需要了解其中一些内容才能使用 Git,因此值得一试。我在这里还注意到,使用的建议--really-refresh
基本上是错误的。出于更新索引中缓存的统计数据的目的,这确实会暂时忽略该assume-unchanged
位,但对实际假设不变的位没有影响。清除该位的方法确实是使用--no-assume-unchanged
,就像您上面引用的花哨的管道一样,以 . 结尾| xargs -0 git update-index --no-assume-unchanged
。跳过工作树位也是如此,除了您使用--no-skip-worktree
. 1
索引是什么,这些git update-index
命令在做什么?
重要的是要意识到,在使用 Git 时,您始终拥有三个我喜欢称之为活动副本的每个文件。2这三个副本之一是您签出 的任何提交中的任何内容。此副本是严格只读的。你不能改变它:它被冻结成一个提交,并且会留在那个提交中,只要那个提交本身存在——本质上,永远存在。3 这意味着冻结的副本是安全的:您所做的任何事情都不会破坏它;你总能把它拿回来。 要查看名为 的文件的冻结副本path/to/file.ext
,请使用git show HEAD:path/to/file.ext
. 此HEAD:file
语法适用于git show
并让您看到当前提交中的冻结副本。(您也可以在任何其他提交中看到冻结的副本。)
现在,这些冻结的副本是一种特殊的、只读的、仅限 Git 的压缩格式。您计算机上的任何其他程序都不能直接访问它们(除非它们对 Git 的内部了解得太多)。而且,由于它们无法更改,因此它们可以用于存档,但对于完成任何实际的新工作却毫无用处。因此,当您git checkout
进行一些特定的提交时,Git 会提取所有冻结的文件,将它们转回正常的日常文件,您可以像往常一样查看和使用这些文件。您可以更改这些普通的读/写文件,或者对它们做任何您想做的事情,包括添加新文件和删除现有文件,所有这些都以您对计算机执行任何操作的通常方式进行。
这些可用的、可操作的文件位于 Git 所称的工作树中。这就是你完成工作的地方。 这是每个文件的第二个副本。 您不必在这里做任何特别的事情:这些只是文件。如果您想查看名为 的文件file
,请使用您总是用来查看名为file
. 它的名字只是file
.
每个文件的第三个副本是 Git 偷偷摸摸的地方。这就是索引的用武之地。索引也称为暂存区,或者有时——现在很少见——缓存。这些都是同一事物的名称。 每个文件的第三个副本都在索引file
中,您可以查看名为using的文件的索引副本git show :file
。也就是说,git show
前面的冒号表示:显示索引中的副本。 您在花哨的管道中使用的git ls-files
命令还列出了索引中的内容。
该文件的第三个副本采用 Git 用于永久冻结文件存储的格式,但并未完全冻结。您可以随时覆盖它。file
您可以用您喜欢的任何新内容替换索引副本。您使用 执行此操作git add
,它获取工作树副本(假设您此时已更改该副本)并将索引副本替换为该版本。或者,如果您愿意,可以使用删除索引副本,这将同时删除索引副本和工作树副本。file
git rm
从技术上讲,索引中的内容只是关于文件的大量缓存数据,加上一堆标志,以及对文件存储的冻结格式副本的引用。当您第一次检查一些提交时,以便您的HEAD
和工作树副本匹配,您的索引副本实际上只是直接重新使用冻结的HEAD
副本,所以这根本不需要额外的空间。当您使用git add
覆盖它时,Git 会获取工作树副本,将其压缩为冻结的、可用于永久存储的副本,并将该副本放在某个位置4并更新索引引用。
这是git commit
使速度如此之快的秘密之一。Git 不必查看您的工作树。它不必重新压缩所有文件。它们已经在你的索引中,准备好了。所要做git commit
的就是将预冻结的文件打包成一个提交。它只是提交您运行时索引中的任何内容git commit
。因此,考虑这一点的一个好方法是index 是您建议的next commit。做什么git add
和git rm
做的是更新您提出的提交。只运行git commit
你的索引中的快照——在暂存区,准备提交——即使这与之前的提交基本相同。和命令是实际更新索引的内容git add
。git rm
这就是为什么以及如何每次提交都是每个文件的完整快照。您不更新的任何文件仍在索引中(“在舞台上”),并将在下一次提交中。
git status
使用标志
假设您在当前签出的提交中有 3000 个文件(因此在您的工作树中),并且您在工作树中更改了一个git add
文件,并在您的索引/暂存区域中更新它。如果你git status
现在运行,git status
不要费心告诉你 3000 个文件中有 2999 个是相同的,因为那不是有用的信息。git status
告诉您的是一个文件已更新。
git status
至少在原则上,这样做的方法是运行两个单独的比较:
当git status
进行这种比较时,由于 Git 将文件内容的内部表示为 blob 哈希 ID,所以第一部分进行得非常快。所以它真的,从字面上看,只是比较每个文件。只需要几毫秒就可以确定 3000 个文件中的 2999 个是相同的,一个是不同的。但第二部分很慢:实际上比较所有 3000 个文件可能需要几秒钟!
所以,git status
骗子。这就是索引的缓存方面发挥作用的地方。每个索引条目都包含对准备提交的冻结格式文件的引用;但它也包含一些由操作系统lstat
系统调用产生的数据。5 Git 可以lstat
对工作树中的文件进行另一个系统调用。在大多数情况下,当且仅当工作树中的文件仍然与Git 在冻结格式中的副本相同时,结果stat
数据与 Git 之前保存的数据匹配,正如索引条目所缓存的那样。如果您修改了工作树副本,操作系统也会更新数据。stat
因此,假设您是git status
,将索引中的每个文件与其在工作树中的副本进行比较,这样您就可以在必要时说不暂存提交。您可以打开每个工作树文件并一直阅读它,并将其内容与解压缩冻结索引副本时获得的内容进行比较。这会告诉你它们是相同还是不同,但是哇,这是很多工作,可能需要几秒钟。但是你有这个缓存stat
的数据,如果你将统计数据与另一个结果进行比较lstat
,那么这需要更少的工作和时间。所以你这样做。如果lstat
结果与缓存的结果匹配,则文件必须相同,您可以什么也不说,继续下一个文件。
但实际上,每个lstat
系统调用也很慢。当然,它比读取每个文件要快数千倍,但仍可能需要数百微秒。如果操作系统的速度非常慢lstat
,需要 3毫秒怎么办? 对 3000 个文件执行此操作,如果每个文件需要 3 毫秒,则需要 9秒,这太长了!
Git对此有一个标志。该--assume-unchanged
标志是每个索引条目中的一个可设置标志,它告诉 Git:不要费心调用lstat
这个工作树副本,只要假设它与缓存的 stat data 匹配。它有第二个稍微更强大的标志 ,--skip-worktree
可以达到相同的结果。(它稍微强大一些,因为某些命令,例如git update-index --really-refresh
,会忽略第一个标志,但不会忽略第二个。)
如果您设置任一位,则将索引的缓存stat
数据与工作树中的真实stat
数据进行比较的操作,以判断文件是否真的被修改,只需假设文件没有被修改。清除这两个位,这些 Git 操作stat
毕竟会调用。然后git status
应该看到文件的更新,只要stat
操作系统返回的数据也更新了。 有一些操作系统级别的技巧可以解决这个问题,但您通常可以使用以下方法击败这些操作系统级别的技巧touch
:
touch path/to/file
确保现在的stat
数据path/to/file
比stat
Git 可能持有的任何缓存数据都更新。
如果有点复杂,这张图片应该足够清晰:索引/暂存区域保存有关每个工作树文件的缓存数据,来自先前的系统调用。如果缓存的数据与操作系统在新调用中报告的内容相匹配,则索引副本必须与工作树副本相匹配。如果您设置了标志位,Git 不会费心进行调用:它只是假设两组数据匹配,因此索引副本与工作树副本匹配,无论这是否真的如此。清除这些位,Git 会返回调用并获得——我们希望——来自操作系统的准确报告。lstat
lstat
lstat
lstat
这张图不再完全正确,因为 Git 现在还可以使用文件系统监视器来避免lstat
不必要的调用。但这完全是另一个问题的话题。
1请注意,花哨的管道假定您已LC_COLLATE
设置为C
,在某些版本中grep
服从该LC_COLLATE
标志。那是:
git ls-files -v | grep '^[a-z]'
可能会列出每个文件,具体取决于LC_COLLATE
. 它还列出了--skip-worktree
文件,但您必须使用单独的git update-index --no-skip-worktree
命令取消设置该标志。这是我写的原因之一git-flagged
。(由于匹配太多而列出太多文件grep
是无害的:您只会调用一些git update-index
实际上不需要运行的命令。)
我没有让我的git-flagged
脚本支持新的 fsmonitor 有效/无效位。如果您的系统正在使用 fsmonitor,并且出现问题,那么您遇到了更大的问题,也许应该全局禁用 fsmonitor,通过git config
和core.fsmonitor
设置。
2这假定一个普通(非--bare
)存储库,并且您没有使用git worktree add
. 您添加的每个工作树git worktree add
都有自己的索引和工作树以及自己的HEAD
,因此每个工作树都会获得另外三个这些活动副本。
3一旦您进行了提交并且它获得了特定的哈希 ID,您可以使用该哈希 ID 来查看提交是否仍然存在。如果它确实存在——而且很可能存在——那么你冻结到其中的文件也仍然以冻结的形式存在。
真正摆脱一个糟糕的提交有点困难。这是可以做到的,所以提交并不一定是永远的,但这是思考它们的一种方式。
4 “某处”实际上是直接进入存储库。如果您提交此文件副本,则使用冻结的副本;如果不是,那么 Git最终会清理掉的通常只是剩余的垃圾。除非您的磁盘空间一直非常短缺,否则您无需担心这些git fsck
会显示为dangling blobs的东西。稍后让 Git 自行清理它们。
5这特指生成数据的 POSIXlstat
系统调用。stat
如果你的底层操作系统没有或不使用stat
数据,Git 仍然需要缓存一些东西,并且会使用某种合成stat
数据,这些数据需要足够好才能完成剩下的工作。