当前位置: 首页 > 知识库问答 >
问题:

git是如何检测文件变化的?

汝开畅
2024-01-02

具体而言, 在执行git status的时候, git到底做了什么?

目前在网上找到的疑似正确答案: git会调用系统函数lstat来读取文件属性, 从而判断文件的大小和修改时间是否有所改变; 于是, 假设某个文件修改后的大小正好和修改前一致, 然后在保存时又强制使用了和之前相同的修改时间来保存, 那么此时git是感知不到这一文件已经发生改变的。

经过个人测试, 发现确实如此, 所以该答案似乎是对的, 但随之产生一些疑惑, 不妨以一个具体的例子说明:
1, 对一个初始化的空库, 新建文档t1.txt, 执行git status, 此时git能将之识别为未追踪文件. 问: git调用lstat具体读取的是t1.txt的哪一些属性, 从而判断出来t1.txt是一个新文件? 个人猜测是检测创建时间是否等于修改时间, 不知是否正确, 但反正不可能是上述答案中的看文件大小和修改时间是否有变.
2, 将t1.txt暂存, 修改其内容后, 再次git status, 此时git能将之识别为已修改文件. 问: git调用lstat具体读取的是t1.txt的哪一些属性, 从而判断出来t1.txt是一个有新内容的旧文件? 按照上述答案, 读取的是其当前的文件大小与修改时间, 然后就要与它以往的文件大小与修改时间做对比, 看是否有改变. 但是问题来了, t1.txt之前的文件大小与修改时间现在已经被新值覆盖掉了, 那还怎么比对? 最直观的想法肯定是存进了版本里, 但发现起码对于修改时间, index和blob里都是没有的, 所以目前猜测是由操作系统负责保存的? 比如大小与修改时间等文件属性其实都存在一个列表里, 所以git才有历史数值可供比对当前数值是否改变.
3, 将t1.txt改名为t2.txt, 再次git status, 此时git能同时识别到t1.txt的删除与t2.txt的新增. 问: 对前者t1.txt的删除, 这应该是比对了index与工作区的结果, 但对于后者t2.txt的新增, git又是怎么调用lstat识别的? 这回是重命名而非新建文件, 所以它的文件大小与修改日期均是未改变的, 亦即其修改日期现在并不等于创建日期, 那git又是怎么识别它是一个新文件的?
4, 再将t2.txt暂存, 会发现相应blob文件的修改日期变了, 亦即改一次文件名就要重新把相同的内容备份完了覆盖一遍, 这个设计不合理吧, 尤其当文件特别巨大时, 不该是这样才对. 对此该怎么理解?


贴一下复现的测试结果: 文件内容从1改到2, git检测不到.
image.png

共有3个答案

伍宝
2024-01-02

问题

这个问题还是比较麻烦的,本质上应该是说 git 到底是只比较了时间和大小,还是比较了 SHA-1

这个问题,我在 unix、linux、windows,三个平台都做了测试,发现结果都不一样。

表现

unix 与 linux

其中 unix、linux 表现是相同的,那就是:只要修改了文件,mtime 被修改为刚提交时,git status 一定会检测到

macos 需要使用 gstat,用 stat 只能看到秒,安装: brew install coreutils

下面是 unix、linux 测试的代码,创建文件时,也使用该代码创建,保证文件前后的 mtime 相同:

#define _POSIX_C_SOURCE 200809L#include <stdio.h>#include <time.h>#include <fcntl.h>#include <sys/stat.h>#include <unistd.h>int main(int argc, char **argv) {    const char *filename = argv[1];    char *content = argv[2];    FILE *file = fopen(filename, "w");    fprintf(file, "%s", content);    fclose(file);    struct timespec times[2];    time_t sec = 1703983139;    times[0].tv_sec = sec;    times[0].tv_nsec = 431009920;    times[1].tv_sec = sec;    times[1].tv_nsec = 431009920;    if (utimensat(AT_FDCWD, filename, times, 0) < 0) {        perror("utimensat");        return 1;    }    return 0;}

windows

但 windows 的表现是不相同的,如果使用 notepad3 设置了 使用原始修改时间保存,不改变大小的情况下修改文件,那么 git status 就不会检测到。

结论

从表现上来看,unix 与 linux 应该都是使用了除 mtime 之外的手段,但是 git 什么时候去计算 SHA-1,用的手段是什么,这个暂时无法得知。

windows 上,则应该就是用的 mtime + 文件大小的方式了。

红经亘
2024-01-02

先看下git status执行过程:
在执行git status时,Git会比较工作区(Working Directory)和暂存区(Index)的状态,然后显示相应的结果。具体来说,Git会执行以下步骤:

1.检查工作区文件状态: Git会遍历工作区中的文件,检查它们的状态,包括是否被修改、是否是新文件或已删除的文件等。

