Git 不跟踪文件历史记录。
Git 确实跟踪内容,并保留提交历史记录。
这意味着,如果您有一些现有提交,然后您移动文件并使用现有提交作为其历史记录进行新提交,则新提交将旧提交作为其提交历史记录。
移动的文件没有历史记录,但旧文件也没有历史记录。当您使用git log 时,您正在查看提交历史,从您命名的任何提交开始—如果您未指定起始提交,git log 以HEAD 开头—然后从那里返回, 到较旧的提交。
即使你使用git log -- <em>path</em> 也是如此:你仍在查看提交历史。您只是指示 git 仅向您显示提交历史的 子集,即在该提交与其前一个提交之间 path 已更改的提交。也就是说,如果您在日志中看到提交 1234567,这意味着 git diff --name-status 1234567^ 1234567 将向您显示由 path 命名的文件的状态更改(以及任何其他名称 - 和-状态变化)。
(语法<em>commit</em>^,它是任何有效的提交名称,后跟一个^字符,表示查找提交的父级,即,在commit 已创建。您也可以将其写为 <em>commit</em>~1。)
当您添加 --follow(并命名单个 path)时,这会告诉 git log 另一件事:如果给定 path 的状态发生变化 em> 是一个重命名,git log 可以改变它查看以前提交的方式。我们已经知道,由于git log 向您展示了这个提交,git diff 将显示该文件的名称和状态。在大多数情况下,文件的状态只是M,表示该文件之前存在并被修改过。但是如果状态是R,这表明git diff计算出在父提交和本次提交之间,文件被重命名。
没有--follow,git log 可能不再看到命名的path(因为差异计算它已被重命名),并且将停止显示提交。 (如果某些其他内容在那些早期提交中具有相同的路径,它可能会在更早的提交中再次出现,但这取决于早期的提交,并不是那么常见。)With @987654348 @,然而,git log 注意到 git diff 在刚才创建差异时决定文件已被重命名的事实。然后它停止在该名称下查找 path,而是开始查找 git diff 决定内容已重命名的路径 from。
也就是说,假设git log 将较旧的(父)提交ab93513 与较新的(子)提交0cea944 进行比较。进一步假设git diff 决定提交ab93513 中dir1/dir2/old.txt 的内容改为文件dir8/dir9/new.txt 的内容。最后,假设您要求git log 向您展示dir8/dir9/new.txt 带有 --follow 选项。然后,由于此时git diff 计算的更改是“内容相似,发生重命名”,git log 从寻找dir8/dir9/new.txt 切换到寻找dir1/dir2/old.txt。
此过程的关键是重命名检测。 由于 git 不跟踪文件历史记录,它必须检测重命名。重命名检测非常可靠,但并不完美——这取决于你如何配置 git,以及文件的内容是完全匹配还是仅仅“足够相似”。而--follow,在git log 中,一次只遵循一条路径(并且只能通过历史倒退——这在使用--reverse 时确实应该有效,但代码是实现--follow是一种可怕的 hack,并且只在一个时间方向上有效)。