更新

  • 2018/10/21 增加保留历史原因
  • 2018/07/08 初次发布

介绍

假如项目开始时使用了多个仓库开发,但是后来发现合并成一个仓库更好。

不同仓库的提交只能凭借提交时间来区分先后顺序,但实际上提交之后可以不用马上推送,因而推送时间才能决定先后顺序,因为集成是以当时服务器上仓库的状态为准。另外,推送时间并不会显示在任何地方,所以实际上是无法从提交记录中推断出推送时间的。

如果不同仓库合并为同一个仓库,那么不需要从推送时间推断不同仓库在某一时间的集成状态,只需要查看不同提交的先后顺序即可。

其次,同一个仓库更容易管理分支,如新建分支时只需要新建一个,而不是在多个仓库中分别新建。

因此我们需要将每个项目根目录中的内容移动到新仓库中对应名字的子目录中。

问题

Git 并不会追踪文件路径的变化。如果文件路径发生变化:

  • 使用 git log 只能看到文件路径变化之后的历史;如果要查看全部历史只能增加额外的 --follow 参数,但有时候使用这个参数获得的历史并不准确。
  • 使用 git blame 只能看到文件路径变化之后的所有修改,并不会追踪文件路径变化之前的历史。

仓库最重要的功能就是提供历史记录,最好能避开 Git 不追踪文件路径变化的问题,保留所有历史。

方案

合并的方案都涉及到修改仓库的目录结构,但是在此基础上可以选择修改或不修改历史。

不修改历史

网上搜索到的方案大部分都是这种,原理很简单:在合并提交中将历史合并进来的同时修改目录结构。

如何用 Git 合并两个库(合并历史记录,解决冲突/改写路径) - 太极客(Very Geek) - SegmentFault 思否

但是此方案有一个缺点:由于并未修改历史提交,在合并提交中移动了文件的目录,即文件路径的变化发生成合并提交中,会导致合并之前的历史记录出现前述的问题。

修改历史

为了可以追溯所有文件的修改历史,最好的方法是文件路径的变化发生在历史开始的时候,原理是将历史提交中文件的路径修改为新的路径。

使用修改历史的方法达到保留所有文件修改历史的目的,规避 Git 不追踪文件路径变化的问题。

实践

使用 filter-branch 命令即可修改历史,使用 --tree-filter 参数指定修改目录的命令。

1
2
3
4
git remote add other /path/to/other.git
git fetch other
git filter-branch -f --tag-name-filter cat --tree-filter "mkdir -p other/dir; git mv -k * other/dir" other/master
git merge other/master

注意如果有 Tag 需要处理则增加 --tag-name-filter

结果

在使用修改历史方式修改目录结构后,可以在仓库中使用 git log -- /path/to/file 看到文件的全部历史,也可以使用 git blame -- /path/to/file 查看文件中每一行对应的最后提交。

参考资料