首先,让我们澄清一些误解...
git
提交是在特定时间点上您整个仓库的快照。
git
提交不是差异或变更集。
git
提交不包含任何文件"重命名"信息。
git
本身不记录、监视、记录或以其他方式关注移动或重命名的文件(在创建提交时)。
以上可能与一些人的直觉相反,甚至对于一些人来说可能是惊人的(当我第一次了解到这个时,我自己也是如此),因为它与所有主要的前置源代码控制系统(SVN、TFS、CSV、Perforce (Helix之前)和其他系统)相反,因为所有这些系统都存储差异或变更集,这是它们模型的基础。
在内部,git
确实使用各种形式的差异比较和增量压缩,但这些故意对用户隐藏,因为它们被认为是一种实现细节。这是因为git的领域模型完全建立在原子提交的概念上,它代表了特定时间点整个存储库的快照状态。此外,它使用操作系统的低级文件更改检测功能来检测哪些特定文件已更改,而无需重新扫描整个工作目录:在Linux/POSIX上,它使用lstat
,在Windows(其中lstat
不可用)上,它使用fscache
。当git计算您存储库的哈希值时,它使用Merkel Tree structures来避免不断重新计算存储库中每个文件的哈希值。
那么,git
如何处理移动或重命名的文件?
...但是我的git
GUI明显显示了文件重命名,而不是文件删除+添加或编辑!
虽然git不会存储文件重命名的信息,但它仍然能够启发式地检测出在任何两个git提交之间被重命名的文件,以及检测出在您未提交的repo工作目录树和您的HEAD提交之间被重命名/移动的文件(也称为“与未修改文件比较”)。
例如:
考虑有2个文件的提交“快照1”:Foo.txt和Bar.txt。
然后将Foo.txt重命名为Qux.txt(并且没有进行其他更改)。
然后将其保存为新提交(“快照2”)。
如果您要求git使用“快照1”和“快照2”进行diff,则git可以看到Foo.txt已重命名为Qux.txt(而Bar.txt未更改),因为它们的内容(因此是文件的密码散列)相同,因此它推断从Foo.txt到Qux.txt进行了文件重命名。
趣闻:如果您要求git执行相同的diff,但使用“快照2”作为基本提交并使用“快照1”作为后续提交,则git将向您显示它检测到从Qux.txt返回到Foo.txt的重命名。
然而,如果您在两个提交之间做了更多操作,例如同时编辑文件,则git可能会将该文件视为一个新的单独文件而不是重命名文件。
这不是错误,而是一种特性:这种行为意味着git可以比文件中心的源代码控制(如TFS和SVN)更好地处理常见的文件系统级重构操作(例如拆分文件),并且您也不会看到与重构相关的虚假重命名。
例如,考虑一种重构场景,您将包含多个类定义的MultipleClasses.cs文件拆分为单独的.cs文件,每个文件一个类。在这种情况下,没有真正的“重命名”正在执行,而git的diff将向您显示1个文件被删除(MultipleClassesw.cs),同时新的SingleClass1.cs、SingleClass2.cs等文件被添加。
我想象您不希望它作为从MultipleClasses.cs重命名为SingleClass1.cs的重命名保存到源代码控制历史记录中,就像如果您允许第一个重命名在SVN/TFS中保存为重命名一样。
但是,正如您可以想象的那样,有时候git的启发式算法不起作用,
需要使用--follow和/或
--find-renames=(也称为-M)。
在此处很好地解释了这一点。
我个人首选的做法是将基于文件系统的和编辑代码文件的更改分别保留在不同的git提交中(因此提交仅包含编辑文件,或仅包含添加+删除文件,或仅包含拆分更改),这样您就可以更轻松地让git的--follow启发式算法检测重命名/移动。
(这确实意味着在使用VS的Refactor Rename功能时,我需要暂时将文件重新命名回来,以便我可以进行编辑文件但不包含任何重命名文件的提交)。
这与Visual Studio有什么关系呢?
考虑以下情况:
git
没有“移动”或“重命名”的概念。请记住:git提交是一个快照,而不是差异/增量。 - Daigit mv
在你的仓库中并不存储任何“特殊”的或独特的东西吗?一个 gitmv
命令与你自己手动移动文件是完全相同的,或者使用其他任何工具。这就是为什么没有 IDE 支持它的原因:因为根本不需要。 - Dai