一旦你在一个目录上运行 git init ,git 就会知道该目录及目录下存在的所有文件。这些是未跟踪的文件。我非常熟悉当您暂存这些文件并且它们成为“跟踪”文件时会发生什么。但我想知道的是,Git 是如何找到未跟踪的文件的?git 是否每隔几分钟或固定间隔运行一次树命令?每次输入 git status 或 git add 时它都会运行树命令吗?或者每次保存文件时?有什么更聪明的事情发生吗?
简而言之,我的问题是:
Git 如何找到未跟踪的文件?
Git 多久搜索一次“未跟踪”文件?
五年前或者更久以前,这个问题曾经有一个简单易行的答案。现在,它......更加复杂。然而,今天的行为应该与过去的行为相匹配,因此,如果您不关心实际的实现\xe2\x80\x94,并且您不应该关心\ xe2\x80\x94,我们可以描述旧的实现。
\n\n\n一旦你在一个目录上运行 git init ,git 就会知道该目录及目录下存在的所有文件。
\n
这实际上不是真的:git init将创建.git目录/文件夹并填充它,但此时 Git 尚未查看工作树,除了查看是否存在.git(以便git init在现有的存储库“重新初始化”它,而不是创建一个新的存储库\xe2\x80\x94,重新初始化步骤通常绝对不执行任何操作,尽管从技术上讲,它或多或少被定义为复制模板挂钩)。
然而,工作树中的任何现有文件现在确实都是未跟踪的文件:
\n\n\n这些是未跟踪的文件。我非常熟悉当您暂存这些文件并且它们成为“跟踪”文件时会发生什么。但我想知道的是,Git 是如何找到未跟踪的文件的?
\n
从逻辑上讲,Git 只需通过工作树的顶层,费力地一次读取一个条目即可完成此操作:
\nDIR *dirp = opendir(worktree);\nRun Code Online (Sandbox Code Playgroud)\n在 C 代码中,后面是一个循环调用readdir.
返回的每个条目都有多个项目。如果我们假设保存指针的变量struct dirent *名为named dp,那么我们至少有两个或三个字段:
dp->d_name\ndp->d_namlen\ndp->d_type\nRun Code Online (Sandbox Code Playgroud)\n该d_type字段可以包含DT_UNKNOWN,表示系统未提供文件类型,或DT_REG或DT_DIR或DT_LNK作为三种可能性,表示“常规文件”、“目录”(文件夹)或“符号链接”。(在类 Unix 系统上也可以使用其他值,但 Git 不会存储此类条目。)
如果类型是DT_UNKNOWN,Git 将需要调用lstat文件名,该文件名是通过将原始worktree路径与斜杠和 C 字符串组合起来构建的dp->d_name(尽管也有一个字段,但它以 NUL 结尾d_namlen)。该调用应该成功,并返回来自任何stat 系统调用的lstat所有常用信息。请注意,调用非常昂贵(通常比打开和读取目录要昂贵得多),因此我们希望尽可能避免它们,并且一旦完成,我们希望尽可能长时间地保留该信息。所以 Git 倾向于这样做;请参阅下面的更多内容。lstat
Git 现在可以查看.gitignore(或通过读取构建的数据结构)以查看是否应跳过.gitignore目录(DT_DIR或)。S_ISDIR()如果是这样,则所有递归都会短路。如果不是,对于每个目录,Git 必须递归地完成所有这些相同的工作,但请参见下文。
对于不是目录且可以存储在存储库中的内容,Git 现在可以测试该条目是否已在 Git 的索引\xe2\x80\x94 中,请注意,目录条目不会存储为该条目的索引条目排序,但索引具有可以存储它们的“扩展名”;我们将在下面\xe2\x80\x94 中看到更多相关信息。如果此过程找到的文件在Git 的索引中,则它是跟踪文件。如果此过程找到的文件不在Git 的索引中,则它是未跟踪的文件。这几乎就是跟踪与非跟踪的全部内容。请注意,被跟踪的文件(其信息现在存储在 Git 的索引中)也已lstat转录其数据(如果我们已经lstat对其进行了转录)。
\n\ngit 是否每隔几分钟或固定间隔运行一次树命令?
\n
(通过“树命令”,我假设您指的是我刚才描述的逻辑递归读取树操作。)
\n两者都不。它根据需要扫描顶级工作树。
\n\n\n每次输入 git status 或 git add 时它都会运行树命令吗?
\n
对于git status,是的。对于git add,仅当您使用了“全部”选项之一,或者要求git add添加一个目录实体时,在这种情况下,它必须对您命名的实体递归地执行读取树操作(前提是\未在 a .gitignore) 中列出。
\n\n有什么更聪明的事情发生吗?
\n
是的。上面描述的是逻辑,但实际实现时尽量使用捷径。另外,现在有一个叫做“fsmonitor daemon”的东西。
\n也许最重要的快捷方式取决于一些在类 Unix 文件系统上运行良好的技巧,但可能不适用于其他系统。这是每个目录都有修改时间的事实。如果您对目录执行或系统调用,则此修改时间显示为a 的st_ctime1字段。我们还知道上次写入 Git 索引的时间,因为我们将其存储在索引的字段中。struct statlstatstat
假设我们已经知道其中path/to/dir 有八个未跟踪的文件,并且知道它们都被忽略了。进一步假设我们在 \xe2\x80\x94somewhere\xe2\x80\x94 中保留一个用于path/to/dir存储其st_ctime字段的实体的条目。假设我们stat现在可以看到它的 ctime 没有改变并且早于 Git 索引的更新。然后我们可以确定它没有获取任何新的未跟踪文件,因此我们可以重新使用旧信息。
Git 还使用同样的技巧来避免为所有跟踪的文件重建 blob 哈希 ID,这更为重要。这比未跟踪的缓存更容易,因此自 2005 年早期的 Git 以来,跟踪的文件(在正常索引条目中列出的文件)就已经应用了这种魔法。
\n未跟踪的缓存代码早在 2015 年就进入了 Git 2.5,在很长一段时间内它都有点不稳定,但现在已经相当可靠了。不幸的是,它所依赖的假设在 NTFS 上并不成立(因此它通常在 Windows 系统上被禁用)。
\nGit 在 2017 年获得了名为fsmonitor的东西,作为 Git 2.16 的一部分,但它也很不稳定。最近,人们在它上面做了很多工作,以使其不那么不稳定,并使其能够在 Windows 以外的系统上运行:具体来说,它现在有了 macOS 和 Linux 实现。fsmonitor 代码与上面的代码非常不同:它侦听操作系统报告的文件系统操作,这些操作可能发生在甚至不在工作树中的目录中,并且在某些系统上,某些事件可能会被删除如果事件发生队列已满。
\nfsmonitor 的工作基本上是加快更新 Git 索引的速度。细节仍在变化,因此我不会尝试进一步描述它。
\n有关 Git 索引中内容的技术信息,请参阅Git 的 Git 存储库中的文件Documentation/technical/index-format.txt。
1您会期望这是st_mtime,并且确实如此,但st_mtime可以通过或类似的系统调用有意设置utimes。然而,还有第二个字段 ,st_ctime它存储一个通常不递减的值,不能以这种方式设置回来。所以我们使用ctimeinode-change-time,而不是mtime修改时间。备份软件通常使用相同的技巧进行增量备份。
| 归档时间: |
|
| 查看次数: |
128 次 |
| 最近记录: |