git - `git add` 如何处理文件<->目录之类的变化?
问题描述
这是一个很长的问题。我正在尝试对一些基本的 Git 功能进行逆向工程,并且在理解git add
引擎盖下的真正功能时遇到了一些麻烦。我已经熟悉 Git 的三棵树,并且索引文件并不是真正的树,而是树的排序数组表示。
我最初的假设如下:什么时候git add <pathspec>
运行,
- 如果
<pathspec>
工作目录中存在:- 从反映工作目录状态的索引文件创建一个索引文件
- 用这个(子)索引覆盖索引文件的相关部分。
- 如果
<pathspec>
仅存在于当前索引文件中:- 这意味着已在工作目录中删除,所以...
- 删除索引文件中与 .
- 如果
<pathspec>
工作目录或索引文件中不存在:fatal: pathspec <...> did not match any files
这个假设反映了“做你被告知要做的事情” git add
,它只查看路径并将该路径处或该路径下的更改注册到索引文件。在大多数情况下,这就是实际git add
的工作方式。
但是有些情况看起来并不简单:
1.用目录替换文件
git init
touch somefile
git add . && git commit
rm somefile
mkdir somefile && touch somefile/file
此时somefile
,正如预期的那样,索引文件仅包含我刚刚删除的文件的单个条目。现在我执行git add
. 我有两种方法可以做到这一点:git add somefile
或git add somefile/file
. (显然我在git add .
这里排除了琐碎的事情)
我所期望的:
git add somefile
: 相当于git add .
- 删除旧条目并添加新条目git add somefile/file
:只为新添加一个索引条目somefile/file
。
实际发生的情况:上述命令中的任何一个都直接导致具有单个索引条目的最终状态somefile/file
- 即,两者都等效于git add .
.
在这里,感觉git add
不是您直接的“按要求执行”命令。git add somefile/file
似乎在提供的路径中和周围窥视,意识到somefile
不再存在并自动删除索引条目。
2.用文件替换目录
git init
mkdir somefile && touch somefile/file
git add . && git commit
rm -r somefile && touch somefile
此时,索引文件包含一个旧条目,somefile/file
正如预期的那样。同样,我执行git add
相同的两个变体。
我所期望的:
git add somefile/file
:通常,删除旧的条目somefile/file
。但如果它四处看看,它还应该为somefile
.git add somefile
: 相当于git add .
。
实际发生的情况:
git add somefile/file
: 导致一个空的索引文件 - 所以,它做了我通常期望它做的事情!git add somefile
: 相当于git add .
, 正如预期的那样
在这里,git add
表现为“做你被告知要做的事情”命令。它只选择路径并用工作目录反映的内容覆盖索引文件的适当部分。git add somefile/file
不会四处寻找,因此不会自动为somefile
.
3. 索引文件不一致
到目前为止,一个可能的理论可能是git add
试图避免不一致的索引文件的情况 - 即,不代表有效工作树的索引文件。但是额外的一层嵌套导致了这一点。
git init
touch file1
git add . && git commit
rm file1 && mkdir file1 && mkdir file1/subdir
touch file1/subdir/something
git add file1/subdir/something
这与案例 1 类似,只是这里的目录多了一层嵌套。此时,索引文件只包含一个旧条目,file1
如预期的那样。同样,现在我们运行git add
但具有三个变体git add file1
:git add file1/subdir
和git add file1/subdir/something
。
我所期望的:
git add file1
: 等价于git add .
, 导致 . 的单个索引条目file1/subdir/something
。git add file1/subdir
andgit add file1/subdir/something
:通常,应该只添加一个条目file1/subdir/something
(导致不一致的索引文件)。但是如果上面的“no-inconsistent-index”理论是正确的,这些也应该删除旧的file1
索引条目,因此相当于git add .
.
实际发生的情况:
git add file1
: 按预期工作,相当于git add .
.git add file1/subdir
andgit add file1/subdir/something
: 只添加一个条目file1/subdir/something
,导致索引文件不一致,无法提交。
我指的不一致的索引文件是:
100644 <object addr> 0 file1
100644 <object addr> 0 file1/subdir/something
因此,只需添加另一层嵌套似乎就不会git add
像在案例 1 中那样偷看!请注意,提供给的路径git add
也无关紧要 - 两者都会file1/subdir
导致file1/subdir/something
索引文件不一致。
上述案例描绘了一个非常复杂的git add
. 我在这里遗漏了什么,还是git add
真的不像看起来那么简单?
解决方案
实际上,这只是意味着您在(至少某些版本的)Git 中发现了一个错误。
Git 理解操作系统不能支持两个实体,一个是文件,另一个是目录/文件夹,具有相同的名称。也就是说,我们不能file1
既是文件 又 file1
是目录。1
现在,关于 Git 的索引的问题是它根本无法在其中保存目录。2 唯一允许的实体是文件。所以要么file1
存在,要么file1/subdir/something
存在,但绝不会两者兼而有之。Git 里面有一堆相当复杂的代码,用于索引本身和在git checkout
、git reset
等期间处理操作系统级别的文件,这些代码应该处理“D/F”(目录/文件)冲突。Git 需要能够在执行文件git checkout
的提交时处理这些问题somefile
,然后执行文件git checkout
所在的不同提交,somefile/file
因此somefile
必须删除文件并插入目录。它需要能够处理我们回到第一种情况的结帐,以便somefile/file
必须删除,然后somefile/
必须是 rmdir-ed,然后somefile
才能创建为文件。并且,它必须处理somefile
三个提交中的一个或两个中的文件但somefile/file
存在于其他两个或一个提交中的合并。
显然,有人错过了一个角落案例。我能够自己重现这个,使用你的步骤,并且:
$ git ls-files --stage
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 file1
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 file1/subdir/something
$ git write-tree
You have both file1 and file1/subdir/something
fatal: git-write-tree: error building trees
这种状态不应该存在。添加file1
-as-a-directory会擦除包含以下内容的索引槽file1
:
$ git add file1
$ git ls-files --stage
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 file1/subdir/something
因为这会触发删除现在不受欢迎的条目的代码。
(很明显,这需要修复和测试套件测试用例。幸运的是,Git 在树构建过程中自我检测到错误用例,因此它不会做出错误的提交。)
1我认为也许我们应该能够做到这一点,但目前 POSIX 规则禁止这样做,并且没有任何类 Unix 文件系统支持它。它也会使归档器变得一团糟tar
。
2这并不完全正确:出于各种加速目的,索引包含“不规则”(非缓存)条目以及描述建议的下一次提交的正常缓存条目。它是不存在目录的缓存条目;非要提交的条目可以包含各种辅助信息。但这些都没有显示git ls-files
。
推荐阅读
- spring - 如何使用 Spring RestController 生成和使用 RabbitMQ 消息并将其发送回用户
- html - 光滑的轮播(下一个/上一个按钮放置)
- oracle - 带有自定义事件“beforeunload”的 Oracle Apex 4.2 动态操作不起作用
- c# - 使用表单授权模拟用户
- attributes - 使用属性和数量在购物车上添加费用
- javascript - 按状态转换数据数组
- swift - 迅速正确计算时间占一天的比例
- html - iFrame 使用来自主机主体的背景图像
- obfuscation - 有哪些好的/便宜的数据混淆和数据屏蔽工具?
- angular8 - 在 Angular 8 中设置启动页面/组件但出现错误“Angular CLI 进程未开始侦听请求”