重命名/移动修改后的文件后,Git不会跟踪历史记录

时间:2017-11-30 10:28:55

标签: git

问题:我需要更改存储库中整个目录的位置。为此,我使用 git mv ,如果需要,我将include头名称更改为当前正确的名称。问题是当我在一次提交期间执行两个操作时。在那种情况下,文件历史记录丢失了(git认为这是删除和创建新文件)。

解决方法: 如果我将这些操作拆分为单独的提交,则不会发生此问题。

问题重新出现:但是,即使我使用上述解决方案,问题也会在与master合并时返回。我有义务只使用no-ff合并。在这种情况下,对master分支的新提交是由两个提交的更改组成的......历史记录无法正常跟踪。

另一个丑陋的解决方法:我可以将这些提交单独提交给master。我无法提供无法编译的代码但是如果我将它从构建过程中排除它可能是可行的...但它很难看而且错了...

我想知道是否有更好的解决方案来解决这个问题。

2 个答案:

答案 0 :(得分:1)

如果您熟悉几乎所有其他版本控制系统(VCS),则很难理解Git对文件历史记录的作用。

事实是Git没有拥有文件历史记录。它在VCS中可能是独一无二的(尽管我没有使用过许多更神秘的VCS)。它最亲近的堂兄,Mercurial,确实有文件历史记录:Mercurial调用清单时,为Mercurial添加的每个文件都分配了一个唯一的编号,这决定了文件的标识。如果更改文件的名称或整个文件的目录,则会保留其标识,因为清单中存在此信息。

Git完全废除了这个概念。 Git根本没有文件历史记录。 Git只有提交

每个提交都存储源树的完整快照。每个提交也有一些父提交,通常只有一个。这更像是传统的基于提交的VCS:可以跟踪各种提交,或查看文件历史记录。但由于Git没有文件历史记录,因此它具有提交历史记录。

为了实现git log --follow和其他有用的项目,Git提供的是重命名检测,而不是文件历史记录。 Git可以查看任何一个特定的提交,并将该提交与其父提交或合并提交进行比较,并与其所有父提交进行比较。当它进行这种比较时,它提供检测文件的选项,这些文件通过该提交重命名:在父项中有一个名称但在子项中有不同名称的文件。 1

Git甚至在比较两个任意提交时提供此重命名检测,这些提交不仅仅是父级和子级。运行:

git diff --find-renames $hash1 $hash2

对两个提交进行比较,并且a/b/c.txt中“路径为$hash1的文件”的候选者看起来很像d/e/f.log中的路径为$hash2的文件,Git可能声称该文件已重命名(然后也可能已修改)。但重要的是要记住,Git只是合成一种将第一个文件转换为第二个的方法。两个提交中的两个实际文件以永久方式存储。它们永远不会被更改:只要存在这些提交,这两个文件就会以这种方式存储在这两个提交中。除非您希望它们,否则这两个文件实际上并不相关。 Git通过比较它们的相似性来“找到”重命名。给Git一组不同的“相似性”标准 - 例如,-M75%而不是-M50% - Git可以选择不同的“足够相似”的文件。

任何提交都没有发生任何事情。他们都被冻结了。但是使用一组不同的“重命名阈值”值,“中断阈值”等等,Git可能会配对不同的路径名。鉴于--no-renames,Git永远不会配对不同的路径名(尽管它仍然会将文件与相同的名称配对)。

(这种动态重命名检测很重要,在合并时很重要,因为merge运行两个 git diff --find-rename操作,从合并基础提交到两个分支提示中的每个提交正在合并。如果Git找到重命名,它会认为它。如果它没有找到重命名,它认为在提示中删除了基本文件,并在提示中创建了一个不同的文件。您可以控制重命名阈值,但你不能设置中断或复制阈值,至少在今天的Git版本中,2.15。)

1 对于合并提交,这一点的含义不太清楚,因为有多个父级:文件child.txt具有名称p1.txt的含义是什么父母#1中的父母#1和{​​{1}}?传统的VCS具有确定文件标识的独特内部编号系统,在这里指定了一个明确的含义,但在实践中,这个含义并不总是有用,而Linus Torvalds在这里的选择是为了取消这个完全的概念,可能部分是对此的反应。

答案 1 :(得分:1)

您可以将--follow命令的git log选项设置为默认值:

git config --global log.follow true