2.比较工作区和暂存区: Git会比较工作区和暂存区中文件的差异,以确定哪些文件已被修改但尚未暂存。

3.比较暂存区和最后一次提交: Git还会比较暂存区和最后一次提交(HEAD指向的提交)的差异,以确定哪些修改已经暂存但尚未提交。

关于您提到的关于文件属性的疑问:

1.旧文件属性的存储: Git并没有存储旧文件的属性,而是在执行git status时,通过调用系统函数lstat来读取当前文件的属性,并与之前保存的状态进行比较。旧的文件属性并不在版本库中,而是在工作区和暂存区的比较中使用的。

2.对新文件的处理: 对于新文件,Git会将其视为未跟踪的文件,而不是与旧文件进行比较。新文件的检测是通过检查工作区中是否存在但未被添加到暂存区的文件来完成。创建时间通常不是Git关心的属性,而是文件系统的特定属性。Git主要关注文件内容和修改时间。

关于改名的情况,Git确实可能会识别为新文件。在Git中,文件改名被视为一系列的文件删除和新增操作,而不是直接的改名。这可能导致Git在执行git status时认为发生了文件的新增。 Git并不关心文件的创建时间,因此对于Git来说,文件改名主要是基于文件内容和修改时间的比较。

贺善
2024-01-02

初始化时,对象库中为空
图片
创建一个 t1.txt 文件,在第一行输入 123456进行 git add操作后在 46 目录下发现新增的 blob 对象,所谓的 SHA 算法也就是 Git 对象中的对象 ID,拆分成“2 位文件目录名+38 位对象名”用于快速查找 Git 中的文件
图片

图片
查看 t1.txt 在 Index 树下如何存储,在 Git 中可以看到其内容:git write-tree是使用当前索引 Index 创建树对象,同时会创建一个树目录,也就是 aa,我们可以忽略它
图片
如果将 t1.txt 文件内容修改,增加一行 123456 并操作 git add,查看当前对象库:
图片
发现创建了一个新的目录,再查看其内部文件:
图片
实际上 t1.txt 的内容修改,Git 会再创建一个新 blob 对象存放整个内容,而不是在原 blob 对象下增量存储
图片
修改 t1.txt 为 t2.txt,再次 git add,新增了 8c 目录和里面的文件实际上 Git 并没有在对象库中删除之前的 t1.txt,创建了一个新对象,新对象里面的内容指向原来的 t1.txt:
图片
最后再将 t2.txt 修改为 t1.txt 并操作 git add 查看 Index 文件修改时间,我们得知 Index 树变化了,但是其他文件目录没有发生变化:
图片
查看当前的 Index 索引树,发现最后结果还是指向了和上面相同的那个 t1.txt 文件:
图片

 类似资料:
  • 我们将创建一个简单的来显示一个电影的信息。 这个应用程序将只包含两个组件:MovieComponent显示有关电影的信息和MainComponent,它使用按钮来保存对电影的引用以执行一些动作。 我们的AppComponent组件将有三个属性:应用程序的slogan,电影的title(标题)和(主角)。 最后两个属性将被传递到模板中引用的MovieComponent元素。 在上面的代码片段中,我们

  • 问题内容: 我需要知道如何使用Go检测文件何时更改。我知道Unix提供了一个名为named的函数,该函数会在更改特定文件时通知您,但我在Go中找不到此函数。请帮我。 问题答案: 目前这里有一个实验包。它应该像go1.3中那样合并到核心中

  • 问题内容: 我使用的是Swift,我希望能够在旋转到风景时加载UIViewController,有人可以指出正确的方向吗? 我在网上找不到任何东西,并且对文档有些困惑。 问题答案: 这是我的工作方式: 在里面 我把函数: 然后在AppDelegate类中放入以下函数: 希望这对其他人有帮助! 谢谢!

  • Figure: Change Detector by Vovka is licensed under Public Domain () 变化检测在旧版本的Angular和新版本之间发生了很大的变化。在Angular 1中,框架保留了一长串观察者(每个属性绑定到我们的模板),需要在每次 digest 循环开始时检查。这被称为脏检查,它是唯一可用的变化检测机制。 在Angular 2中,信息流是单向的

  • 问题内容: 我想将两个图像添加到单个图像视图(即,用于横向显示一个图像,用于纵向显示另一个图像),但是我不知道如何使用快速语言检测方向变化。 我尝试了这个答案,但只拍了一张图片 我是iOS开发的新手,任何建议将不胜感激! 问题答案:

  • 我想在单个图像视图中添加两幅图像(即横向一幅图像和纵向另一幅图像),但我不知道如何使用swift语言检测方向变化。 我试过这个答案但它只需要一个图像 我是iOS开发新手,如有任何建议,将不胜感激!