Git跨仓库迁移代码文件,并保留git历史记录

背景:

由于架构优化、组件下沉、仓库调整等原因,在工作中时常需要将仓库A中的代码迁移到仓库B中,同时希望代码迁移后,能够保留其git历史记录。

  • 解决A仓库全部迁移到B仓库,并保留git历史记录问题
  • 解决A仓库中部分子目录文件迁移到B仓库,并保留git历史记录问题。
  • 解决2中,可能丢失部分git记录的问题。
  • Git设置多个远程仓库,并分别pull下来

    git项目初始化后,默认情况下会有一个叫origin的远程仓库。我们也可以用 git remote add repo_A [repo_A的仓库地址] 来新增一个"远程"仓库,然后使用git pull,将多个仓库的代码拉下来并merge。这样便可以实现A仓库全部迁移到B仓库,并保留git历史记录。

    注意这里的 [repo_A的仓库地址] 也可以是本地仓库路径。比如我这里添加了两个repo,repo-A是位于本地路径的"远程"仓库。

    Git filter-branch 重写历史

    我们已经知道了如何将A仓库全部迁移到B仓库,如果我们只需要将A仓库中的部分子目录文件迁移,就需要用到git重写历史的功能。 git filter-branch 是git提供的重写历史的命令,我们可以用 subdirectory-filter 来实现重写git项目子路径的git历史记录。

    --subdirectory-filter <directory> 重写子路径的git历史记录

    Only look at the history which touches the given subdirectory. The result will contain that directory (and only that) as its project root.

    subdirectory-filter 可以从git项目中过滤出给定子目录的git历史记录,同时会把给定的子目录作为git项目的根目录。

    举例看下效果,原始repo_A的状态如下,有other和ugc俩个子目录,现在我们只需要迁移A仓库的ugc目录,因此需要先把ugc目录的git历史记录过滤出来。

    image.png

    按上述的方案已经能够实现A仓库中部分子目录文件迁移到B仓库,但是某些情况下会丢失git记录。publishwtt是从publish模块抽离出来的,按上面的方案提取publishwtt路径的git历史记录,只能提取到新建publishwtt目录时间点之后的git记录,我们的情况是publishwtt刚从publish抽离不久,这样很多git历史会丢失,这是我们不愿意接受的。

    既然publishwtt从publish抽离来的,那么我们保留publishwtt和publish的父路径的git历史记录,就不会有git记录丢失的问题了。但是直接使用其父路径的历史记录,会把该目录的所有子目录的历史记录都保留下来,造成过多无用记录,我们还应该删除bytecert、profile等路径的git记录。可以用到 index-filter 来删除指定文件或文件夹,以及其git记录。

    git filter-branch --force --index-filter 'git rm --cached -r --ignore-unmatch 文件路径' --prune-empty --tag-name-filter cat -- --all
    

       该命令会遍历所有commit记录,删除提交记录中包含指定文件的记录,如果删除后是空提交,把空提交记录也删除。subdirectory-filterindex-filter配合使用,可以较好的实现A仓库中部分子目录文件迁移到B仓库,并保留git历史记录。

    实现过程:

      假设是要把repo A的 ugc/publish移动到repo B的 ugc/publish里面去。

  • 可以先把remote端给remove掉,这样本地的修改一定不会影响到网络上的版本(可选项);
  • 使用git filter-branch把需要的目录/文件筛选出来,放到对应目录里面去;
  • 然后将此修改提交
  • 1. git remote rm origin
    2. git filter-branch --subdirectory-filter <directory: ugc/publish> -- --all
    3. mkdir <directory: ugc/publish >
    4. mv * <directory: ugc/publish >
    5. git add .
    6. git commit
    

      备注:一般的仓库执行会比较快,对于主业务仓库,比如头条ttmain有20多万次commit记录,执行时间会很长,命令跑起来后就可以先去干其他事了。另外,3-6行为修改文件路径,视情况决定是否需要执行。

    第二步:将准备好的repo_A合并到repo_B中去。

  • 切换到repo B的目录;
  • 将本地的修改后的repo A添加为repo B的一个远程仓库,起名为from_repo_A
  • 将这个名为from_repo_A的远程仓库 pull进来,并merge;
  • 删除远程仓库 from_repo_A。
  • 1. cd <repo B directory>
    2. git remote add <from_repo_A> < repo_A_directory>
    3. git pull <from_repo_A> master --allow-unrelated-histories 
    4. git remote rm <from_repo_A>
    

    迁移完成后repo-B的sourcetree状态:

    迁移子目录可能丢失git历史记录问题

      前面已经介绍过迁移子目录可能会丢失git历史记录的场景,这里直接介绍实现过程:

      上图已经是使用subdirectory-filter重写历史记录得到的git项目,再继续使用index-filter删除bytecert、profile等无关子目录的git历史记录。删除bytecert历史记录:

    git filter-branch --force --index-filter 'git rm --cached -r --ignore-unmatch bytecert' --prune-empty --tag-name-filter cat -- --all

      该工程commit次数较多,每个删除任务执行时间较久,我们这里要删除的比较多,可以写一个.sh脚本,跑起来后,我们就可以去干其他事了。执行完后,将这个准备好的repo_A合并到repo_B中去即可。

    git filter-branch --force --index-filter 'git rm --cached -r --ignore-unmatch profile' --prune-empty --tag-name-filter cat -- --all
    git filter-branch --force --index-filter 'git rm --cached -r --ignore-unmatch bytecert' --prune-empty --tag-name-filter cat -- --all
    

      通过git的重写历史和支持设置多个远程仓库的能力,可以实现跨仓库迁移代码,并保留git历史记录。